Repository: adobe-webplatform/Snap.svg Branch: master Commit: c8e483c96945 Files: 124 Total size: 3.2 MB Directory structure: gitextract_cmqa50dk/ ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CONTRIBUTING ├── Gruntfile.js ├── LICENSE ├── NOTICE ├── README.md ├── bower.json ├── component.json ├── demos/ │ ├── animated-game/ │ │ ├── index.html │ │ └── js/ │ │ ├── backbone.js │ │ ├── main.js │ │ ├── path-animal.js │ │ ├── seedrandom.js │ │ ├── snap.svg.js │ │ ├── tree-face.js │ │ └── underscore.js │ ├── animated-map/ │ │ └── index.html │ ├── clock/ │ │ └── index.html │ ├── illustrated-infographic-coffee/ │ │ └── index.html │ ├── pattern/ │ │ └── index.html │ ├── snap-ad/ │ │ ├── Gruntfile.js │ │ ├── README.md │ │ ├── SnapAd.tmproj │ │ ├── package.json │ │ ├── site/ │ │ │ ├── css/ │ │ │ │ └── screen.css │ │ │ ├── index.html │ │ │ ├── index.min.html │ │ │ └── js/ │ │ │ ├── main.js │ │ │ └── vendor/ │ │ │ └── require.js │ │ └── src/ │ │ ├── .sass-cache/ │ │ │ └── 7eb2e4ecfecd432261de95f74b5302c6e0956ac9/ │ │ │ └── screen.scssc │ │ ├── config.rb │ │ ├── css/ │ │ │ └── screen.css │ │ ├── index.html │ │ ├── js/ │ │ │ ├── app/ │ │ │ │ ├── app.js │ │ │ │ ├── burst.js │ │ │ │ ├── device.js │ │ │ │ ├── heart.js │ │ │ │ ├── logo.js │ │ │ │ └── mesh.js │ │ │ ├── main.js │ │ │ └── vendor/ │ │ │ ├── fss/ │ │ │ │ ├── Color.js │ │ │ │ ├── Core.js │ │ │ │ ├── Geometry.js │ │ │ │ ├── Light.js │ │ │ │ ├── Material.js │ │ │ │ ├── Math.js │ │ │ │ ├── Mesh.js │ │ │ │ ├── Object.js │ │ │ │ ├── Plane.js │ │ │ │ ├── Renderer.js │ │ │ │ ├── SVGRenderer.js │ │ │ │ ├── Scene.js │ │ │ │ ├── Triangle.js │ │ │ │ ├── Vector3.js │ │ │ │ ├── Vector4.js │ │ │ │ └── Vertex.js │ │ │ ├── fss.js │ │ │ ├── require.js │ │ │ ├── snap.svg-min.js │ │ │ └── snap.svg.js │ │ └── sass/ │ │ └── screen.scss │ ├── snap-mascot/ │ │ ├── crocodile-1.html │ │ ├── crocodile-2.html │ │ ├── index.html │ │ ├── snap-logo.html │ │ └── style.css │ └── tutorial/ │ ├── 1.html │ ├── 2.html │ ├── 3.html │ ├── 4.html │ ├── index.html │ └── tutorial.html ├── dist/ │ ├── snap.svg-min.js │ └── snap.svg.js ├── doc/ │ ├── css/ │ │ ├── dr.css │ │ ├── main.css │ │ ├── prism.css │ │ └── topcoat-desktop-light.css │ ├── fonts/ │ │ └── stylesheet.css │ ├── js/ │ │ └── prism.js │ └── reference.html ├── dr.json ├── history.md ├── package.json ├── src/ │ ├── align.js │ ├── amd-banner.js │ ├── amd-footer.js │ ├── animation.js │ ├── attr.js │ ├── attradd.js │ ├── class.js │ ├── colors.js │ ├── copy.js │ ├── element.js │ ├── equal.js │ ├── filter.js │ ├── matrix.js │ ├── mina.js │ ├── mouse.js │ ├── paper.js │ ├── path.js │ ├── set.js │ └── svg.js ├── template.dot └── test/ ├── attradd.js ├── attrs.js ├── class.js ├── colors.js ├── element.js ├── filter.js ├── gradients.js ├── matrix.js ├── mina.js ├── paper.js ├── path.js ├── primitives.js ├── res/ │ └── file-for-ajax.txt ├── set.js ├── snap-tests.js ├── system.js ├── test.html └── test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ TAGS *~ _* .DS_Store node_modules playground *.svg ================================================ FILE: .gitmodules ================================================ [submodule "third-party/mocha"] path = third-party/mocha url = https://github.com/visionmedia/mocha.git [submodule "third-party/eve"] path = third-party/eve url = https://github.com/adobe-webplatform/eve.git [submodule "third-party/expect"] path = third-party/expect url = https://github.com/LearnBoost/expect.js.git ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "node" sudo: true before_script: - npm install -g grunt-cli - npm install - grunt - cd test script: phantomjs test.js notifications: slack: snapsvg:1zY6dZQuE1vhnug8pLSbbYMv ================================================ FILE: CONTRIBUTING ================================================ Contributions to this code are covered by the Adobe contributors license agreement. Developers must sign and submit the Adobe CLA in order to contribute to this project. ================================================ FILE: Gruntfile.js ================================================ module.exports = function(grunt) { var pkg = grunt.file.readJSON("package.json"), core = [ "./src/mina.js", "./src/svg.js", "./src/element.js", "./src/animation.js", "./src/matrix.js", "./src/attr.js", "./src/class.js", "./src/attradd.js", "./src/paper.js", "./src/path.js", "./src/set.js", "./src/equal.js", "./src/mouse.js", "./src/filter.js", "./src/align.js", "./src/colors.js" ], src = [ "./node_modules/eve/eve.js", "./src/amd-banner.js", "./src/amd-footer.js" ]; src.splice(2, 0, core); // Project configuration. grunt.initConfig({ // Metadata. pkg: pkg, banner: grunt.file.read("./src/copy.js") .replace(/@VERSION/, pkg.version) .replace(/@DATE/, grunt.template.today("yyyy-mm-dd")) + "\n", // Task configuration. uglify: { options: { banner: "<%= banner %>", report: "min" }, dist: { src: "<%= concat.target.dest %>", dest: "dist/snap.svg-min.js" } }, concat: { options: { banner: "<%= banner %>" }, target: { dest: "dist/snap.svg.js", src: src } }, exec: { dr: { command: "node node_modules/dr.js/dr dr.json" }, test: { command: "cd test; phantomjs test.js" }, eslint: { command: "./node_modules/eslint/bin/eslint.js " + core.join(" ") }, }, prettify: { options: { indent: 4, indent_char: " ", wrap_line_length: 80, brace_style: "expand", unformatted: ["code", "pre", "script"] }, one: { src: "./doc/reference.html", dest: "./doc/reference.html" } } }); grunt.loadNpmTasks("grunt-contrib-concat"); grunt.loadNpmTasks("grunt-contrib-uglify"); grunt.loadNpmTasks("grunt-exec"); grunt.loadNpmTasks("grunt-prettify"); grunt.registerTask("default", ["exec:eslint", "concat", "uglify", "exec:dr", "prettify"]); grunt.registerTask("lint", ["exec:eslint"]); grunt.registerTask("test", ["exec:test"]); }; ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2013 Adobe Systems Incorporated 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. ================================================ FILE: NOTICE ================================================ Snap.svg is licensed under the Apache license version 2.0, January 2004 (see LICENSE file). Snap.svg uses the following third party libraries that may have licenses differing from that of Snap.svg itself. You can find the libraries and their respective licenses below. - eve ./node_modules/eve https://github.com/adobe-webplatform/eve/ Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 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. - Mocha ./node_modules/mocha https://github.com/visionmedia/mocha/ (The MIT License) Copyright (c) 2011-2013 TJ Holowaychuk 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. - Expect ./node_modules/expect.js https://github.com/LearnBoost/expect.js (The MIT License) Copyright (c) 2011 Guillermo Rauch 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. - Grunt ./node_modules/grunt http://gruntjs.com Copyright (c) 2013 "Cowboy" Ben Alman 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. - Backbone ./demos/animated-game/js/backbone.js http://backbonejs.org/ (The MIT License) Copyright (c) 2010-2013 Jeremy Ashkenas, DocumentCloud 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. - Underscore ./demos/animated-game/js/underscore.js http://underscorejs.org (The MIT License) Copyright (c) 2010-2013 Jeremy Ashkenas, DocumentCloud 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. - jQuery ./demos/animated-game/js/jquery-1.9.0.min.js http://http://jquery.com/ (The MIT License) Copyright 2013 jQuery Foundation and other contributors http://jquery.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. ================================================ FILE: README.md ================================================ [Snap.svg](http://snapsvg.io) · [![Build Status](https://travis-ci.org/adobe-webplatform/Snap.svg.svg?branch=dev)](https://travis-ci.org/adobe-webplatform/Snap.svg) [![CDNJS](https://img.shields.io/cdnjs/v/snap.svg.svg)](https://cdnjs.com/libraries/snap.svg/) [![GitHub Tag](https://img.shields.io/github/tag/adobe-webplatform/snap.svg.svg)](https://github.com/adobe-webplatform/Snap.svg/releases) [![License](https://img.shields.io/npm/l/snapsvg.svg)](https://github.com/adobe-webplatform/Snap.svg/blob/master/LICENSE) ====== A JavaScript SVG library for the modern web. Learn more at [snapsvg.io](http://snapsvg.io). [Follow us on Twitter.](https://twitter.com/snapsvg) ### Install * [Bower](http://bower.io/) - `bower install snap.svg` ![Bower](https://img.shields.io/bower/v/snap.svg.svg) * [npm](http://npmjs.com/) - `npm install snapsvg` [![npm version](https://img.shields.io/npm/v/snapsvg.svg?style=flat)](https://www.npmjs.com/package/snapsvg) [![Downloads](https://img.shields.io/npm/dt/snapsvg.svg)](https://www.npmjs.com/package/snapsvg) * Manual Minified - https://github.com/adobe-webplatform/Snap.svg/raw/master/dist/snap.svg-min.js * Manual Unminified - https://raw.githubusercontent.com/adobe-webplatform/Snap.svg/master/dist/snap.svg.js ### Learn * [About Snap.svg](http://snapsvg.io/about/) * [Getting Started](http://snapsvg.io/start/) * [API Reference](http://snapsvg.io/docs/) * [Slack Room](https://snapsvg.slack.com/). [Invite](https://snapsvg.slack.com/shared_invite/MTM2NTE4MTk3MDYwLTE0ODYwODgzNzUtYjQ0YmM1N2U0Mg) ### Use In your HTML file, load simply by: ```html ``` No other scripts are needed. Both the minified and uncompressed (for development) versions are in the `/dist` folder. #### webpack To load with webpack 2.x and 3.x, install [Imports Loader](https://github.com/webpack-contrib/imports-loader) (`npm i -D imports-loader`), and add the following to your webpack config: ```js module: { rules: [ { test: require.resolve('snapsvg/dist/snap.svg.js'), use: 'imports-loader?this=>window,fix=>module.exports=0', }, ], }, resolve: { alias: { snapsvg: 'snapsvg/dist/snap.svg.js', }, }, ``` Then, in any module you’d like to require Snap, use: ``` import Snap from 'snapsvg'; ``` ### Build [![Build Status](https://travis-ci.org/adobe-webplatform/Snap.svg.svg?branch=dev)](https://travis-ci.org/adobe-webplatform/Snap.svg) Snap.svg uses [Grunt](http://gruntjs.com/) to build. * Open the terminal from the Snap.svg directory: ```sh cd Snap.svg ``` * Install its command line interface (CLI) globally: ```sh npm install -g grunt-cli ``` _*You might need to use `sudo npm`, depending on your configuration._ * Install dependencies with npm: ```sh npm install ``` _*Snap.svg uses Grunt 0.4.0. You might want to [read](http://gruntjs.com/getting-started) more on their website if you haven’t upgraded since a lot has changed._ * To build the files run ```sh grunt ``` * The results will be built into the `dist` folder. * Alternatively type `grunt watch` to have the build run automatically when you make changes to source files. ### Testing Tests are located in `test` folder. To run tests, simply open `test.html` in there. Automatic tests use PhantomJS to scrap this file, so you can use it as a reference. Alternatively, install [PhantomJS](http://phantomjs.org) and run command ```sh grunt test ``` ### Contribute * [Fill out the CLA](http://snapsvg.io/contributions/). * [Fork](https://help.github.com/articles/fork-a-repo) the repo. * Create a branch: ```sh git checkout -b my_branch ``` * Add your changes. * Check that tests are passing * Commit your changes: ```sh git commit -am "Added some awesome stuff" ``` * Push your branch: ```sh git push origin my_branch ``` * Make a [pull request](https://help.github.com/articles/using-pull-requests) to `dev`(!) branch. *Note:* Pull requests to other branches than `dev` or without filled CLA wouldn’t be accepted. ================================================ FILE: bower.json ================================================ { "name": "Snap.svg", "version": "0.5.1", "homepage": "http://snapsvg.io", "authors": [ "Dmitry Baranovskiy " ], "description": "The JavaScript library for modern SVG graphics", "main": "dist/snap.svg-min.js", "keywords": [ "svg", "snap", "js", "javascript" ], "license": "Apache 2", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "demos", "src" ] } ================================================ FILE: component.json ================================================ { "name": "Snap.svg", "repo": "adobe-webplatform/Snap.svg", "description": "The JavaScript library for modern SVG graphics.", "version": "0.5.1", "keywords": ["svg", "snap", "js", "javascript"], "dependencies": {}, "development": {}, "main": "dist/snap.svg-min.js", "scripts": [ "dist/snap.svg-min.js" ] } ================================================ FILE: demos/animated-game/index.html ================================================ illustration
================================================ FILE: demos/animated-game/js/backbone.js ================================================ // Backbone.js 1.0.0 // (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org (function(root, factory) { // Set up Backbone appropriately for the environment. if (typeof exports !== 'undefined') { // Node/CommonJS, no need for jQuery in that case. factory(root, exports, require('underscore')); } else if (typeof define === 'function' && define.amd) { // AMD define(['underscore', 'jquery', 'exports'], function(_, $, exports) { // Export global even in AMD case in case this script is loaded with // others that may still expect a global Backbone. root.Backbone = factory(root, exports, _, $); }); } else { // Browser globals root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); } }(this, function(root, Backbone, _, $) { // Initial Setup // ------------- // Save the previous value of the `Backbone` variable, so that it can be // restored later on, if `noConflict` is used. var previousBackbone = root.Backbone; // Create local references to array methods we'll want to use later. var array = []; var push = array.push; var slice = array.slice; var splice = array.splice; // Current version of the library. Keep in sync with `package.json`. Backbone.VERSION = '1.0.0'; // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable. Backbone.$ = $; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; }; // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and // set a `X-Http-Method-Override` header. Backbone.emulateHTTP = false; // Turn on `emulateJSON` to support legacy servers that can't deal with direct // `application/json` requests ... will encode the body as // `application/x-www-form-urlencoded` instead and will send the model in a // form param named `model`. Backbone.emulateJSON = false; // Backbone.Events // --------------- // A module that can be mixed in to *any object* in order to provide it with // custom events. You may bind with `on` or remove with `off` callback // functions to an event; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; // _.extend(object, Backbone.Events); // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // var Events = Backbone.Events = { // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. on: function(name, callback, context) { if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; this._events || (this._events = {}); var events = this._events[name] || (this._events[name] = []); events.push({callback: callback, context: context, ctx: context || this}); return this; }, // Bind an event to only be triggered a single time. After the first time // the callback is invoked, it will be removed. once: function(name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; var once = _.once(function() { self.off(name, once); callback.apply(this, arguments); }); once._callback = callback; return this.on(name, once, context); }, // Remove one or many callbacks. If `context` is null, removes all // callbacks with that function. If `callback` is null, removes all // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. off: function(name, callback, context) { var retain, ev, events, names, i, l, j, k; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!name && !callback && !context) { this._events = {}; return this; } names = name ? [name] : _.keys(this._events); for (i = 0, l = names.length; i < l; i++) { name = names[i]; if (events = this._events[name]) { this._events[name] = retain = []; if (callback || context) { for (j = 0, k = events.length; j < k; j++) { ev = events[j]; if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || (context && context !== ev.context)) { retain.push(ev); } } } if (!retain.length) delete this._events[name]; } } return this; }, // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). trigger: function(name) { if (!this._events) return this; var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; var events = this._events[name]; var allEvents = this._events.all; if (events) triggerEvents(events, args); if (allEvents) triggerEvents(allEvents, arguments); return this; }, // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. stopListening: function(obj, name, callback) { var listeners = this._listeners; if (!listeners) return this; var deleteListener = !name && !callback; if (typeof name === 'object') callback = this; if (obj) (listeners = {})[obj._listenerId] = obj; for (var id in listeners) { listeners[id].off(name, callback, this); if (deleteListener) delete this._listeners[id]; } return this; } }; // Regular expression used to split event strings. var eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event // names `"change blur"` and jQuery-style event maps `{change: action}` // in terms of the existing API. var eventsApi = function(obj, action, name, rest) { if (!name) return true; // Handle event maps. if (typeof name === 'object') { for (var key in name) { obj[action].apply(obj, [key, name[key]].concat(rest)); } return false; } // Handle space separated event names. if (eventSplitter.test(name)) { var names = name.split(eventSplitter); for (var i = 0, l = names.length; i < l; i++) { obj[action].apply(obj, [names[i]].concat(rest)); } return false; } return true; }; // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); } }; var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; // Inversion-of-control versions of `on` and `once`. Tell *this* object to // listen to an event in another object ... keeping track of what it's // listening to. _.each(listenMethods, function(implementation, method) { Events[method] = function(obj, name, callback) { var listeners = this._listeners || (this._listeners = {}); var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); listeners[id] = obj; if (typeof name === 'object') callback = this; obj[implementation](name, callback, this); return this; }; }); // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; // Allow the `Backbone` object to serve as a global event bus, for folks who // want global "pubsub" in a convenient place. _.extend(Backbone, Events); // Backbone.Model // -------------- // Backbone **Models** are the basic data object in the framework -- // frequently representing a row in a table in a database on your server. // A discrete chunk of data and a bunch of useful, related methods for // performing computations and transformations on that data. // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var defaults; var attrs = attributes || {}; options || (options = {}); this.cid = _.uniqueId('c'); this.attributes = {}; _.extend(this, _.pick(options, modelOptions)); if (options.parse) attrs = this.parse(attrs, options) || {}; if (defaults = _.result(this, 'defaults')) { attrs = _.defaults({}, attrs, defaults); } this.set(attrs, options); this.changed = {}; this.initialize.apply(this, arguments); }; // A list of options to be attached directly to the model, if provided. var modelOptions = ['url', 'urlRoot', 'collection']; // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, // The value returned during the last failed validation. validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. idAttribute: 'id', // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Return a copy of the model's `attributes` object. toJSON: function(options) { return _.clone(this.attributes); }, // Proxy `Backbone.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. sync: function() { return Backbone.sync.apply(this, arguments); }, // Get the value of an attribute. get: function(attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. escape: function(attr) { return _.escape(this.get(attr)); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has: function(attr) { return this.get(attr) != null; }, // Set a hash of model attributes on the object, firing `"change"`. This is // the core primitive operation of a model, updating the data and notifying // anyone who needs to know about the change in state. The heart of the beast. set: function(key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); // Run validation. if (!this._validate(attrs, options)) return false; // Extract attributes and options. unset = options.unset; silent = options.silent; changes = []; changing = this._changing; this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // For each `set` attribute, update or delete the current value. for (attr in attrs) { val = attrs[attr]; if (!_.isEqual(current[attr], val)) changes.push(attr); if (!_.isEqual(prev[attr], val)) { this.changed[attr] = val; } else { delete this.changed[attr]; } unset ? delete current[attr] : current[attr] = val; } // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = true; for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. if (changing) return this; if (!silent) { while (this._pending) { this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }, // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. unset: function(attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. clear: function(options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged: function(attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, // Return an object containing all the attributes that have changed, or // false if there are no changed attributes. Useful for determining what // parts of a view need to be updated and/or what attributes need to be // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. changedAttributes: function(diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false; var old = this._changing ? this._previousAttributes : this.attributes; for (var attr in diff) { if (_.isEqual(old[attr], (val = diff[attr]))) continue; (changed || (changed = {}))[attr] = val; } return changed; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. previous: function(attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes: function() { return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. fetch: function(options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { if (!model.set(model.parse(resp, options), options)) return false; if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. save: function(key, val, options) { var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. if (key == null || typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`. if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false; options = _.extend({validate: true}, options); // Do not persist invalid models. if (!this._validate(attrs, options)) return false; // Set temporary attributes if `{wait: true}`. if (attrs && options.wait) { this.attributes = _.extend({}, attributes, attrs); } // After a successful server-side save, the client is (optionally) // updated with the server-side state. if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { return false; } if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); if (method === 'patch') options.attrs = attrs; xhr = this.sync(method, this, options); // Restore attributes. if (attrs && options.wait) this.attributes = attributes; return xhr; }, // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. destroy: function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; var destroy = function() { model.trigger('destroy', model, model.collection, options); }; options.success = function(resp) { if (options.wait || model.isNew()) destroy(); if (success) success(model, resp, options); if (!model.isNew()) model.trigger('sync', model, resp, options); }; if (this.isNew()) { options.success(); return false; } wrapError(this, options); var xhr = this.sync('delete', this, options); if (!options.wait) destroy(); return xhr; }, // Default URL for the model's representation on the server -- if you're // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. parse: function(resp, options) { return resp; }, // Create a new model with identical attributes to this one. clone: function() { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. isNew: function() { return this.id == null; }, // Check if the model is currently in a valid state. isValid: function(options) { return this._validate({}, _.extend(options || {}, { validate: true })); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error})); return false; } }); // Underscore methods that we want to implement on the Model. var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; // Mix in each Underscore method as a proxy to `Model#attributes`. _.each(modelMethods, function(method) { Model.prototype[method] = function() { var args = slice.call(arguments); args.unshift(this.attributes); return _[method].apply(_, args); }; }); // Backbone.Collection // ------------------- // If models tend to represent a single row of data, a Backbone Collection is // more analagous to a table full of data ... or a small slice or page of that // table, or a collection of rows that belong together for a particular reason // -- all of the messages in this particular folder, all of the documents // belonging to this particular author, and so on. Collections maintain // indexes of their models, both in order, and for lookup by `id`. // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); if (options.url) this.url = options.url; if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; this._reset(); this.initialize.apply(this, arguments); if (models) this.reset(models, _.extend({silent: true}, options)); }; // Default options for `Collection#set`. var setOptions = {add: true, remove: true, merge: true}; var addOptions = {add: true, merge: false, remove: false}; // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { // The default model for a collection is just a **Backbone.Model**. // This should be overridden in most cases. model: Model, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // The JSON representation of a Collection is an array of the // models' attributes. toJSON: function(options) { return this.map(function(model){ return model.toJSON(options); }); }, // Proxy `Backbone.sync` by default. sync: function() { return Backbone.sync.apply(this, arguments); }, // Add a model, or list of models to the set. add: function(models, options) { return this.set(models, _.defaults(options || {}, addOptions)); }, // Remove a model, or a list of models from the set. remove: function(models, options) { models = _.isArray(models) ? models.slice() : [models]; options || (options = {}); var i, l, index, model; for (i = 0, l = models.length; i < l; i++) { model = this.get(models[i]); if (!model) continue; delete this._byId[model.id]; delete this._byId[model.cid]; index = this.indexOf(model); this.models.splice(index, 1); this.length--; if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); } this._removeReference(model); } return this; }, // Update a collection by `set`-ing a new list of models, adding new ones, // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **Model#set**, // the core operation for updating the data contained by the collection. set: function(models, options) { options = _.defaults(options || {}, setOptions); if (options.parse) models = this.parse(models, options); if (!_.isArray(models)) models = models ? [models] : []; var i, l, model, attrs, existing, sort; var at = options.at; var sortable = this.comparator && (at == null) && options.sort !== false; var sortAttr = _.isString(this.comparator) ? this.comparator : null; var toAdd = [], toRemove = [], modelMap = {}; // Turn bare objects into model references, and prevent invalid models // from being added. for (i = 0, l = models.length; i < l; i++) { if (!(model = this._prepareModel(models[i], options))) continue; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. if (existing = this.get(model)) { if (options.remove) modelMap[existing.cid] = true; if (options.merge) { existing.set(model.attributes, options); if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; } // This is a new model, push it to the `toAdd` list. } else if (options.add) { toAdd.push(model); // Listen to added models' events, and index models for lookup by // `id` and by `cid`. model.on('all', this._onModelEvent, this); this._byId[model.cid] = model; if (model.id != null) this._byId[model.id] = model; } } // Remove nonexistent models if appropriate. if (options.remove) { for (i = 0, l = this.length; i < l; ++i) { if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); } if (toRemove.length) this.remove(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. if (toAdd.length) { if (sortable) sort = true; this.length += toAdd.length; if (at != null) { splice.apply(this.models, [at, 0].concat(toAdd)); } else { push.apply(this.models, toAdd); } } // Silently sort the collection if appropriate. if (sort) this.sort({silent: true}); if (options.silent) return this; // Trigger `add` events. for (i = 0, l = toAdd.length; i < l; i++) { (model = toAdd[i]).trigger('add', model, this, options); } // Trigger `sort` if the collection was sorted. if (sort) this.trigger('sort', this, options); return this; }, // When you have more items than you want to add or remove individually, // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. reset: function(models, options) { options || (options = {}); for (var i = 0, l = this.models.length; i < l; i++) { this._removeReference(this.models[i]); } options.previousModels = this.models; this._reset(); this.add(models, _.extend({silent: true}, options)); if (!options.silent) this.trigger('reset', this, options); return this; }, // Add a model to the end of the collection. push: function(model, options) { model = this._prepareModel(model, options); this.add(model, _.extend({at: this.length}, options)); return model; }, // Remove a model from the end of the collection. pop: function(options) { var model = this.at(this.length - 1); this.remove(model, options); return model; }, // Add a model to the beginning of the collection. unshift: function(model, options) { model = this._prepareModel(model, options); this.add(model, _.extend({at: 0}, options)); return model; }, // Remove a model from the beginning of the collection. shift: function(options) { var model = this.at(0); this.remove(model, options); return model; }, // Slice out a sub-array of models from the collection. slice: function(begin, end) { return this.models.slice(begin, end); }, // Get a model from the set by id. get: function(obj) { if (obj == null) return void 0; return this._byId[obj.id != null ? obj.id : obj.cid || obj]; }, // Get the model at the given index. at: function(index) { return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { if (_.isEmpty(attrs)) return first ? void 0 : []; return this[first ? 'find' : 'filter'](function(model) { for (var key in attrs) { if (attrs[key] !== model.get(key)) return false; } return true; }); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. findWhere: function(attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. sort: function(options) { if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); // Run sort based on type of `comparator`. if (_.isString(this.comparator) || this.comparator.length === 1) { this.models = this.sortBy(this.comparator, this); } else { this.models.sort(_.bind(this.comparator, this)); } if (!options.silent) this.trigger('sort', this, options); return this; }, // Figure out the smallest index at which a model should be inserted so as // to maintain order. sortedIndex: function(model, value, context) { value || (value = this.comparator); var iterator = _.isFunction(value) ? value : function(model) { return model.get(value); }; return _.sortedIndex(this.models, model, iterator, context); }, // Pluck an attribute from each model in the collection. pluck: function(attr) { return _.invoke(this.models, 'get', attr); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. fetch: function(options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var success = options.success; var collection = this; options.success = function(resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success(collection, resp, options); collection.trigger('sync', collection, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. create: function(model, options) { options = options ? _.clone(options) : {}; if (!(model = this._prepareModel(model, options))) return false; if (!options.wait) this.add(model, options); var collection = this; var success = options.success; options.success = function(resp) { if (options.wait) collection.add(model, options); if (success) success(model, resp, options); }; model.save(null, options); return model; }, // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. parse: function(resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. clone: function() { return new this.constructor(this.models); }, // Private method to reset all internal state. Called when the collection // is first initialized or reset. _reset: function() { this.length = 0; this.models = []; this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. _prepareModel: function(attrs, options) { if (attrs instanceof Model) { if (!attrs.collection) attrs.collection = this; return attrs; } options || (options = {}); options.collection = this; var model = new this.model(attrs, options); if (!model._validate(attrs, options)) { this.trigger('invalid', this, attrs, options); return false; } return model; }, // Internal method to sever a model's ties to a collection. _removeReference: function(model) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, // Internal method called every time a model in the set fires an event. // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. _onModelEvent: function(event, model, collection, options) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (model && event === 'change:' + model.idAttribute) { delete this._byId[model.previous(model.idAttribute)]; if (model.id != null) this._byId[model.id] = model; } this.trigger.apply(this, arguments); } }); // Underscore methods that we want to implement on the Collection. // 90% of the core usefulness of Backbone Collections is actually implemented // right here: var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'chain']; // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function(method) { Collection.prototype[method] = function() { var args = slice.call(arguments); args.unshift(this.models); return _[method].apply(_, args); }; }); // Underscore methods that take a property name as an argument. var attributeMethods = ['groupBy', 'countBy', 'sortBy']; // Use attributes instead of properties. _.each(attributeMethods, function(method) { Collection.prototype[method] = function(value, context) { var iterator = _.isFunction(value) ? value : function(model) { return model.get(value); }; return _[method](this.models, iterator, context); }; }); // Backbone.View // ------------- // Backbone Views are almost more convention than they are actual code. A View // is simply a JavaScript object that represents a logical chunk of UI in the // DOM. This might be a single item, an entire list, a sidebar or panel, or // even the surrounding frame which wraps your whole app. Defining a chunk of // UI as a **View** allows you to define your DOM events declaratively, without // having to worry about render order ... and makes it easy for the view to // react to specific changes in the state of your models. // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... var View = Backbone.View = function(options) { this.cid = _.uniqueId('view'); this._configure(options || {}); this._ensureElement(); this.initialize.apply(this, arguments); this.delegateEvents(); }; // Cached regex to split keys for `delegate`. var delegateEventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; // Set up all inheritable **Backbone.View** properties and methods. _.extend(View.prototype, Events, { // The default `tagName` of a View's element is `"div"`. tagName: 'div', // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be prefered to global lookups where possible. $: function(selector) { return this.$el.find(selector); }, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. render: function() { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable Backbone.Events listeners. remove: function() { this.$el.remove(); this.stopListening(); return this; }, // Change the view's element (`this.el` property), including event // re-delegation. setElement: function(element, delegate) { if (this.$el) this.undelegateEvents(); this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); this.el = this.$el[0]; if (delegate !== false) this.delegateEvents(); return this; }, // Set callbacks, where `this.events` is a hash of // // *{"event selector": "callback"}* // // { // 'mousedown .title': 'edit', // 'click .button': 'save' // 'click .open': function(e) { ... } // } // // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. // This only works for delegate-able events: not `focus`, `blur`, and // not `change`, `submit`, and `reset` in Internet Explorer. delegateEvents: function(events) { if (!(events || (events = _.result(this, 'events')))) return this; this.undelegateEvents(); for (var key in events) { var method = events[key]; if (!_.isFunction(method)) method = this[events[key]]; if (!method) continue; var match = key.match(delegateEventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateEvents' + this.cid; if (selector === '') { this.$el.on(eventName, method); } else { this.$el.on(eventName, selector, method); } } return this; }, // Clears all callbacks previously bound to the view with `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // Backbone views attached to the same DOM element. undelegateEvents: function() { this.$el.off('.delegateEvents' + this.cid); return this; }, // Performs the initial configuration of a View with a set of options. // Keys with special meaning *(e.g. model, collection, id, className)* are // attached directly to the view. See `viewOptions` for an exhaustive // list. _configure: function(options) { if (this.options) options = _.extend({}, _.result(this, 'options'), options); _.extend(this, _.pick(options, viewOptions)); this.options = options; }, // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. _ensureElement: function() { if (!this.el) { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.id) attrs.id = _.result(this, 'id'); if (this.className) attrs['class'] = _.result(this, 'className'); var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); this.setElement($el, false); } else { this.setElement(_.result(this, 'el'), false); } } }); // Backbone.sync // ------------- // Override this function to change the manner in which Backbone persists // models to the server. You will be passed the type of request, and the // model in question. By default, makes a RESTful Ajax request // to the model's `url()`. Some possible customizations could be: // // * Use `setTimeout` to batch rapid-fire updates into a single request. // * Send up the models as XML instead of JSON. // * Persist models via WebSockets instead of Ajax. // // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests // as `POST`, with a `_method` parameter containing the true HTTP method, // as well as all requests with the body as `application/x-www-form-urlencoded` // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. Backbone.sync = function(method, model, options) { var type = methodMap[method]; // Default options, unless specified. _.defaults(options || (options = {}), { emulateHTTP: Backbone.emulateHTTP, emulateJSON: Backbone.emulateJSON }); // Default JSON-request options. var params = {type: type, dataType: 'json'}; // Ensure that we have a URL. if (!options.url) { params.url = _.result(model, 'url') || urlError(); } // Ensure that we have the appropriate request data. if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { params.contentType = 'application/json'; params.data = JSON.stringify(options.attrs || model.toJSON(options)); } // For older servers, emulate JSON by encoding the request into an HTML-form. if (options.emulateJSON) { params.contentType = 'application/x-www-form-urlencoded'; params.data = params.data ? {model: params.data} : {}; } // For older servers, emulate HTTP by mimicking the HTTP method with `_method` // And an `X-HTTP-Method-Override` header. if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; options.beforeSend = function(xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; } // Don't process data on a non-GET request. if (params.type !== 'GET' && !options.emulateJSON) { params.processData = false; } // If we're sending a `PATCH` request, and we're in an old Internet Explorer // that still has ActiveX enabled by default, override jQuery to use that // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. if (params.type === 'PATCH' && window.ActiveXObject && !(window.external && window.external.msActiveXFilteringEnabled)) { params.xhr = function() { return new ActiveXObject("Microsoft.XMLHTTP"); }; } // Make the request, allowing the user to override any Ajax options. var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); model.trigger('request', model, xhr, options); return xhr; }; // Map from CRUD to HTTP for our default `Backbone.sync` implementation. var methodMap = { 'create': 'POST', 'update': 'PUT', 'patch': 'PATCH', 'delete': 'DELETE', 'read': 'GET' }; // Set the default implementation of `Backbone.ajax` to proxy through to `$`. // Override this if you'd like to use a different library. Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; // Backbone.Router // --------------- // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. var Router = Backbone.Router = function(options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply(this, arguments); }; // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. _.extend(Router.prototype, Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Manually bind a single named route to a callback. For example: // // this.route('search/:query/p:num', 'search', function(query, num) { // ... // }); // route: function(route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; Backbone.history.route(route, function(fragment) { var args = router._extractParameters(route, fragment); callback && callback.apply(router, args); router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); Backbone.history.trigger('route', router, name, args); }); return this; }, // Simple proxy to `Backbone.history` to save a fragment into the history. navigate: function(fragment, options) { Backbone.history.navigate(fragment, options); return this; }, // Bind all defined routes to `Backbone.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. _bindRoutes: function() { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); while ((route = routes.pop()) != null) { this.route(route, this.routes[route]); } }, // Convert a route string into a regular expression, suitable for matching // against the current location hash. _routeToRegExp: function(route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional){ return optional ? match : '([^\/]+)'; }) .replace(splatParam, '(.*?)'); return new RegExp('^' + route + '$'); }, // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. _extractParameters: function(route, fragment) { var params = route.exec(fragment).slice(1); return _.map(params, function(param) { return param ? decodeURIComponent(param) : null; }); } }); // Backbone.History // ---------------- // Handles cross-browser history management, based on either // [pushState](http://diveintohtml5.info/history.html) and real URLs, or // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. var History = Backbone.History = function() { this.handlers = []; _.bindAll(this, 'checkUrl'); // Ensure that `History` can be used outside of the browser. if (typeof window !== 'undefined') { this.location = window.location; this.history = window.history; } }; // Cached regex for stripping a leading hash/slash and trailing space. var routeStripper = /^[#\/]|\s+$/g; // Cached regex for stripping leading and trailing slashes. var rootStripper = /^\/+|\/+$/g; // Cached regex for detecting MSIE. var isExplorer = /msie [\w.]+/; // Cached regex for removing a trailing slash. var trailingSlash = /\/$/; // Has the history handling already been started? History.started = false; // Set up all inheritable **Backbone.History** properties and methods. _.extend(History.prototype, Events, { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. getHash: function(window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the cross-browser normalized URL fragment, either from the URL, // the hash, or the override. getFragment: function(fragment, forcePushState) { if (fragment == null) { if (this._hasPushState || !this._wantsHashChange || forcePushState) { fragment = this.location.pathname; var root = this.root.replace(trailingSlash, ''); if (!fragment.indexOf(root)) fragment = fragment.substr(root.length); } else { fragment = this.getHash(); } } return fragment.replace(routeStripper, ''); }, // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. start: function(options) { if (History.started) throw new Error("Backbone.history has already been started"); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? this.options = _.extend({}, {root: '/'}, this.options, options); this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); if (oldIE && this._wantsHashChange) { this.iframe = Backbone.$('
================================================ FILE: demos/snap-mascot/snap-logo.html ================================================ Snap ================================================ FILE: demos/snap-mascot/style.css ================================================ html { overflow: hidden; } body { background: #eee; text-align: center; } svg { display: inline-block; } iframe { display: inline-block; border: none; height: 300px; width: 400px; margin: 10px; } ================================================ FILE: demos/tutorial/1.html ================================================ Tutorial
  1. // Simple dashed pattern on circle var s = Snap("#svg");
  2. // This will be our shape. It could be anything. var bigCircle = s.circle(150, 150, 100);
  3. bigCircle.attr({ stroke: "#000", strokeWidth: 5 });
  4. // Now let's create pattern var p = s.path("M110,95,95,110M115,100,100,115").attr({ fill: "none", stroke: "#bada55", strokeWidth: 4 });
  5. //This is where our pattern cut will happen: var cut = s.rect(100, 100, 10, 10).attr({fill: "#fff", opacity: .5})
  6. cut.remove();
  7. //make it a pattern var ptrn = p.pattern(100, 100, 10, 10); //ptrn is an invisible <pattern> element.
  8. // Then use it as a fill on the big circle bigCircle.attr({ fill: ptrn });
  9. //We still have access to original path for the pattern, lets animate it a bit:
  10. p.animate({strokeWidth: 1, stroke: "#FF4136"}, 1e3); //Note that pattern could have as many elements as you want
================================================ FILE: demos/tutorial/2.html ================================================ Tutorial
  1. // Simple dashed pattern on circle with mask var s = Snap("#svg"); var bigCircle = s.circle(150, 150, 100).attr({ fill: "#bada55", stroke: "#000", strokeWidth: 6 }); var p = s.path("M110,95,95,110M115,100,100,115").attr({ fill: "none", stroke: "#bada55", strokeWidth: 4 }); var ptrn = p.pattern(100, 100, 10, 10); bigCircle.attr({ fill: ptrn }); //Here is our circle from the first Snap-bit.
  2. //Lets create a masking circle
  3. var ring = s.circle(150, 150, 92).attr({ fill: "none", stroke: "#fff", strokeWidth: 10 });
  4. //This looks correct, but only because our background is white
  5. s.rect(0, 150, 300, 30).attr({fill: "#85144B"}).insertBefore(bigCircle); //Uh-oh, lets try to apply this ring as a mask:
  6. bigCircle.attr({ mask: ring });
  7. //Not exactly what we want. We need to invert the mask
  8. var mask = s.mask();
  9. // Background rect: mask.add(s.rect(0, 0, "100%", "100%").attr({fill: "#fff"}));
  10. // and our ring, but black mask.add(ring.attr({stroke: "#000"}));
  11. bigCircle.attr({ mask: mask });
  12. //Now, let’s animate the ring:
  13. ring.animate({r: 10}, 1e3);
================================================ FILE: demos/tutorial/3.html ================================================ Tutorial
  1. // Simple dashed pattern on circle with mask // Lets connect mask and circle together. Also useful if we have a path // and can’t calculate offset easily. var s = Snap("#svg"); // So, lets start with an empty circle. // It’s important that it will not have any attributes set var bigCircle = s.circle(150, 150, 100);
  2. //----------------------------------------------------- // Lets use it again for patterned fill var c1 = bigCircle.use(); // Create pattern var p = s.path("M110,95,95,110M115,100,100,115").attr({ fill: "none", stroke: "#bada55", strokeWidth: 4 }); var ptrn = p.pattern(100, 100, 10, 10); // and apply some nice attributes c1.attr({ fill: ptrn });
  3. //----------------------------------------------------- // Lets use it for stroke var c2 = bigCircle.use(); c2.attr({ fill: "none", stroke: "#000", strokeWidth: 6 });
  4. // Lets create a masking circle
  5. var ring = bigCircle.use(); ring.attr({ fill: "none", stroke: "#000", strokeWidth: 20 // we need only inner 10px of it });
  6. // Hide bigCircle by moving it to <defs> bigCircle.toDefs();
  7. var mask = s.mask();
  8. // Background rect: mask.add(s.rect(0, 0, "100%", "100%").attr({fill: "#fff"}));
  9. // and our ring mask.add(ring);
  10. c1.attr({ mask: mask });
  11. //Now, let’s animate bigCircle:
  12. bigCircle.animate({r: 50}, 5e3, mina.elastic); // Despite bigCircle is not visible it affect all 3 “uses” of it.
================================================ FILE: demos/tutorial/4.html ================================================ Tutorial
  1. // Text on the path var s = Snap("#svg");
  2. // Setting the background var bg = s.rect(50, 50, 200, 200, 10).attr({fill: "#ccc"});
  3. var t = s.text(150, 150, "Test Text").attr({ font: "30px Helvetica, sans-serif", textAnchor: "middle", fill: "#ddd" });
  4. // Lets create a mask
  5. var t2 = t.use().attr({ stroke: "#000", strokeWidth: 10, strokeLinecap: "round", strokeLinejoin: "round" });
  6. var mask = s.mask();
  7. // Background rect: mask.add(s.rect(0, 0, "100%", "100%").attr({fill: "#fff"}));
  8. // and our ring mask.add(t2);
  9. bg.attr({ mask: mask });
  10. //Now, let’s animate:
  11. t2.animate({strokeWidth: 4}, 5e3, mina.bounce);
================================================ FILE: demos/tutorial/index.html ================================================ Tutorial
  1. // First, let's create our drawing surface out of an existing SVG element // If you want to create a new surface just provide dimensions // like s = Snap(800, 600); var s = Snap("#svg");
  2. // Let's create a big circle in the middle: var bigCircle = s.circle(150, 150, 100); // By default it is black, let's change its attributes
  3. bigCircle.attr({ fill: "#bada55", stroke: "#000", strokeWidth: 5 });
  4. // Now let's create another small circle: var smallCircle = s.circle(100, 150, 70);
  5. // Let's put this small circle and another one into a group: var discs = s.group(smallCircle, s.circle(200, 150, 70));
  6. // Now we can change attributes for the whole group discs.attr({ fill: "#fff" });
  7. // Now more interesting stuff // Let's assign this group as a mask for our big circle bigCircle.attr({ mask: discs });
  8. // Despite our small circle being part of a group // and part of a mask we can still access it: smallCircle.animate({r: 50}, 1000);
  9. // We don’t have a reference for second small circle, // but we can easily grab it with CSS selectors: discs.select("circle:nth-child(2)").animate({r: 50}, 1000);
  10. // Now let's create pattern var p = s.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ fill: "none", stroke: "#bada55", strokeWidth: 5 }); // To create a pattern, // just specify dimensions in the pattern method: p = p.pattern(0, 0, 10, 10); // Then use it as a fill on the big circle bigCircle.attr({ fill: p });
  11. // We can also grab a pattern from an SVG // already embedded into our page discs.attr({ fill: Snap("#pattern") });
  12. // Let's change the fill of the circles to gradient // This string means relative radial gradient // from white to black discs.attr({fill: "r()#fff-#000"}); // Note that we have two gradients, one for each circle
  13. // If we want them to share one gradient, // we need to use an absolute gradient with capital R discs.attr({fill: "R(150, 150, 100)#fff-#000"});
  14. // Of course we can animate color as well p.select("path").animate({stroke: "#f00"}, 1000);
  15. // Now let's load an external SVG file: Snap.load("mascot.svg", function (f) { // Note that we traverse and change attr before the SVG // is even added to the page f.select("polygon[fill='#09B39C']").attr({fill: "#bada55"}); g = f.select("g"); s.append(g); // Making croc draggable. Go ahead drag it around! g.drag(); // Obviously drag could take event handlers too // Looks like our croc is made from more than one polygon... });
  16. // Now let's load an external SVG file: Snap.load("mascot.svg", function (f) { // Note that we traverse and change attr before SVG // is even added to the page f.selectAll("polygon[fill='#09B39C']").attr({fill: "#bada55"}); g = f.select("g"); s.append(g); // Making croc draggable. Go ahead drag it around! g.drag(); // Obviously drag could take event handlers too // That’s better! selectAll for the rescue. });
  17. // Writing text is as simple as: s.text(200, 100, "Snap.svg");
  18. // Provide an array of strings (or arrays), to generate tspans var t = s.text(200, 120, ["Snap", ".", "svg"]); t.selectAll("tspan:nth-child(3)").attr({ fill: "#900", "font-size": "20px" });
================================================ FILE: demos/tutorial/tutorial.html ================================================ Snap.svg ================================================ FILE: dist/snap.svg-min.js ================================================ // Snap.svg 0.5.1 // // Copyright (c) 2013 – 2017 Adobe Systems Incorporated. All rights reserved. // // 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. // // build: 2017-02-07 !function(a){var b,c,d="0.5.0",e="hasOwnProperty",f=/[\.\/]/,g=/\s*,\s*/,h="*",i=function(a,b){return a-b},j={n:{}},k=function(){for(var a=0,b=this.length;b>a;a++)if("undefined"!=typeof this[a])return this[a]},l=function(){for(var a=this.length;--a;)if("undefined"!=typeof this[a])return this[a]},m=Object.prototype.toString,n=String,o=Array.isArray||function(a){return a instanceof Array||"[object Array]"==m.call(a)};eve=function(a,d){var e,f=c,g=Array.prototype.slice.call(arguments,2),h=eve.listeners(a),j=0,m=[],n={},o=[],p=b;o.firstDefined=k,o.lastDefined=l,b=a,c=0;for(var q=0,r=h.length;r>q;q++)"zIndex"in h[q]&&(m.push(h[q].zIndex),h[q].zIndex<0&&(n[h[q].zIndex]=h[q]));for(m.sort(i);m[j]<0;)if(e=n[m[j++]],o.push(e.apply(d,g)),c)return c=f,o;for(q=0;r>q;q++)if(e=h[q],"zIndex"in e)if(e.zIndex==m[j]){if(o.push(e.apply(d,g)),c)break;do if(j++,e=n[m[j]],e&&o.push(e.apply(d,g)),c)break;while(e)}else n[e.zIndex]=e;else if(o.push(e.apply(d,g)),c)break;return c=f,b=p,o},eve._events=j,eve.listeners=function(a){var b,c,d,e,g,i,k,l,m=o(a)?a:a.split(f),n=j,p=[n],q=[];for(e=0,g=m.length;g>e;e++){for(l=[],i=0,k=p.length;k>i;i++)for(n=p[i].n,c=[n[m[e]],n[h]],d=2;d--;)b=c[d],b&&(l.push(b),q=q.concat(b.f||[]));p=l}return q},eve.separator=function(a){a?(a=n(a).replace(/(?=[\.\^\]\[\-])/g,"\\"),a="["+a+"]",f=new RegExp(a)):f=/[\.\/]/},eve.on=function(a,b){if("function"!=typeof b)return function(){};for(var c=o(a)?o(a[0])?a:[a]:n(a).split(g),d=0,e=c.length;e>d;d++)!function(a){for(var c,d=o(a)?a:n(a).split(f),e=j,g=0,h=d.length;h>g;g++)e=e.n,e=e.hasOwnProperty(d[g])&&e[d[g]]||(e[d[g]]={n:{}});for(e.f=e.f||[],g=0,h=e.f.length;h>g;g++)if(e.f[g]==b){c=!0;break}!c&&e.f.push(b)}(c[d]);return function(a){+a==+a&&(b.zIndex=+a)}},eve.f=function(a){var b=[].slice.call(arguments,1);return function(){eve.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},eve.stop=function(){c=1},eve.nt=function(a){var c=o(b)?b.join("."):b;return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(c):c},eve.nts=function(){return o(b)?b:b.split(f)},eve.off=eve.unbind=function(a,b){if(!a)return void(eve._events=j={n:{}});var c=o(a)?o(a[0])?a:[a]:n(a).split(g);if(c.length>1)for(var d=0,i=c.length;i>d;d++)eve.off(c[d],b);else{c=o(a)?a:n(a).split(f);var k,l,m,d,i,p,q,r=[j],s=[];for(d=0,i=c.length;i>d;d++)for(p=0;pd;d++)for(k=r[d];k.n;){if(b){if(k.f){for(p=0,q=k.f.length;q>p;p++)if(k.f[p]==b){k.f.splice(p,1);break}!k.f.length&&delete k.f}for(l in k.n)if(k.n[e](l)&&k.n[l].f){var t=k.n[l].f;for(p=0,q=t.length;q>p;p++)if(t[p]==b){t.splice(p,1);break}!t.length&&delete k.n[l].f}}else{delete k.f;for(l in k.n)k.n[e](l)&&k.n[l].f&&delete k.n[l].f}k=k.n}a:for(d=0,i=s.length;i>d;d++){k=s[d];for(l in k.n[k.name].f)continue a;for(l in k.n[k.name].n)continue a;delete k.n[k.name]}}},eve.once=function(a,b){var c=function(){return eve.off(a,c),b.apply(this,arguments)};return eve.on(a,c)},eve.version=d,eve.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=eve:"function"==typeof define&&define.amd?define("eve",[],function(){return eve}):a.eve=eve}(this),function(a,b){if("function"==typeof define&&define.amd)define(["eve"],function(c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("eve");module.exports=b(a,c)}else b(a,a.eve)}(window||this,function(a,b){var c=function(b){var c,d={},e=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){return setTimeout(a,16,(new Date).getTime()),!0},f=Array.isArray||function(a){return a instanceof Array||"[object Array]"==Object.prototype.toString.call(a)},g=0,h="M"+(+new Date).toString(36),i=function(){return h+(g++).toString(36)},j=Date.now||function(){return+new Date},k=function(a){var b=this;if(null==a)return b.s;var c=b.s-a;b.b+=b.dur*c,b.B+=b.dur*c,b.s=a},l=function(a){var b=this;return null==a?b.spd:void(b.spd=a)},m=function(a){var b=this;return null==a?b.dur:(b.s=b.s*a/b.dur,void(b.dur=a))},n=function(){var a=this;delete d[a.id],a.update(),b("mina.stop."+a.id,a)},o=function(){var a=this;a.pdif||(delete d[a.id],a.update(),a.pdif=a.get()-a.b)},p=function(){var a=this;a.pdif&&(a.b=a.get()-a.pdif,delete a.pdif,d[a.id]=a,r())},q=function(){var a,b=this;if(f(b.start)){a=[];for(var c=0,d=b.start.length;d>c;c++)a[c]=+b.start[c]+(b.end[c]-b.start[c])*b.easing(b.s)}else a=+b.start+(b.end-b.start)*b.easing(b.s);b.set(a)},r=function(a){if(!a)return void(c||(c=e(r)));var f=0;for(var g in d)if(d.hasOwnProperty(g)){var h=d[g],i=h.get();f++,h.s=(i-h.b)/(h.dur/h.spd),h.s>=1&&(delete d[g],h.s=1,f--,function(a){setTimeout(function(){b("mina.finish."+a.id,a)})}(h)),h.update()}c=f?e(r):!1},s=function(a,b,c,e,f,g,h){var j={id:i(),start:a,end:b,b:c,s:0,dur:e-c,spd:1,get:f,set:g,easing:h||s.linear,status:k,speed:l,duration:m,stop:n,pause:o,resume:p,update:q};d[j.id]=j;var t,u=0;for(t in d)if(d.hasOwnProperty(t)&&(u++,2==u))break;return 1==u&&r(),j};return s.time=j,s.getById=function(a){return d[a]||null},s.linear=function(a){return a},s.easeout=function(a){return Math.pow(a,1.7)},s.easein=function(a){return Math.pow(a,.48)},s.easeinout=function(a){if(1==a)return 1;if(0==a)return 0;var b=.48-a/1.04,c=Math.sqrt(.1734+b*b),d=c-b,e=Math.pow(Math.abs(d),1/3)*(0>d?-1:1),f=-c-b,g=Math.pow(Math.abs(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},s.backin=function(a){if(1==a)return 1;var b=1.70158;return a*a*((b+1)*a-b)},s.backout=function(a){if(0==a)return 0;a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},s.elastic=function(a){return a==!!a?a:Math.pow(2,-10*a)*Math.sin((a-.075)*(2*Math.PI)/.3)+1},s.bounce=function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b},a.mina=s,s}("undefined"==typeof b?function(){}:b),d=function(a){function c(a,b){if(a){if(a.nodeType)return w(a);if(e(a,"array")&&c.set)return c.set.apply(c,a);if(a instanceof s)return a;if(null==b)try{return a=y.doc.querySelector(String(a)),w(a)}catch(d){return null}}return a=null==a?"100%":a,b=null==b?"100%":b,new v(a,b)}function d(a,b){if(b){if("#text"==a&&(a=y.doc.createTextNode(b.text||b["#text"]||"")),"#comment"==a&&(a=y.doc.createComment(b.text||b["#text"]||"")),"string"==typeof a&&(a=d(a)),"string"==typeof b)return 1==a.nodeType?"xlink:"==b.substring(0,6)?a.getAttributeNS(T,b.substring(6)):"xml:"==b.substring(0,4)?a.getAttributeNS(U,b.substring(4)):a.getAttribute(b):"text"==b?a.nodeValue:null;if(1==a.nodeType){for(var c in b)if(b[z](c)){var e=A(b[c]);e?"xlink:"==c.substring(0,6)?a.setAttributeNS(T,c.substring(6),e):"xml:"==c.substring(0,4)?a.setAttributeNS(U,c.substring(4),e):a.setAttribute(c,e):a.removeAttribute(c)}}else"text"in b&&(a.nodeValue=b.text)}else a=y.doc.createElementNS(U,a);return a}function e(a,b){return b=A.prototype.toLowerCase.call(b),"finite"==b?isFinite(a):"array"==b&&(a instanceof Array||Array.isArray&&Array.isArray(a))?!0:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||J.call(a).slice(8,-1).toLowerCase()==b}function f(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=f(a[c]));return b}function h(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function i(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),g=d.cache=d.cache||{},i=d.count=d.count||[];return g[z](f)?(h(i,f),c?c(g[f]):g[f]):(i.length>=1e3&&delete g[i.shift()],i.push(f),g[f]=a.apply(b,e),c?c(g[f]):g[f])}return d}function j(a,b,c,d,e,f){if(null==e){var g=a-c,h=b-d;return g||h?(180+180*D.atan2(-h,-g)/H+360)%360:0}return j(a,b,e,f)-j(c,d,e,f)}function k(a){return a%360*H/180}function l(a){return 180*a/H%360}function m(a){var b=[];return a=a.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g,function(a,c,d){return d=d.split(/\s*,\s*|\s+/),"rotate"==c&&1==d.length&&d.push(0,0),"scale"==c&&(d.length>2?d=d.slice(0,2):2==d.length&&d.push(0,0),1==d.length&&d.push(d[0],0,0)),"skewX"==c?b.push(["m",1,0,D.tan(k(d[0])),1,0,0]):"skewY"==c?b.push(["m",1,D.tan(k(d[0])),0,1,0,0]):b.push([c.charAt(0)].concat(d)),a}),b}function n(a,b){var d=aa(a),e=new c.Matrix;if(d)for(var f=0,g=d.length;g>f;f++){var h,i,j,k,l,m=d[f],n=m.length,o=A(m[0]).toLowerCase(),p=m[0]!=o,q=p?e.invert():0;"t"==o&&2==n?e.translate(m[1],0):"t"==o&&3==n?p?(h=q.x(0,0),i=q.y(0,0),j=q.x(m[1],m[2]),k=q.y(m[1],m[2]),e.translate(j-h,k-i)):e.translate(m[1],m[2]):"r"==o?2==n?(l=l||b,e.rotate(m[1],l.x+l.width/2,l.y+l.height/2)):4==n&&(p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.rotate(m[1],j,k)):e.rotate(m[1],m[2],m[3])):"s"==o?2==n||3==n?(l=l||b,e.scale(m[1],m[n-1],l.x+l.width/2,l.y+l.height/2)):4==n?p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.scale(m[1],m[1],j,k)):e.scale(m[1],m[1],m[2],m[3]):5==n&&(p?(j=q.x(m[3],m[4]),k=q.y(m[3],m[4]),e.scale(m[1],m[2],j,k)):e.scale(m[1],m[2],m[3],m[4])):"m"==o&&7==n&&e.add(m[1],m[2],m[3],m[4],m[5],m[6])}return e}function o(a){var b=a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||a.node.parentNode&&w(a.node.parentNode)||c.select("svg")||c(0,0),d=b.select("defs"),e=null==d?!1:d.node;return e||(e=u("defs",b.node).node),e}function p(a){return a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||c.select("svg")}function q(a,b,c){function e(a){if(null==a)return I;if(a==+a)return a;d(j,{width:a});try{return j.getBBox().width}catch(b){return 0}}function f(a){if(null==a)return I;if(a==+a)return a;d(j,{height:a});try{return j.getBBox().height}catch(b){return 0}}function g(d,e){null==b?i[d]=e(a.attr(d)||0):d==b&&(i=e(null==c?a.attr(d)||0:c))}var h=p(a).node,i={},j=h.querySelector(".svg---mgr");switch(j||(j=d("rect"),d(j,{x:-9e9,y:-9e9,width:10,height:10,"class":"svg---mgr",fill:"none"}),h.appendChild(j)),a.type){case"rect":g("rx",e),g("ry",f);case"image":g("width",e),g("height",f);case"text":g("x",e),g("y",f);break;case"circle":g("cx",e),g("cy",f),g("r",e);break;case"ellipse":g("cx",e),g("cy",f),g("rx",e),g("ry",f);break;case"line":g("x1",e),g("x2",e),g("y1",f),g("y2",f);break;case"marker":g("refX",e),g("markerWidth",e),g("refY",f),g("markerHeight",f);break;case"radialGradient":g("fx",e),g("fy",f);break;case"tspan":g("dx",e),g("dy",f);break;default:g(b,e)}return h.removeChild(j),i}function r(a){e(a,"array")||(a=Array.prototype.slice.call(arguments,0));for(var b=0,c=0,d=this.node;this[b];)delete this[b++];for(b=0;bc;c++){var e={type:a[c].type,attr:a[c].attr()},f=a[c].children();b.push(e),f.length&&x(f,e.childNodes=[])}}c.version="0.5.1",c.toString=function(){return"Snap v"+this.version},c._={};var y={win:a.window,doc:a.window.document};c._.glob=y;var z="hasOwnProperty",A=String,B=parseFloat,C=parseInt,D=Math,E=D.max,F=D.min,G=D.abs,H=(D.pow,D.PI),I=(D.round,""),J=Object.prototype.toString,K=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,L=(c._.separator=/[,\s]+/,/[\s]*,[\s]*/),M={hs:1,rg:1},N=/([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,O=/([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,P=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\s]*,?[\s]*/gi,Q=0,R="S"+(+new Date).toString(36),S=function(a){return(a&&a.type?a.type:I)+R+(Q++).toString(36)},T="http://www.w3.org/1999/xlink",U="http://www.w3.org/2000/svg",V={};c.url=function(a){return"url('#"+a+"')"};c._.$=d,c._.id=S,c.format=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return A(b).replace(a,function(a,b){return c(a,b,d)})}}(),c._.clone=f,c._.cacher=i,c.rad=k,c.deg=l,c.sin=function(a){return D.sin(c.rad(a))},c.tan=function(a){return D.tan(c.rad(a))},c.cos=function(a){return D.cos(c.rad(a))},c.asin=function(a){return c.deg(D.asin(a))},c.acos=function(a){return c.deg(D.acos(a))},c.atan=function(a){return c.deg(D.atan(a))},c.atan2=function(a){return c.deg(D.atan2(a))},c.angle=j,c.len=function(a,b,d,e){return Math.sqrt(c.len2(a,b,d,e))},c.len2=function(a,b,c,d){return(a-c)*(a-c)+(b-d)*(b-d)},c.closestPoint=function(a,b,c){function d(a){var d=a.x-b,e=a.y-c;return d*d+e*e}for(var e,f,g,h,i=a.node,j=i.getTotalLength(),k=j/i.pathSegList.numberOfItems*.125,l=1/0,m=0;j>=m;m+=k)(h=d(g=i.getPointAtLength(m))).5;){var n,o,p,q,r,s;(p=f-k)>=0&&(r=d(n=i.getPointAtLength(p)))f)return b-f;if(f>a-c)return b-f+a}return b},c.getRGB=i(function(a){if(!a||(a=A(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:Z};if(!(M[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=W(a)),!a)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};var b,d,f,g,h,i,j=a.match(K);return j?(j[2]&&(f=C(j[2].substring(5),16),d=C(j[2].substring(3,5),16),b=C(j[2].substring(1,3),16)),j[3]&&(f=C((h=j[3].charAt(3))+h,16),d=C((h=j[3].charAt(2))+h,16),b=C((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=B(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),f=B(i[2]),"%"==i[2].slice(-1)&&(f*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsb2rgb(b,d,f,g)):j[6]?(i=j[6].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsl2rgb(b,d,f,g)):(b=F(D.round(b),255),d=F(D.round(d),255),f=F(D.round(f),255),g=F(E(g,0),1),j={r:b,g:d,b:f,toString:Z},j.hex="#"+(16777216|f|d<<8|b<<16).toString(16).slice(1),j.opacity=e(g,"finite")?g:1,j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z}},c),c.hsb=i(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=i(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=i(function(a,b,c,d){if(e(d,"finite")){var f=D.round;return"rgba("+[f(a),f(b),f(c),+d.toFixed(2)]+")"}return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)});var W=function(a){var b=y.doc.getElementsByTagName("head")[0]||y.doc.getElementsByTagName("svg")[0],c="rgb(255, 0, 0)";return(W=i(function(a){if("red"==a.toLowerCase())return c;b.style.color=c,b.style.color=a;var d=y.doc.defaultView.getComputedStyle(b,I).getPropertyValue("color");return d==c?null:d}))(a)},X=function(){return"hsb("+[this.h,this.s,this.b]+")"},Y=function(){return"hsl("+[this.h,this.s,this.l]+")"},Z=function(){return 1==this.opacity||null==this.opacity?this.hex:"rgba("+[this.r,this.g,this.b,this.opacity]+")"},$=function(a,b,d){if(null==b&&e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&e(a,string)){var f=c.getRGB(a);a=f.r,b=f.g,d=f.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},_=function(a,b,d,f){a=D.round(255*a),b=D.round(255*b),d=D.round(255*d);var g={r:a,g:b,b:d,opacity:e(f,"finite")?f:1,hex:c.rgb(a,b,d),toString:Z};return e(f,"finite")&&(g.opacity=f),g};c.color=function(a){var b;return e(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):e(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):(e(a,"string")&&(a=c.getRGB(a)),e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&!("error"in a)?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1,a.error=1)),a.toString=Z,a},c.hsb2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var f,g,h,i,j;return a=a%360/60,j=c*b,i=j*(1-G(a%2-1)),f=g=h=c-j,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.hsl2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var f,g,h,i,j;return a=a%360/60,j=2*b*(.5>c?c:1-c),i=j*(1-G(a%2-1)),f=g=h=c-j/2,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.rgb2hsb=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=E(a,b,c),g=f-F(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:X}},c.rgb2hsl=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=E(a,b,c),h=F(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:Y}},c.parsePathString=function(a){if(!a)return null;var b=c.path(a);if(b.arr)return c.path.clone(b.arr);var d={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},f=[];return e(a,"array")&&e(a[0],"array")&&(f=c.path.clone(a)),f.length||A(a).replace(N,function(a,b,c){var e=[],g=b.toLowerCase();if(c.replace(P,function(a,b){b&&e.push(+b)}),"m"==g&&e.length>2&&(f.push([b].concat(e.splice(0,2))),g="l",b="m"==b?"l":"L"),"o"==g&&1==e.length&&f.push([b,e[0]]),"r"==g)f.push([b].concat(e));else for(;e.length>=d[g]&&(f.push([b].concat(e.splice(0,d[g]))),d[g]););}),f.toString=c.path.toString,b.arr=c.path.clone(f),f};var aa=c.parseTransformString=function(a){if(!a)return null;var b=[];return e(a,"array")&&e(a[0],"array")&&(b=c.path.clone(a)),b.length||A(a).replace(O,function(a,c,d){var e=[];c.toLowerCase();d.replace(P,function(a,b){b&&e.push(+b)}),b.push([c].concat(e))}),b.toString=c.path.toString,b};c._.svgTransform2string=m,c._.rgTransform=/^[a-z][\s]*-?\.?\d/i,c._.transform2matrix=n,c._unit2px=q;y.doc.contains||y.doc.compareDocumentPosition?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)for(;b;)if(b=b.parentNode,b==a)return!0;return!1};c._.getSomeDefs=o,c._.getSomeSVG=p,c.select=function(a){return a=A(a).replace(/([^\\]):/g,"$1\\:"),w(y.doc.querySelector(a))},c.selectAll=function(a){for(var b=y.doc.querySelectorAll(a),d=(c.set||Array)(),e=0;ei;i++)h[g[i].nodeName]=g[i].nodeValue;return h}if(e(a,"string")){if(!(arguments.length>1))return b("snap.util.getattr."+a,d).firstDefined();var k={};k[a]=c,a=k}for(var l in a)a[z](l)&&b("snap.util.attr."+l,d,a[l]);return d},c.parse=function(a){var b=y.doc.createDocumentFragment(),c=!0,d=y.doc.createElement("div");if(a=A(a),a.match(/^\s*<\s*svg(?:\s|>)/)||(a=""+a+"",c=!1),d.innerHTML=a,a=d.getElementsByTagName("svg")[0])if(c)b=a;else for(;a.firstChild;)b.appendChild(a.firstChild);return new t(b)},c.fragment=function(){for(var a=Array.prototype.slice.call(arguments,0),b=y.doc.createDocumentFragment(),d=0,e=a.length;e>d;d++){var f=a[d];f.node&&f.node.nodeType&&b.appendChild(f.node),f.nodeType&&b.appendChild(f),"string"==typeof f&&b.appendChild(c.parse(f).node)}return new t(b)},c._.make=u,c._.wrap=w,v.prototype.el=function(a,b){var c=u(a,this.node);return b&&c.attr(b),c},s.prototype.children=function(){for(var a=[],b=this.node.childNodes,d=0,e=b.length;e>d;d++)a[d]=c(b[d]);return a},s.prototype.toJSON=function(){var a=[];return x([this],a),a[0]},b.on("snap.util.getattr",function(){var a=b.nt();a=a.substring(a.lastIndexOf(".")+1);var c=a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});return ba[z](c)?this.node.ownerDocument.defaultView.getComputedStyle(this.node,null).getPropertyValue(c):d(this.node,a)});var ba={"alignment-baseline":0,"baseline-shift":0,clip:0,"clip-path":0,"clip-rule":0,color:0,"color-interpolation":0,"color-interpolation-filters":0,"color-profile":0,"color-rendering":0,cursor:0,direction:0,display:0,"dominant-baseline":0,"enable-background":0,fill:0,"fill-opacity":0,"fill-rule":0,filter:0,"flood-color":0,"flood-opacity":0,font:0,"font-family":0,"font-size":0,"font-size-adjust":0,"font-stretch":0,"font-style":0,"font-variant":0,"font-weight":0,"glyph-orientation-horizontal":0,"glyph-orientation-vertical":0,"image-rendering":0,kerning:0,"letter-spacing":0,"lighting-color":0,marker:0,"marker-end":0,"marker-mid":0,"marker-start":0,mask:0,opacity:0,overflow:0,"pointer-events":0,"shape-rendering":0,"stop-color":0,"stop-opacity":0,stroke:0,"stroke-dasharray":0,"stroke-dashoffset":0,"stroke-linecap":0,"stroke-linejoin":0,"stroke-miterlimit":0,"stroke-opacity":0,"stroke-width":0,"text-anchor":0,"text-decoration":0,"text-rendering":0,"unicode-bidi":0,visibility:0,"word-spacing":0,"writing-mode":0};b.on("snap.util.attr",function(a){var c=b.nt(),e={};c=c.substring(c.lastIndexOf(".")+1),e[c]=a;var f=c.replace(/-(\w)/gi,function(a,b){return b.toUpperCase()}),g=c.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});ba[z](g)?this.node.style[f]=null==a?I:a:d(this.node,e)}),function(a){}(v.prototype),c.ajax=function(a,c,d,f){var g=new XMLHttpRequest,h=S();if(g){if(e(c,"function"))f=d,d=c,c=null;else if(e(c,"object")){var i=[];for(var j in c)c.hasOwnProperty(j)&&i.push(encodeURIComponent(j)+"="+encodeURIComponent(c[j]));c=i.join("&")}return g.open(c?"POST":"GET",a,!0),c&&(g.setRequestHeader("X-Requested-With","XMLHttpRequest"),g.setRequestHeader("Content-type","application/x-www-form-urlencoded")),d&&(b.once("snap.ajax."+h+".0",d),b.once("snap.ajax."+h+".200",d),b.once("snap.ajax."+h+".304",d)),g.onreadystatechange=function(){4==g.readyState&&b("snap.ajax."+h+"."+g.status,f,g)},4==g.readyState?g:(g.send(c),g)}},c.load=function(a,b,d){c.ajax(a,function(a){var e=c.parse(a.responseText);d?b.call(d,e):b(e)})};var ca=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,h=e.clientLeft||d.clientLeft||0,i=b.top+(g.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(g.win.pageXOffset||e.scrollLeft||d.scrollLeft)-h;return{y:i,x:j}};return c.getElementByPoint=function(a,b){var c=this,d=(c.canvas,y.doc.elementFromPoint(a,b));if(y.win.opera&&"svg"==d.tagName){var e=ca(d),f=d.createSVGRect();f.x=a-e.x,f.y=b-e.y,f.width=f.height=1;var g=d.getIntersectionList(f,null);g.length&&(d=g[g.length-1])}return d?w(d):null},c.plugin=function(a){a(c,s,v,y,t)},y.win.Snap=c,c}(a||this);return d.plugin(function(c,d,e,f,g){function h(a,b){if(null==b){var d=!0;if(b="linearGradient"==a.type||"radialGradient"==a.type?a.node.getAttribute("gradientTransform"):"pattern"==a.type?a.node.getAttribute("patternTransform"):a.node.getAttribute("transform"),!b)return new c.Matrix;b=c._.svgTransform2string(b)}else b=c._.rgTransform.test(b)?m(b).replace(/\.{3}|\u2026/g,a._.transform||""):c._.svgTransform2string(b),l(b,"array")&&(b=c.path?c.path.toString.call(b):m(b)),a._.transform=b;var e=c._.transform2matrix(b,a.getBBox(1));return d?e:void(a.matrix=e)}function i(a){function b(a,b){var d=o(a.node,b);d=d&&d.match(g),d=d&&d[2],d&&"#"==d.charAt()&&(d=d.substring(1),d&&(i[d]=(i[d]||[]).concat(function(d){var e={};e[b]=c.url(d),o(a.node,e)})))}function d(a){var b=o(a.node,"xlink:href");b&&"#"==b.charAt()&&(b=b.substring(1),b&&(i[b]=(i[b]||[]).concat(function(b){a.attr("xlink:href","#"+b)})))}for(var e,f=a.selectAll("*"),g=/^\s*url\(("|'|)(.*)\1\)\s*$/,h=[],i={},j=0,k=f.length;k>j;j++){e=f[j],b(e,"fill"),b(e,"stroke"),b(e,"filter"),b(e,"mask"),b(e,"clip-path"),d(e);var l=o(e.node,"id");l&&(o(e.node,{id:e.id}),h.push({old:l,id:e.id}))}for(j=0,k=h.length;k>j;j++){var m=i[h[j].old];if(m)for(var n=0,p=m.length;p>n;n++)m[n](h[j].id)}}function j(a){return function(){var b=a?"<"+this.type:"",c=this.node.attributes,d=this.node.childNodes;if(a)for(var e=0,f=c.length;f>e;e++)b+=" "+c[e].name+'="'+c[e].value.replace(/"/g,'\\"')+'"';if(d.length){for(a&&(b+=">"),e=0,f=d.length;f>e;e++)3==d[e].nodeType?b+=d[e].nodeValue:1==d[e].nodeType&&(b+=s(d[e]).toString());a&&(b+="")}else a&&(b+="/>");return b}}var k=d.prototype,l=c.is,m=String,n=c._unit2px,o=c._.$,p=c._.make,q=c._.getSomeDefs,r="hasOwnProperty",s=c._.wrap;k.getBBox=function(a){if("tspan"==this.type)return c._.box(this.node.getClientRects().item(0));if(!c.Matrix||!c.path)return this.node.getBBox();var b=this,d=new c.Matrix;if(b.removed)return c._.box();for(;"use"==b.type;)if(a||(d=d.add(b.transform().localMatrix.translate(b.attr("x")||0,b.attr("y")||0))),b.original)b=b.original;else{var e=b.attr("xlink:href");b=b.original=b.node.ownerDocument.getElementById(e.substring(e.indexOf("#")+1))}var f=b._,g=c.path.get[b.type]||c.path.get.deflt;try{return a?(f.bboxwt=g?c.path.getBBox(b.realPath=g(b)):c._.box(b.node.getBBox()),c._.box(f.bboxwt)):(b.realPath=g(b),b.matrix=b.transform().localMatrix,f.bbox=c.path.getBBox(c.path.map(b.realPath,d.add(b.matrix))),c._.box(f.bbox))}catch(h){return c._.box()}};var t=function(){return this.string};k.transform=function(a){var b=this._;if(null==a){for(var d,e=this,f=new c.Matrix(this.node.getCTM()),g=h(this),i=[g],j=new c.Matrix,k=g.toTransformString(),l=m(g)==m(this.matrix)?m(b.transform):k;"svg"!=e.type&&(e=e.parent());)i.push(h(e));for(d=i.length;d--;)j.add(i[d]);return{string:l,globalMatrix:f,totalMatrix:j,localMatrix:g,diffMatrix:f.clone().add(g.invert()),global:f.toTransformString(),total:j.toTransformString(),local:k,toString:t}}return a instanceof c.Matrix?(this.matrix=a,this._.transform=a.toTransformString()):h(this,a),this.node&&("linearGradient"==this.type||"radialGradient"==this.type?o(this.node,{gradientTransform:this.matrix}):"pattern"==this.type?o(this.node,{patternTransform:this.matrix}):o(this.node,{transform:this.matrix})),this},k.parent=function(){return s(this.node.parentNode)},k.append=k.add=function(a){if(a){if("set"==a.type){var b=this;return a.forEach(function(a){b.add(a)}),this}a=s(a),this.node.appendChild(a.node),a.paper=this.paper}return this},k.appendTo=function(a){return a&&(a=s(a),a.append(this)),this},k.prepend=function(a){if(a){if("set"==a.type){var b,c=this;return a.forEach(function(a){b?b.after(a):c.prepend(a),b=a}),this}a=s(a);var d=a.parent();this.node.insertBefore(a.node,this.node.firstChild),this.add&&this.add(),a.paper=this.paper,this.parent()&&this.parent().add(),d&&d.add()}return this},k.prependTo=function(a){return a=s(a),a.prepend(this),this},k.before=function(a){if("set"==a.type){var b=this;return a.forEach(function(a){var c=a.parent();b.node.parentNode.insertBefore(a.node,b.node),c&&c.add()}),this.parent().add(),this}a=s(a);var c=a.parent();return this.node.parentNode.insertBefore(a.node,this.node),this.parent()&&this.parent().add(),c&&c.add(),a.paper=this.paper,this},k.after=function(a){a=s(a);var b=a.parent();return this.node.nextSibling?this.node.parentNode.insertBefore(a.node,this.node.nextSibling):this.node.parentNode.appendChild(a.node),this.parent()&&this.parent().add(),b&&b.add(),a.paper=this.paper,this},k.insertBefore=function(a){a=s(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},k.insertAfter=function(a){a=s(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node.nextSibling),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},k.remove=function(){var a=this.parent();return this.node.parentNode&&this.node.parentNode.removeChild(this.node),delete this.paper,this.removed=!0,a&&a.add(),this},k.select=function(a){return s(this.node.querySelector(a))},k.selectAll=function(a){for(var b=this.node.querySelectorAll(a),d=(c.set||Array)(),e=0;e{contents}',{x:+b.x.toFixed(3),y:+b.y.toFixed(3),width:+b.width.toFixed(3),height:+b.height.toFixed(3), contents:this.outerSVG()});return"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(d)))}},g.prototype.select=k.select,g.prototype.selectAll=k.selectAll}),d.plugin(function(a,d,e,f,g){function h(a,b,c){return function(d){var e=d.slice(a,b);return 1==e.length&&(e=e[0]),c?c(e):e}}var i=d.prototype,j=a.is,k=String,l="hasOwnProperty",m=function(a,b,d,e){"function"!=typeof d||d.length||(e=d,d=c.linear),this.attr=a,this.dur=b,d&&(this.easing=d),e&&(this.callback=e)};a._.Animation=m,a.animation=function(a,b,c,d){return new m(a,b,c,d)},i.inAnim=function(){var a=this,b=[];for(var c in a.anims)a.anims[l](c)&&!function(a){b.push({anim:new m(a._attrs,a.dur,a.easing,a._callback),mina:a,curStatus:a.status(),status:function(b){return a.status(b)},stop:function(){a.stop()}})}(a.anims[c]);return b},a.animate=function(a,d,e,f,g,h){"function"!=typeof g||g.length||(h=g,g=c.linear);var i=c.time(),j=c(a,d,i,i+f,c.time,e,g);return h&&b.once("mina.finish."+j.id,h),j},i.stop=function(){for(var a=this.inAnim(),b=0,c=a.length;c>b;b++)a[b].stop();return this},i.animate=function(a,d,e,f){"function"!=typeof e||e.length||(f=e,e=c.linear),a instanceof m&&(f=a.callback,e=a.easing,d=a.dur,a=a.attr);var g,i,n,o,p=[],q=[],r={},s=this;for(var t in a)if(a[l](t)){s.equal?(o=s.equal(t,k(a[t])),g=o.from,i=o.to,n=o.f):(g=+s.attr(t),i=+a[t]);var u=j(g,"array")?g.length:1;r[t]=h(p.length,p.length+u,n),p=p.concat(g),q=q.concat(i)}var v=c.time(),w=c(p,q,v,v+d,c.time,function(a){var b={};for(var c in r)r[l](c)&&(b[c]=r[c](a));s.attr(b)},e);return s.anims[w.id]=w,w._attrs=a,w._callback=f,b("snap.animcreated."+s.id,w),b.once("mina.finish."+w.id,function(){b.off("mina.*."+w.id),delete s.anims[w.id],f&&f.call(s)}),b.once("mina.stop."+w.id,function(){b.off("mina.*."+w.id),delete s.anims[w.id]}),s}}),d.plugin(function(a,b,c,d,e){function f(a,b,c,d,e,f){return null==b&&"[object SVGMatrix]"==g.call(a)?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,void(this.f=a.f)):void(null!=a?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0))}var g=Object.prototype.toString,h=String,i=Math,j="";!function(b){function c(a){return a[0]*a[0]+a[1]*a[1]}function d(a){var b=i.sqrt(c(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}b.add=function(a,b,c,d,e,g){if(a&&a instanceof f)return this.add(a.a,a.b,a.c,a.d,a.e,a.f);var h=a*this.a+b*this.c,i=a*this.b+b*this.d;return this.e+=e*this.a+g*this.c,this.f+=e*this.b+g*this.d,this.c=c*this.a+d*this.c,this.d=c*this.b+d*this.d,this.a=h,this.b=i,this},f.prototype.multLeft=function(a,b,c,d,e,g){if(a&&a instanceof f)return this.multLeft(a.a,a.b,a.c,a.d,a.e,a.f);var h=a*this.a+c*this.b,i=a*this.c+c*this.d,j=a*this.e+c*this.f+e;return this.b=b*this.a+d*this.b,this.d=b*this.c+d*this.d,this.f=b*this.e+d*this.f+g,this.a=h,this.c=i,this.e=j,this},b.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new f(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},b.clone=function(){return new f(this.a,this.b,this.c,this.d,this.e,this.f)},b.translate=function(a,b){return this.e+=a*this.a+b*this.c,this.f+=a*this.b+b*this.d,this},b.scale=function(a,b,c,d){return null==b&&(b=a),(c||d)&&this.translate(c,d),this.a*=a,this.b*=a,this.c*=b,this.d*=b,(c||d)&&this.translate(-c,-d),this},b.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var e=+i.cos(b).toFixed(9),f=+i.sin(b).toFixed(9);return this.add(e,f,-f,e,c,d),this.add(1,0,0,1,-c,-d)},b.skewX=function(a){return this.skew(a,0)},b.skewY=function(a){return this.skew(0,a)},b.skew=function(b,c){b=b||0,c=c||0,b=a.rad(b),c=a.rad(c);var d=i.tan(b).toFixed(9),e=i.tan(c).toFixed(9);return this.add(1,e,d,1,0,0)},b.x=function(a,b){return a*this.a+b*this.c+this.e},b.y=function(a,b){return a*this.b+b*this.d+this.f},b.get=function(a){return+this[h.fromCharCode(97+a)].toFixed(4)},b.toString=function(){return"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")"},b.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},b.determinant=function(){return this.a*this.d-this.b*this.c},b.split=function(){var b={};b.dx=this.e,b.dy=this.f;var e=[[this.a,this.b],[this.c,this.d]];b.scalex=i.sqrt(c(e[0])),d(e[0]),b.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*b.shear,e[1][1]-e[0][1]*b.shear],b.scaley=i.sqrt(c(e[1])),d(e[1]),b.shear/=b.scaley,this.determinant()<0&&(b.scalex=-b.scalex);var f=e[0][1],g=e[1][1];return 0>g?(b.rotate=a.deg(i.acos(g)),0>f&&(b.rotate=360-b.rotate)):b.rotate=a.deg(i.asin(f)),b.isSimple=!(+b.shear.toFixed(9)||b.scalex.toFixed(9)!=b.scaley.toFixed(9)&&b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate,b},b.toTransformString=function(a){var b=a||this.split();return+b.shear.toFixed(9)?"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]:(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[+b.dx.toFixed(4),+b.dy.toFixed(4)]:j)+(b.rotate?"r"+[+b.rotate.toFixed(4),0,0]:j)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:j))}}(f.prototype),a.Matrix=f,a.matrix=function(a,b,c,d,e,g){return new f(a,b,c,d,e,g)}}),d.plugin(function(a,c,d,e,f){function g(d){return function(e){if(b.stop(),e instanceof f&&1==e.node.childNodes.length&&("radialGradient"==e.node.firstChild.tagName||"linearGradient"==e.node.firstChild.tagName||"pattern"==e.node.firstChild.tagName)&&(e=e.node.firstChild,n(this).appendChild(e),e=l(e)),e instanceof c)if("radialGradient"==e.type||"linearGradient"==e.type||"pattern"==e.type){e.node.id||p(e.node,{id:e.id});var g=q(e.node.id)}else g=e.attr(d);else if(g=a.color(e),g.error){var h=a(n(this).ownerSVGElement).gradient(e);h?(h.node.id||p(h.node,{id:h.id}),g=q(h.node.id)):g=e}else g=r(g);var i={};i[d]=g,p(this.node,i),this.node.style[d]=t}}function h(a){b.stop(),a==+a&&(a+="px"),this.node.style.fontSize=a}function i(a){for(var b=[],c=a.childNodes,d=0,e=c.length;e>d;d++){var f=c[d];3==f.nodeType&&b.push(f.nodeValue),"tspan"==f.tagName&&(1==f.childNodes.length&&3==f.firstChild.nodeType?b.push(f.firstChild.nodeValue):b.push(i(f)))}return b}function j(){return b.stop(),this.node.style.fontSize}var k=a._.make,l=a._.wrap,m=a.is,n=a._.getSomeDefs,o=/^url\((['"]?)([^)]+)\1\)$/,p=a._.$,q=a.url,r=String,s=a._.separator,t="";a.deurl=function(a){var b=String(a).match(o);return b?b[2]:a},b.on("snap.util.attr.mask",function(a){if(a instanceof c||a instanceof f){if(b.stop(),a instanceof f&&1==a.node.childNodes.length&&(a=a.node.firstChild,n(this).appendChild(a),a=l(a)),"mask"==a.type)var d=a;else d=k("mask",n(this)),d.node.appendChild(a.node);!d.node.id&&p(d.node,{id:d.id}),p(this.node,{mask:q(d.id)})}}),function(a){b.on("snap.util.attr.clip",a),b.on("snap.util.attr.clip-path",a),b.on("snap.util.attr.clipPath",a)}(function(a){if(a instanceof c||a instanceof f){b.stop();for(var d,e=a.node;e;){if("clipPath"===e.nodeName){d=new c(e);break}if("svg"===e.nodeName){d=void 0;break}e=e.parentNode}d||(d=k("clipPath",n(this)),d.node.appendChild(a.node),!d.node.id&&p(d.node,{id:d.id})),p(this.node,{"clip-path":q(d.node.id||d.id)})}}),b.on("snap.util.attr.fill",g("fill")),b.on("snap.util.attr.stroke",g("stroke"));var u=/^([lr])(?:\(([^)]*)\))?(.*)$/i;b.on("snap.util.grad.parse",function(a){function b(a,b){for(var c=(b-h)/(a-i),d=i;a>d;d++)f[d].offset=+(+h+c*(d-i)).toFixed(2);i=a,h=b}a=r(a);var c=a.match(u);if(!c)return null;var d=c[1],e=c[2],f=c[3];e=e.split(/\s*,\s*/).map(function(a){return+a==a?+a:a}),1==e.length&&0==e[0]&&(e=[]),f=f.split("-"),f=f.map(function(a){a=a.split(":");var b={color:a[0]};return a[1]&&(b.offset=parseFloat(a[1])),b});var g=f.length,h=0,i=0;g--;for(var j=0;g>j;j++)"offset"in f[j]&&b(j,f[j].offset);return f[g].offset=f[g].offset||100,b(g,f[g].offset),{type:d,params:e,stops:f}}),b.on("snap.util.attr.d",function(c){b.stop(),m(c,"array")&&m(c[0],"array")&&(c=a.path.toString.call(c)),c=r(c),c.match(/[ruo]/i)&&(c=a.path.toAbsolute(c)),p(this.node,{d:c})})(-1),b.on("snap.util.attr.#text",function(a){b.stop(),a=r(a);for(var c=e.doc.createTextNode(a);this.node.firstChild;)this.node.removeChild(this.node.firstChild);this.node.appendChild(c)})(-1),b.on("snap.util.attr.path",function(a){b.stop(),this.attr({d:a})})(-1),b.on("snap.util.attr.class",function(a){b.stop(),this.node.className.baseVal=a})(-1),b.on("snap.util.attr.viewBox",function(a){var c;c=m(a,"object")&&"x"in a?[a.x,a.y,a.width,a.height].join(" "):m(a,"array")?a.join(" "):a,p(this.node,{viewBox:c}),b.stop()})(-1),b.on("snap.util.attr.transform",function(a){this.transform(a),b.stop()})(-1),b.on("snap.util.attr.r",function(a){"rect"==this.type&&(b.stop(),p(this.node,{rx:a,ry:a}))})(-1),b.on("snap.util.attr.textpath",function(a){if(b.stop(),"text"==this.type){var d,e,f;if(!a&&this.textPath){for(e=this.textPath;e.node.firstChild;)this.node.appendChild(e.node.firstChild);return e.remove(),void delete this.textPath}if(m(a,"string")){var g=n(this),h=l(g.parentNode).path(a);g.appendChild(h.node),d=h.id,h.attr({id:d})}else a=l(a),a instanceof c&&(d=a.attr("id"),d||(d=a.id,a.attr({id:d})));if(d)if(e=this.textPath,f=this.node,e)e.attr({"xlink:href":"#"+d});else{for(e=p("textPath",{"xlink:href":"#"+d});f.firstChild;)e.appendChild(f.firstChild);f.appendChild(e),this.textPath=l(e)}}})(-1),b.on("snap.util.attr.text",function(a){if("text"==this.type){for(var c=this.node,d=function(a){var b=p("tspan");if(m(a,"array"))for(var c=0;c1&&(a=Array.prototype.slice.call(arguments,0));var b={};return i(a,"object")&&!i(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polyline",b)},h.polygon=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return i(a,"object")&&!i(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polygon",b)},function(){function d(){return this.selectAll("stop")}function e(a,b){var d=l("stop"),e={offset:+b+"%"};a=c.color(a),e["stop-color"]=a.hex,a.opacity<1&&(e["stop-opacity"]=a.opacity),l(d,e);for(var f,g=this.stops(),h=0;hb){this.node.insertBefore(d,g[h].node),f=!0;break}}return f||this.node.appendChild(d),this}function f(){if("linearGradient"==this.type){var a=l(this.node,"x1")||0,b=l(this.node,"x2")||1,d=l(this.node,"y1")||0,e=l(this.node,"y2")||0;return c._.box(a,d,math.abs(b-a),math.abs(e-d))}var f=this.node.cx||.5,g=this.node.cy||.5,h=this.node.r||0;return c._.box(f-h,g-h,2*h,2*h)}function g(a){var d=a,e=this.stops();if("string"==typeof a&&(d=b("snap.util.grad.parse",null,"l(0,0,0,1)"+a).firstDefined().stops),c.is(d,"array")){for(var f=0;fh;h++){var i=f[h];d.addStop(i.color,i.offset)}return d}function j(a,b,h,i,j){var k=c._.make("linearGradient",a);return k.stops=d,k.addStop=e,k.getBBox=f,k.setStops=g,null!=b&&l(k.node,{x1:b,y1:h,x2:i,y2:j}),k}function k(a,b,g,h,i,j){var k=c._.make("radialGradient",a);return k.stops=d,k.addStop=e,k.getBBox=f,null!=b&&l(k.node,{cx:b,cy:g,r:h}),null!=i&&null!=j&&l(k.node,{fx:i,fy:j}),k}var l=c._.$;h.gradient=function(a){return i(this.defs,a)},h.gradientLinear=function(a,b,c,d){return j(this.defs,a,b,c,d)},h.gradientRadial=function(a,b,c,d,e){return k(this.defs,a,b,c,d,e)},h.toString=function(){var a,b=this.node.ownerDocument,d=b.createDocumentFragment(),e=b.createElement("div"),f=this.node.cloneNode(!0);return d.appendChild(e),e.appendChild(f),c._.$(f,{xmlns:"http://www.w3.org/2000/svg"}),a=e.innerHTML,d.removeChild(d.firstChild),a},h.toDataURL=function(){return a&&a.btoa?"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(this))):void 0},h.clear=function(){for(var a,b=this.node.firstChild;b;)a=b.nextSibling,"defs"!=b.tagName?b.parentNode.removeChild(b):h.clear.call({node:b}),b=a}}()}),d.plugin(function(a,b,c,d){function e(a){var b=e.ps=e.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[M](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]}function f(a,b,c,d){return null==a&&(a=b=c=d=0),null==b&&(b=a.y,c=a.width,d=a.height,a=a.x),{x:a,y:b,width:c,w:c,height:d,h:d,x2:a+c,y2:b+d,cx:a+c/2,cy:b+d/2,r1:P.min(c,d)/2,r2:P.max(c,d)/2,r0:P.sqrt(c*c+d*d)/2,path:y(a,b,c,d),vb:[a,b,c,d].join(" ")}}function g(){return this.join(",").replace(N,"$1")}function h(a){var b=L(a);return b.toString=g,b}function i(a,b,c,d,e,f,g,h,i){return null==i?p(a,b,c,d,e,f,g,h):k(a,b,c,d,e,f,g,h,q(a,b,c,d,e,f,g,h,i))}function j(c,d){function e(a){return+(+a).toFixed(3)}return a._.cacher(function(a,f,g){a instanceof b&&(a=a.attr("d")),a=G(a);for(var h,j,l,m,n,o="",p={},q=0,r=0,s=a.length;s>r;r++){if(l=a[r],"M"==l[0])h=+l[1],j=+l[2];else{if(m=i(h,j,l[1],l[2],l[3],l[4],l[5],l[6]),q+m>f){if(d&&!p.start){if(n=i(h,j,l[1],l[2],l[3],l[4],l[5],l[6],f-q),o+=["C"+e(n.start.x),e(n.start.y),e(n.m.x),e(n.m.y),e(n.x),e(n.y)],g)return o;p.start=o,o=["M"+e(n.x),e(n.y)+"C"+e(n.n.x),e(n.n.y),e(n.end.x),e(n.end.y),e(l[5]),e(l[6])].join(),q+=m,h=+l[5],j=+l[6];continue}if(!c&&!d)return n=i(h,j,l[1],l[2],l[3],l[4],l[5],l[6],f-q)}q+=m,h=+l[5],j=+l[6]}o+=l.shift()+l}return p.end=o,n=c?q:d?p:k(h,j,l[0],l[1],l[2],l[3],l[4],l[5],1)},null,a._.clone)}function k(a,b,c,d,e,f,g,h,i){var j=1-i,k=T(j,3),l=T(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*P.atan2(q-s,r-t)/Q;return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}}function l(b,c,d,e,g,h,i,j){a.is(b,"array")||(b=[b,c,d,e,g,h,i,j]);var k=F.apply(null,b);return f(k.min.x,k.min.y,k.max.x-k.min.x,k.max.y-k.min.y)}function m(a,b,c){return b>=a.x&&b<=a.x+a.width&&c>=a.y&&c<=a.y+a.height}function n(a,b){return a=f(a),b=f(b),m(b,a.x,a.y)||m(b,a.x2,a.y)||m(b,a.x,a.y2)||m(b,a.x2,a.y2)||m(a,b.x,b.y)||m(a,b.x2,b.y)||m(a,b.x,b.y2)||m(a,b.x2,b.y2)||(a.xb.x||b.xa.x)&&(a.yb.y||b.ya.y)}function o(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function p(a,b,c,d,e,f,g,h,i){null==i&&(i=1),i=i>1?1:0>i?0:i;for(var j=i/2,k=12,l=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0,p=0;k>p;p++){var q=j*l[p]+j,r=o(q,a,c,e,g),s=o(q,b,d,f,h),t=r*r+s*s;n+=m[p]*P.sqrt(t)}return j*n}function q(a,b,c,d,e,f,g,h,i){if(!(0>i||p(a,b,c,d,e,f,g,h)n;)l/=2,m+=(i>j?1:-1)*l,j=p(a,b,c,d,e,f,g,h,m);return m}}function r(a,b,c,d,e,f,g,h){if(!(S(a,c)S(e,g)||S(b,d)S(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+R(a,c).toFixed(2)||n>+S(a,c).toFixed(2)||n<+R(e,g).toFixed(2)||n>+S(e,g).toFixed(2)||o<+R(b,d).toFixed(2)||o>+S(b,d).toFixed(2)||o<+R(f,h).toFixed(2)||o>+S(f,h).toFixed(2)))return{x:l,y:m}}}}function s(a,b,c){var d=l(a),e=l(b);if(!n(d,e))return c?0:[];for(var f=p.apply(0,a),g=p.apply(0,b),h=~~(f/8),i=~~(g/8),j=[],m=[],o={},q=c?0:[],s=0;h+1>s;s++){var t=k.apply(0,a.concat(s/h));j.push({x:t.x,y:t.y,t:s/h})}for(s=0;i+1>s;s++)t=k.apply(0,b.concat(s/i)),m.push({x:t.x,y:t.y,t:s/i});for(s=0;h>s;s++)for(var u=0;i>u;u++){var v=j[s],w=j[s+1],x=m[u],y=m[u+1],z=U(w.x-v.x)<.001?"y":"x",A=U(y.x-x.x)<.001?"y":"x",B=r(v.x,v.y,w.x,w.y,x.x,x.y,y.x,y.y);if(B){if(o[B.x.toFixed(4)]==B.y.toFixed(4))continue;o[B.x.toFixed(4)]=B.y.toFixed(4);var C=v.t+U((B[z]-v[z])/(w[z]-v[z]))*(w.t-v.t),D=x.t+U((B[A]-x[A])/(y[A]-x[A]))*(y.t-x.t);C>=0&&1>=C&&D>=0&&1>=D&&(c?q++:q.push({x:B.x,y:B.y,t1:C,t2:D}))}}return q}function t(a,b){return v(a,b)}function u(a,b){return v(a,b,1)}function v(a,b,c){a=G(a),b=G(b);for(var d,e,f,g,h,i,j,k,l,m,n=c?0:[],o=0,p=a.length;p>o;o++){var q=a[o];if("M"==q[0])d=h=q[1],e=i=q[2];else{"C"==q[0]?(l=[d,e].concat(q.slice(1)),d=l[6],e=l[7]):(l=[d,e,d,e,h,i,h,i],d=h,e=i);for(var r=0,t=b.length;t>r;r++){var u=b[r];if("M"==u[0])f=j=u[1],g=k=u[2];else{"C"==u[0]?(m=[f,g].concat(u.slice(1)),f=m[6],g=m[7]):(m=[f,g,f,g,j,k,j,k],f=j,g=k);var v=s(l,m,c);if(c)n+=v;else{for(var w=0,x=v.length;x>w;w++)v[w].segment1=o,v[w].segment2=r,v[w].bez1=l,v[w].bez2=m;n=n.concat(v)}}}}}return n}function w(a,b,c){var d=x(a);return m(d,b,c)&&v(a,[["M",b,c],["H",d.x2+10]],1)%2==1}function x(a){var b=e(a);if(b.bbox)return L(b.bbox);if(!a)return f();a=G(a);for(var c,d=0,g=0,h=[],i=[],j=0,k=a.length;k>j;j++)if(c=a[j],"M"==c[0])d=c[1],g=c[2],h.push(d),i.push(g);else{var l=F(d,g,c[1],c[2],c[3],c[4],c[5],c[6]);h=h.concat(l.min.x,l.max.x),i=i.concat(l.min.y,l.max.y),d=c[5],g=c[6]}var m=R.apply(0,h),n=R.apply(0,i),o=S.apply(0,h),p=S.apply(0,i),q=f(m,n,o-m,p-n);return b.bbox=L(q),q}function y(a,b,c,d,e){if(e)return[["M",+a+ +e,b],["l",c-2*e,0],["a",e,e,0,0,1,e,e],["l",0,d-2*e],["a",e,e,0,0,1,-e,e],["l",2*e-c,0],["a",e,e,0,0,1,-e,-e],["l",0,2*e-d],["a",e,e,0,0,1,e,-e],["z"]];var f=[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]];return f.toString=g,f}function z(a,b,c,d,e){if(null==e&&null==d&&(d=c),a=+a,b=+b,c=+c,d=+d,null!=e)var f=Math.PI/180,h=a+c*Math.cos(-d*f),i=a+c*Math.cos(-e*f),j=b+c*Math.sin(-d*f),k=b+c*Math.sin(-e*f),l=[["M",h,j],["A",c,c,0,+(e-d>180),0,i,k]];else l=[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]];return l.toString=g,l}function A(b){var c=e(b),d=String.prototype.toLowerCase;if(c.rel)return h(c.rel);a.is(b,"array")&&a.is(b&&b[0],"array")||(b=a.parsePathString(b));var f=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=b[0][1],j=b[0][2],k=i,l=j,m++,f.push(["M",i,j]));for(var n=m,o=b.length;o>n;n++){var p=f[n]=[],q=b[n];if(q[0]!=d.call(q[0]))switch(p[0]=d.call(q[0]),p[0]){case"a":p[1]=q[1],p[2]=q[2],p[3]=q[3],p[4]=q[4],p[5]=q[5],p[6]=+(q[6]-i).toFixed(3),p[7]=+(q[7]-j).toFixed(3);break;case"v":p[1]=+(q[1]-j).toFixed(3);break;case"m":k=q[1],l=q[2];default:for(var r=1,s=q.length;s>r;r++)p[r]=+(q[r]-(r%2?i:j)).toFixed(3)}else{p=f[n]=[],"m"==q[0]&&(k=q[1]+i,l=q[2]+j);for(var t=0,u=q.length;u>t;t++)f[n][t]=q[t]}var v=f[n].length;switch(f[n][0]){case"z":i=k,j=l;break;case"h":i+=+f[n][v-1];break;case"v":j+=+f[n][v-1];break;default:i+=+f[n][v-2],j+=+f[n][v-1]}}return f.toString=g,c.rel=h(f),f}function B(b){var c=e(b);if(c.abs)return h(c.abs);if(K(b,"array")&&K(b&&b[0],"array")||(b=a.parsePathString(b)),!b||!b.length)return[["M",0,0]];var d,f=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=+b[0][1],j=+b[0][2],k=i,l=j,m++,f[0]=["M",i,j]);for(var n,o,p=3==b.length&&"M"==b[0][0]&&"R"==b[1][0].toUpperCase()&&"Z"==b[2][0].toUpperCase(),q=m,r=b.length;r>q;q++){if(f.push(n=[]),o=b[q],d=o[0],d!=d.toUpperCase())switch(n[0]=d.toUpperCase(),n[0]){case"A":n[1]=o[1],n[2]=o[2],n[3]=o[3],n[4]=o[4],n[5]=o[5],n[6]=+o[6]+i,n[7]=+o[7]+j;break;case"V":n[1]=+o[1]+j;break;case"H":n[1]=+o[1]+i;break;case"R":for(var s=[i,j].concat(o.slice(1)),t=2,u=s.length;u>t;t++)s[t]=+s[t]+i,s[++t]=+s[t]+j;f.pop(),f=f.concat(I(s,p));break;case"O":f.pop(),s=z(i,j,o[1],o[2]),s.push(s[0]),f=f.concat(s);break;case"U":f.pop(),f=f.concat(z(i,j,o[1],o[2],o[3])),n=["U"].concat(f[f.length-1].slice(-2));break;case"M":k=+o[1]+i,l=+o[2]+j;default:for(t=1,u=o.length;u>t;t++)n[t]=+o[t]+(t%2?i:j)}else if("R"==d)s=[i,j].concat(o.slice(1)),f.pop(),f=f.concat(I(s,p)),n=["R"].concat(o.slice(-2));else if("O"==d)f.pop(),s=z(i,j,o[1],o[2]),s.push(s[0]),f=f.concat(s);else if("U"==d)f.pop(),f=f.concat(z(i,j,o[1],o[2],o[3])),n=["U"].concat(f[f.length-1].slice(-2));else for(var v=0,w=o.length;w>v;v++)n[v]=o[v];if(d=d.toUpperCase(),"O"!=d)switch(n[0]){case"Z":i=+k,j=+l;break;case"H":i=n[1];break;case"V":j=n[1];break;case"M":k=n[n.length-2],l=n[n.length-1];default:i=n[n.length-2],j=n[n.length-1]}}return f.toString=g,c.abs=h(f),f}function C(a,b,c,d){return[a,b,c,d,c,d]}function D(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]}function E(b,c,d,e,f,g,h,i,j,k){var l,m=120*Q/180,n=Q/180*(+f||0),o=[],p=a._.cacher(function(a,b,c){var d=a*P.cos(c)-b*P.sin(c),e=a*P.sin(c)+b*P.cos(c);return{x:d,y:e}});if(!d||!e)return[b,c,i,j,i,j];if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(b,c,-n),b=l.x,c=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(P.cos(Q/180*f),P.sin(Q/180*f),(b-i)/2),r=(c-j)/2,s=q*q/(d*d)+r*r/(e*e);s>1&&(s=P.sqrt(s),d=s*d,e=s*e);var t=d*d,u=e*e,v=(g==h?-1:1)*P.sqrt(U((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*d*r/e+(b+i)/2,x=v*-e*q/d+(c+j)/2,y=P.asin(((c-x)/e).toFixed(9)),z=P.asin(((j-x)/e).toFixed(9));y=w>b?Q-y:y,z=w>i?Q-z:z,0>y&&(y=2*Q+y),0>z&&(z=2*Q+z),h&&y>z&&(y-=2*Q),!h&&z>y&&(z-=2*Q)}var A=z-y;if(U(A)>m){var B=z,C=i,D=j;z=y+m*(h&&z>y?1:-1),i=w+d*P.cos(z),j=x+e*P.sin(z),o=E(i,j,d,e,f,0,h,C,D,[z,B,w,x])}A=z-y;var F=P.cos(y),G=P.sin(y),H=P.cos(z),I=P.sin(z),J=P.tan(A/4),K=4/3*d*J,L=4/3*e*J,M=[b,c],N=[b+K*G,c-L*F],O=[i+K*I,j-L*H],R=[i,j];if(N[0]=2*M[0]-N[0],N[1]=2*M[1]-N[1],k)return[N,O,R].concat(o);o=[N,O,R].concat(o).join().split(",");for(var S=[],T=0,V=o.length;V>T;T++)S[T]=T%2?p(o[T-1],o[T],n).y:p(o[T],o[T+1],n).x;return S}function F(a,b,c,d,e,f,g,h){for(var i,j,k,l,m,n,o,p,q=[],r=[[],[]],s=0;2>s;++s)if(0==s?(j=6*a-12*c+6*e,i=-3*a+9*c-9*e+3*g,k=3*c-3*a):(j=6*b-12*d+6*f,i=-3*b+9*d-9*f+3*h,k=3*d-3*b),U(i)<1e-12){if(U(j)<1e-12)continue;l=-k/j,l>0&&1>l&&q.push(l)}else o=j*j-4*k*i,p=P.sqrt(o),0>o||(m=(-j+p)/(2*i),m>0&&1>m&&q.push(m),n=(-j-p)/(2*i),n>0&&1>n&&q.push(n));for(var t,u=q.length,v=u;u--;)l=q[u],t=1-l,r[0][u]=t*t*t*a+3*t*t*l*c+3*t*l*l*e+l*l*l*g,r[1][u]=t*t*t*b+3*t*t*l*d+3*t*l*l*f+l*l*l*h;return r[0][v]=a,r[1][v]=b,r[0][v+1]=g,r[1][v+1]=h,r[0].length=r[1].length=v+2,{min:{x:R.apply(0,r[0]),y:R.apply(0,r[1])},max:{x:S.apply(0,r[0]),y:S.apply(0,r[1])}}}function G(a,b){var c=!b&&e(a);if(!b&&c.curve)return h(c.curve);for(var d=B(a),f=b&&B(b),g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},i={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},j=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"].concat(E.apply(0,[b.x,b.y].concat(a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e].concat(a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"].concat(D(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"].concat(D(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"].concat(C(b.x,b.y,a[1],a[2]));break;case"H":a=["C"].concat(C(b.x,b.y,a[1],b.y));break;case"V":a=["C"].concat(C(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"].concat(C(b.x,b.y,b.X,b.Y))}return a}),k=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)m[b]="A",f&&(n[b]="A"),a.splice(b++,0,["C"].concat(c.splice(0,6)));a.splice(b,1),r=S(d.length,f&&f.length||0)}},l=function(a,b,c,e,g){a&&b&&"M"==a[g][0]&&"M"!=b[g][0]&&(b.splice(g,0,["M",e.x,e.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],r=S(d.length,f&&f.length||0))},m=[],n=[],o="",p="",q=0,r=S(d.length,f&&f.length||0);r>q;q++){d[q]&&(o=d[q][0]),"C"!=o&&(m[q]=o,q&&(p=m[q-1])),d[q]=j(d[q],g,p),"A"!=m[q]&&"C"==o&&(m[q]="C"),k(d,q),f&&(f[q]&&(o=f[q][0]),"C"!=o&&(n[q]=o,q&&(p=n[q-1])),f[q]=j(f[q],i,p),"A"!=n[q]&&"C"==o&&(n[q]="C"),k(f,q)),l(d,f,g,i,q),l(f,d,i,g,q);var s=d[q],t=f&&f[q],u=s.length,v=f&&t.length;g.x=s[u-2],g.y=s[u-1],g.bx=O(s[u-4])||g.x,g.by=O(s[u-3])||g.y,i.bx=f&&(O(t[v-4])||i.x),i.by=f&&(O(t[v-3])||i.y),i.x=f&&t[v-2],i.y=f&&t[v-1]}return f||(c.curve=h(d)),f?[d,f]:d}function H(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=G(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a}function I(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}var J=b.prototype,K=a.is,L=a._.clone,M="hasOwnProperty",N=/,?([a-z]),?/gi,O=parseFloat,P=Math,Q=P.PI,R=P.min,S=P.max,T=P.pow,U=P.abs,V=j(1),W=j(),X=j(0,1),Y=a._unit2px,Z={path:function(a){return a.attr("path")},circle:function(a){var b=Y(a);return z(b.cx,b.cy,b.r)},ellipse:function(a){var b=Y(a); return z(b.cx||0,b.cy||0,b.rx,b.ry)},rect:function(a){var b=Y(a);return y(b.x||0,b.y||0,b.width,b.height,b.rx,b.ry)},image:function(a){var b=Y(a);return y(b.x||0,b.y||0,b.width,b.height)},line:function(a){return"M"+[a.attr("x1")||0,a.attr("y1")||0,a.attr("x2"),a.attr("y2")]},polyline:function(a){return"M"+a.attr("points")},polygon:function(a){return"M"+a.attr("points")+"z"},deflt:function(a){var b=a.node.getBBox();return y(b.x,b.y,b.width,b.height)}};a.path=e,a.path.getTotalLength=V,a.path.getPointAtLength=W,a.path.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return X(a,b).end;var d=X(a,c,1);return b?X(d,b).end:d},J.getTotalLength=function(){return this.node.getTotalLength?this.node.getTotalLength():void 0},J.getPointAtLength=function(a){return W(this.attr("d"),a)},J.getSubpath=function(b,c){return a.path.getSubpath(this.attr("d"),b,c)},a._.box=f,a.path.findDotsAtSegment=k,a.path.bezierBBox=l,a.path.isPointInsideBBox=m,a.closest=function(b,c,d,e){for(var g=100,h=f(b-g/2,c-g/2,g,g),i=[],j=d[0].hasOwnProperty("x")?function(a){return{x:d[a].x,y:d[a].y}}:function(a){return{x:d[a],y:e[a]}},k=0;1e6>=g&&!k;){for(var l=0,n=d.length;n>l;l++){var o=j(l);if(m(h,o.x,o.y)){k++,i.push(o);break}}k||(g*=2,h=f(b-g/2,c-g/2,g,g))}if(1e6!=g){var p,q=1/0;for(l=0,n=i.length;n>l;l++){var r=a.len(b,c,i[l].x,i[l].y);q>r&&(q=r,i[l].len=r,p=i[l])}return p}},a.path.isBBoxIntersect=n,a.path.intersection=t,a.path.intersectionNumber=u,a.path.isPointInside=w,a.path.getBBox=x,a.path.get=Z,a.path.toRelative=A,a.path.toAbsolute=B,a.path.toCubic=G,a.path.map=H,a.path.toString=g,a.path.clone=h}),d.plugin(function(a,d,e,f){var g=Math.max,h=Math.min,i=function(a){if(this.items=[],this.bindings={},this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)a[b]&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},j=i.prototype;j.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],a&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},j.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},j.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this},j.animate=function(d,e,f,g){"function"!=typeof f||f.length||(g=f,f=c.linear),d instanceof a._.Animation&&(g=d.callback,f=d.easing,e=f.dur,d=d.attr);var h=arguments;if(a.is(d,"array")&&a.is(h[h.length-1],"array"))var i=!0;var j,k=function(){j?this.b=j:j=this.b},l=0,m=this,n=g&&function(){++l==m.length&&g.call(this)};return this.forEach(function(a,c){b.once("snap.animcreated."+a.id,k),i?h[c]&&a.animate.apply(a,h[c]):a.animate(d,e,f,n)})},j.remove=function(){for(;this.length;)this.pop().remove();return this},j.bind=function(a,b,c){var d={};if("function"==typeof b)this.bindings[a]=b;else{var e=c||a;this.bindings[a]=function(a){d[e]=a,b.attr(d)}}return this},j.attr=function(a){var b={};for(var c in a)this.bindings[c]?this.bindings[c](a[c]):b[c]=a[c];for(var d=0,e=this.items.length;e>d;d++)this.items[d].attr(b);return this},j.clear=function(){for(;this.length;)this.pop()},j.splice=function(a,b,c){a=0>a?g(this.length+a,0):a,b=g(0,h(this.length-a,b));var d,e=[],f=[],j=[];for(d=2;dd;d++)f.push(this[a+d]);for(;dd?j[d]:e[d-k];for(d=this.items.length=this.length-=b-k;this[d];)delete this[d++];return new i(f)},j.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0;return!1},j.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},j.getBBox=function(){for(var a=[],b=[],c=[],d=[],e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}return a=h.apply(0,a),b=h.apply(0,b),c=g.apply(0,c),d=g.apply(0,d),{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b,cx:a+(c-a)/2,cy:b+(d-b)/2}},j.clone=function(a){a=new i;for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},j.toString=function(){return"Snap‘s set"},j.type="set",a.Set=i,a.set=function(){var a=new i;return arguments.length&&a.push.apply(a,Array.prototype.slice.call(arguments,0)),a}}),d.plugin(function(a,c,d,e){function f(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}}function g(b,c,d){b=b||new a.Matrix,c=c||new a.Matrix,b=a.parseTransformString(b.toTransformString())||[],c=a.parseTransformString(c.toTransformString())||[];for(var e,g,h,i,j=Math.max(b.length,c.length),k=[],n=[],o=0;j>o;o++){if(h=b[o]||f(c[o]),i=c[o]||f(h),h[0]!=i[0]||"r"==h[0].toLowerCase()&&(h[2]!=i[2]||h[3]!=i[3])||"s"==h[0].toLowerCase()&&(h[3]!=i[3]||h[4]!=i[4])){b=a._.transform2matrix(b,d()),c=a._.transform2matrix(c,d()),k=[["m",b.a,b.b,b.c,b.d,b.e,b.f]],n=[["m",c.a,c.b,c.c,c.d,c.e,c.f]];break}for(k[o]=[],n[o]=[],e=0,g=Math.max(h.length,i.length);g>e;e++)e in h&&(k[o][e]=h[e]),e in i&&(n[o][e]=i[e])}return{from:m(k),to:m(n),f:l(k)}}function h(a){return a}function i(a){return function(b){return+b.toFixed(3)+a}}function j(a){return a.join(" ")}function k(b){return a.rgb(b[0],b[1],b[2],b[3])}function l(a){var b,c,d,e,f,g,h=0,i=[];for(b=0,c=a.length;c>b;b++){for(f="[",g=['"'+a[b][0]+'"'],d=1,e=a[b].length;e>d;d++)g[d]="val["+h++ +"]";f+=g+"]",i[b]=f}return Function("val","return Snap.path.toString.call(["+i+"])")}function m(a){for(var b=[],c=0,d=a.length;d>c;c++)for(var e=1,f=a[c].length;f>e;e++)b.push(a[c][e]);return b}function n(a){return isFinite(a)}function o(b,c){return a.is(b,"array")&&a.is(c,"array")?b.toString()==c.toString():!1}var p={},q=/[%a-z]+$/i,r=String;p.stroke=p.fill="colour",c.prototype.equal=function(a,c){return b("snap.util.equal",this,a,c).firstDefined()},b.on("snap.util.equal",function(b,c){var d,e,f=r(this.attr(b)||""),s=this;if("colour"==p[b])return d=a.color(f),e=a.color(c),{from:[d.r,d.g,d.b,d.opacity],to:[e.r,e.g,e.b,e.opacity],f:k};if("viewBox"==b)return d=this.attr(b).vb.split(" ").map(Number),e=c.split(" ").map(Number),{from:d,to:e,f:j};if("transform"==b||"gradientTransform"==b||"patternTransform"==b)return"string"==typeof c&&(c=r(c).replace(/\.{3}|\u2026/g,f)),f=this.matrix,c=a._.rgTransform.test(c)?a._.transform2matrix(c,this.getBBox()):a._.transform2matrix(a._.svgTransform2string(c),this.getBBox()),g(f,c,function(){return s.getBBox(1)});if("d"==b||"path"==b)return d=a.path.toCubic(f,c),{from:m(d[0]),to:m(d[1]),f:l(d[0])};if("points"==b)return d=r(f).split(a._.separator),e=r(c).split(a._.separator),{from:d,to:e,f:function(a){return a}};if(n(f)&&n(c))return{from:parseFloat(f),to:parseFloat(c),f:h};var t=f.match(q),u=r(c).match(q);return t&&o(t,u)?{from:parseFloat(f),to:parseFloat(c),f:i(t)}:{from:this.asPX(b),to:this.asPX(b,c),f:h}})}),d.plugin(function(a,c,d,e){for(var f=c.prototype,g="hasOwnProperty",h=("createTouch"in e.doc),i=["click","dblclick","mousedown","mousemove","mouseout","mouseover","mouseup","touchstart","touchmove","touchend","touchcancel"],j={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},k=(function(a,b){var c="y"==a?"scrollTop":"scrollLeft",d=b&&b.node?b.node.ownerDocument:e.doc;return d[c in d.documentElement?"documentElement":"body"][c]}),l=function(){return this.originalEvent.preventDefault()},m=function(){return this.originalEvent.stopPropagation()},n=function(a,b,c,d){var e=h&&j[b]?j[b]:b,f=function(e){var f=k("y",d),i=k("x",d);if(h&&j[g](b))for(var n=0,o=e.targetTouches&&e.targetTouches.length;o>n;n++)if(e.targetTouches[n].target==a||a.contains(e.targetTouches[n].target)){var p=e;e=e.targetTouches[n],e.originalEvent=p,e.preventDefault=l,e.stopPropagation=m;break}var q=e.clientX+i,r=e.clientY+f;return c.call(d,e,q,r)};return b!==e&&a.addEventListener(b,f,!1),a.addEventListener(e,f,!1),function(){return b!==e&&a.removeEventListener(b,f,!1),a.removeEventListener(e,f,!1),!0}},o=[],p=function(a){for(var c,d=a.clientX,e=a.clientY,f=k("y"),g=k("x"),i=o.length;i--;){if(c=o[i],h){for(var j,l=a.touches&&a.touches.length;l--;)if(j=a.touches[l],j.identifier==c.el._drag.id||c.el.node.contains(j.target)){d=j.clientX,e=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();var m=c.el.node;m.nextSibling,m.parentNode,m.style.display;d+=g,e+=f,b("snap.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},q=function(c){a.unmousemove(p).unmouseup(q);for(var d,e=o.length;e--;)d=o[e],d.el._drag={},b("snap.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c),b.off("snap.drag.*."+d.el.id);o=[]},r=i.length;r--;)!function(b){a[b]=f[b]=function(c,d){if(a.is(c,"function"))this.events=this.events||[],this.events.push({name:b,f:c,unbind:n(this.node||document,b,c,d||this)});else for(var e=0,f=this.events.length;f>e;e++)if(this.events[e].name==b)try{this.events[e].f.call(this)}catch(g){}return this},a["un"+b]=f["un"+b]=function(a){for(var c=this.events||[],d=c.length;d--;)if(c[d].name==b&&(c[d].f==a||!a))return c[d].unbind(),c.splice(d,1),!c.length&&delete this.events,this;return this}}(i[r]);f.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},f.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var s=[];f.drag=function(c,d,e,f,g,h){function i(i,j,l){(i.originalEvent||i).preventDefault(),k._drag.x=j,k._drag.y=l,k._drag.id=i.identifier,!o.length&&a.mousemove(p).mouseup(q),o.push({el:k,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("snap.drag.start."+k.id,d),c&&b.on("snap.drag.move."+k.id,c),e&&b.on("snap.drag.end."+k.id,e),b("snap.drag.start."+k.id,g||f||k,j,l,i)}function j(a,c,d){b("snap.draginit."+k.id,k,a,c,d)}var k=this;if(!arguments.length){var l;return k.drag(function(a,b){this.attr({transform:l+(l?"T":"t")+[a,b]})},function(){l=this.transform().local})}return b.on("snap.draginit."+k.id,i),k._drag={},s.push({el:k,start:i,init:j}),k.mousedown(j),k},f.undrag=function(){for(var c=s.length;c--;)s[c].el==this&&(this.unmousedown(s[c].init),s.splice(c,1),b.unbind("snap.drag.*."+this.id),b.unbind("snap.draginit."+this.id));return!s.length&&a.unmousemove(p).unmouseup(q),this}}),d.plugin(function(a,c,d,e){var f=(c.prototype,d.prototype),g=/^\s*url\((.+)\)/,h=String,i=a._.$;a.filter={},f.filter=function(b){var d=this;"svg"!=d.type&&(d=d.paper);var e=a.parse(h(b)),f=a._.id(),g=(d.node.offsetWidth,d.node.offsetHeight,i("filter"));return i(g,{id:f,filterUnits:"userSpaceOnUse"}),g.appendChild(e.node),d.defs.appendChild(g),new c(g)},b.on("snap.util.getattr.filter",function(){b.stop();var c=i(this.node,"filter");if(c){var d=h(c).match(g);return d&&a.select(d[1])}}),b.on("snap.util.attr.filter",function(d){if(d instanceof c&&"filter"==d.type){b.stop();var e=d.node.id;e||(i(d.node,{id:d.id}),e=d.id),i(this.node,{filter:a.url(e)})}d&&"none"!=d||(b.stop(),this.node.removeAttribute("filter"))}),a.filter.blur=function(b,c){null==b&&(b=2);var d=null==c?b:[b,c];return a.format('',{def:d})},a.filter.blur.toString=function(){return this()},a.filter.shadow=function(b,c,d,e,f){return null==f&&(null==e?(f=d,d=4,e="#000"):(f=e,e=d,d=4)),null==d&&(d=4),null==f&&(f=1),null==b&&(b=0,c=2),null==c&&(c=b),e=a.color(e),a.format('',{color:e,dx:b,dy:c,blur:d,opacity:f})},a.filter.shadow.toString=function(){return this()},a.filter.grayscale=function(b){return null==b&&(b=1),a.format('',{a:.2126+.7874*(1-b),b:.7152-.7152*(1-b),c:.0722-.0722*(1-b),d:.2126-.2126*(1-b),e:.7152+.2848*(1-b),f:.0722-.0722*(1-b),g:.2126-.2126*(1-b),h:.0722+.9278*(1-b)})},a.filter.grayscale.toString=function(){return this()},a.filter.sepia=function(b){return null==b&&(b=1),a.format('',{a:.393+.607*(1-b),b:.769-.769*(1-b),c:.189-.189*(1-b),d:.349-.349*(1-b),e:.686+.314*(1-b),f:.168-.168*(1-b),g:.272-.272*(1-b),h:.534-.534*(1-b),i:.131+.869*(1-b)})},a.filter.sepia.toString=function(){return this()},a.filter.saturate=function(b){return null==b&&(b=1),a.format('',{amount:1-b})},a.filter.saturate.toString=function(){return this()},a.filter.hueRotate=function(b){return b=b||0,a.format('',{angle:b})},a.filter.hueRotate.toString=function(){return this()},a.filter.invert=function(b){return null==b&&(b=1),a.format('',{amount:b,amount2:1-b})},a.filter.invert.toString=function(){return this()},a.filter.brightness=function(b){return null==b&&(b=1),a.format('',{amount:b})},a.filter.brightness.toString=function(){return this()},a.filter.contrast=function(b){return null==b&&(b=1),a.format('',{amount:b,amount2:.5-b/2})},a.filter.contrast.toString=function(){return this()}}),d.plugin(function(a,b,c,d,e){var f=a._.box,g=a.is,h=/^[^a-z]*([tbmlrc])/i,i=function(){return"T"+this.dx+","+this.dy};b.prototype.getAlign=function(a,b){null==b&&g(a,"string")&&(b=a,a=null),a=a||this.paper;var c=a.getBBox?a.getBBox():f(a),d=this.getBBox(),e={};switch(b=b&&b.match(h),b=b?b[1].toLowerCase():"c"){case"t":e.dx=0,e.dy=c.y-d.y;break;case"b":e.dx=0,e.dy=c.y2-d.y2;break;case"m":e.dx=0,e.dy=c.cy-d.cy;break;case"l":e.dx=c.x-d.x,e.dy=0;break;case"r":e.dx=c.x2-d.x2,e.dy=0;break;default:e.dx=c.cx-d.cx,e.dy=0}return e.toString=i,e},b.prototype.align=function(a,b){return this.transform("..."+this.getAlign(a,b))}}),d.plugin(function(b,c,d,e){function f(a){a=a.split(/(?=#)/);var b=new String(a[5]);return b[50]=a[0],b[100]=a[1],b[200]=a[2],b[300]=a[3],b[400]=a[4],b[500]=a[5],b[600]=a[6],b[700]=a[7],b[800]=a[8],b[900]=a[9],a[10]&&(b.A100=a[10],b.A200=a[11],b.A400=a[12],b.A700=a[13]),b}var g="#ffebee#ffcdd2#ef9a9a#e57373#ef5350#f44336#e53935#d32f2f#c62828#b71c1c#ff8a80#ff5252#ff1744#d50000",h="#FCE4EC#F8BBD0#F48FB1#F06292#EC407A#E91E63#D81B60#C2185B#AD1457#880E4F#FF80AB#FF4081#F50057#C51162",i="#F3E5F5#E1BEE7#CE93D8#BA68C8#AB47BC#9C27B0#8E24AA#7B1FA2#6A1B9A#4A148C#EA80FC#E040FB#D500F9#AA00FF",j="#EDE7F6#D1C4E9#B39DDB#9575CD#7E57C2#673AB7#5E35B1#512DA8#4527A0#311B92#B388FF#7C4DFF#651FFF#6200EA",k="#E8EAF6#C5CAE9#9FA8DA#7986CB#5C6BC0#3F51B5#3949AB#303F9F#283593#1A237E#8C9EFF#536DFE#3D5AFE#304FFE",l="#E3F2FD#BBDEFB#90CAF9#64B5F6#64B5F6#2196F3#1E88E5#1976D2#1565C0#0D47A1#82B1FF#448AFF#2979FF#2962FF",m="#E1F5FE#B3E5FC#81D4FA#4FC3F7#29B6F6#03A9F4#039BE5#0288D1#0277BD#01579B#80D8FF#40C4FF#00B0FF#0091EA",n="#E0F7FA#B2EBF2#80DEEA#4DD0E1#26C6DA#00BCD4#00ACC1#0097A7#00838F#006064#84FFFF#18FFFF#00E5FF#00B8D4",o="#E0F2F1#B2DFDB#80CBC4#4DB6AC#26A69A#009688#00897B#00796B#00695C#004D40#A7FFEB#64FFDA#1DE9B6#00BFA5",p="#E8F5E9#C8E6C9#A5D6A7#81C784#66BB6A#4CAF50#43A047#388E3C#2E7D32#1B5E20#B9F6CA#69F0AE#00E676#00C853",q="#F1F8E9#DCEDC8#C5E1A5#AED581#9CCC65#8BC34A#7CB342#689F38#558B2F#33691E#CCFF90#B2FF59#76FF03#64DD17",r="#F9FBE7#F0F4C3#E6EE9C#DCE775#D4E157#CDDC39#C0CA33#AFB42B#9E9D24#827717#F4FF81#EEFF41#C6FF00#AEEA00",s="#FFFDE7#FFF9C4#FFF59D#FFF176#FFEE58#FFEB3B#FDD835#FBC02D#F9A825#F57F17#FFFF8D#FFFF00#FFEA00#FFD600",t="#FFF8E1#FFECB3#FFE082#FFD54F#FFCA28#FFC107#FFB300#FFA000#FF8F00#FF6F00#FFE57F#FFD740#FFC400#FFAB00",u="#FFF3E0#FFE0B2#FFCC80#FFB74D#FFA726#FF9800#FB8C00#F57C00#EF6C00#E65100#FFD180#FFAB40#FF9100#FF6D00",v="#FBE9E7#FFCCBC#FFAB91#FF8A65#FF7043#FF5722#F4511E#E64A19#D84315#BF360C#FF9E80#FF6E40#FF3D00#DD2C00",w="#EFEBE9#D7CCC8#BCAAA4#A1887F#8D6E63#795548#6D4C41#5D4037#4E342E#3E2723",x="#FAFAFA#F5F5F5#EEEEEE#E0E0E0#BDBDBD#9E9E9E#757575#616161#424242#212121",y="#ECEFF1#CFD8DC#B0BEC5#90A4AE#78909C#607D8B#546E7A#455A64#37474F#263238";b.mui={},b.flat={},b.mui.red=f(g),b.mui.pink=f(h),b.mui.purple=f(i),b.mui.deeppurple=f(j),b.mui.indigo=f(k),b.mui.blue=f(l),b.mui.lightblue=f(m),b.mui.cyan=f(n),b.mui.teal=f(o),b.mui.green=f(p),b.mui.lightgreen=f(q),b.mui.lime=f(r),b.mui.yellow=f(s),b.mui.amber=f(t),b.mui.orange=f(u),b.mui.deeporange=f(v),b.mui.brown=f(w),b.mui.grey=f(x),b.mui.bluegrey=f(y),b.flat.turquoise="#1abc9c",b.flat.greensea="#16a085",b.flat.sunflower="#f1c40f",b.flat.orange="#f39c12",b.flat.emerland="#2ecc71",b.flat.nephritis="#27ae60",b.flat.carrot="#e67e22",b.flat.pumpkin="#d35400",b.flat.peterriver="#3498db",b.flat.belizehole="#2980b9",b.flat.alizarin="#e74c3c",b.flat.pomegranate="#c0392b",b.flat.amethyst="#9b59b6",b.flat.wisteria="#8e44ad",b.flat.clouds="#ecf0f1",b.flat.silver="#bdc3c7",b.flat.wetasphalt="#34495e",b.flat.midnightblue="#2c3e50",b.flat.concrete="#95a5a6",b.flat.asbestos="#7f8c8d",b.importMUIColors=function(){for(var c in b.mui)b.mui.hasOwnProperty(c)&&(a[c]=b.mui[c])}}),d}); ================================================ FILE: dist/snap.svg.js ================================================ // Snap.svg 0.5.1 // // Copyright (c) 2013 – 2017 Adobe Systems Incorporated. All rights reserved. // // 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. // // build: 2017-02-07 // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. // ┌────────────────────────────────────────────────────────────┐ \\ // │ Eve 0.5.0 - JavaScript Events Library │ \\ // ├────────────────────────────────────────────────────────────┤ \\ // │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\ // └────────────────────────────────────────────────────────────┘ \\ (function (glob) { var version = "0.5.0", has = "hasOwnProperty", separator = /[\.\/]/, comaseparator = /\s*,\s*/, wildcard = "*", fun = function () {}, numsort = function (a, b) { return a - b; }, current_event, stop, events = {n: {}}, firstDefined = function () { for (var i = 0, ii = this.length; i < ii; i++) { if (typeof this[i] != "undefined") { return this[i]; } } }, lastDefined = function () { var i = this.length; while (--i) { if (typeof this[i] != "undefined") { return this[i]; } } }, objtos = Object.prototype.toString, Str = String, isArray = Array.isArray || function (ar) { return ar instanceof Array || objtos.call(ar) == "[object Array]"; }; /*\ * eve [ method ] * Fires event with given `name`, given scope and other parameters. > Arguments - name (string) name of the *event*, dot (`.`) or slash (`/`) separated - scope (object) context for the event handlers - varargs (...) the rest of arguments will be sent to event handlers = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value. \*/ eve = function (name, scope) { var e = events, oldstop = stop, args = Array.prototype.slice.call(arguments, 2), listeners = eve.listeners(name), z = 0, f = false, l, indexed = [], queue = {}, out = [], ce = current_event, errors = []; out.firstDefined = firstDefined; out.lastDefined = lastDefined; current_event = name; stop = 0; for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) { indexed.push(listeners[i].zIndex); if (listeners[i].zIndex < 0) { queue[listeners[i].zIndex] = listeners[i]; } } indexed.sort(numsort); while (indexed[z] < 0) { l = queue[indexed[z++]]; out.push(l.apply(scope, args)); if (stop) { stop = oldstop; return out; } } for (i = 0; i < ii; i++) { l = listeners[i]; if ("zIndex" in l) { if (l.zIndex == indexed[z]) { out.push(l.apply(scope, args)); if (stop) { break; } do { z++; l = queue[indexed[z]]; l && out.push(l.apply(scope, args)); if (stop) { break; } } while (l) } else { queue[l.zIndex] = l; } } else { out.push(l.apply(scope, args)); if (stop) { break; } } } stop = oldstop; current_event = ce; return out; }; // Undocumented. Debug only. eve._events = events; /*\ * eve.listeners [ method ] * Internal method which gives you array of all event handlers that will be triggered by the given `name`. > Arguments - name (string) name of the event, dot (`.`) or slash (`/`) separated = (array) array of event handlers \*/ eve.listeners = function (name) { var names = isArray(name) ? name : name.split(separator), e = events, item, items, k, i, ii, j, jj, nes, es = [e], out = []; for (i = 0, ii = names.length; i < ii; i++) { nes = []; for (j = 0, jj = es.length; j < jj; j++) { e = es[j].n; items = [e[names[i]], e[wildcard]]; k = 2; while (k--) { item = items[k]; if (item) { nes.push(item); out = out.concat(item.f || []); } } } es = nes; } return out; }; /*\ * eve.separator [ method ] * If for some reasons you don’t like default separators (`.` or `/`) you can specify yours * here. Be aware that if you pass a string longer than one character it will be treated as * a list of characters. - separator (string) new separator. Empty string resets to default: `.` or `/`. \*/ eve.separator = function (sep) { if (sep) { sep = Str(sep).replace(/(?=[\.\^\]\[\-])/g, "\\"); sep = "[" + sep + "]"; separator = new RegExp(sep); } else { separator = /[\.\/]/; } }; /*\ * eve.on [ method ] ** * Binds given event handler with a given name. You can use wildcards “`*`” for the names: | eve.on("*.under.*", f); | eve("mouse.under.floor"); // triggers f * Use @eve to trigger the listener. ** - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - f (function) event handler function ** - name (array) if you don’t want to use separators, you can use array of strings - f (function) event handler function ** = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. > Example: | eve.on("mouse", eatIt)(2); | eve.on("mouse", scream); | eve.on("mouse", catchIt)(1); * This will ensure that `catchIt` function will be called before `eatIt`. * * If you want to put your handler before non-indexed handlers, specify a negative value. * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”. \*/ eve.on = function (name, f) { if (typeof f != "function") { return function () {}; } var names = isArray(name) ? (isArray(name[0]) ? name : [name]) : Str(name).split(comaseparator); for (var i = 0, ii = names.length; i < ii; i++) { (function (name) { var names = isArray(name) ? name : Str(name).split(separator), e = events, exist; for (var i = 0, ii = names.length; i < ii; i++) { e = e.n; e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}}); } e.f = e.f || []; for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) { exist = true; break; } !exist && e.f.push(f); }(names[i])); } return function (zIndex) { if (+zIndex == +zIndex) { f.zIndex = +zIndex; } }; }; /*\ * eve.f [ method ] ** * Returns function that will fire given event with optional arguments. * Arguments that will be passed to the result function will be also * concated to the list of final arguments. | el.onclick = eve.f("click", 1, 2); | eve.on("click", function (a, b, c) { | console.log(a, b, c); // 1, 2, [event object] | }); > Arguments - event (string) event name - varargs (…) and any other arguments = (function) possible event handler function \*/ eve.f = function (event) { var attrs = [].slice.call(arguments, 1); return function () { eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0))); }; }; /*\ * eve.stop [ method ] ** * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing. \*/ eve.stop = function () { stop = 1; }; /*\ * eve.nt [ method ] ** * Could be used inside event handler to figure out actual name of the event. ** > Arguments ** - subname (string) #optional subname of the event ** = (string) name of the event, if `subname` is not specified * or = (boolean) `true`, if current event’s name contains `subname` \*/ eve.nt = function (subname) { var cur = isArray(current_event) ? current_event.join(".") : current_event; if (subname) { return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(cur); } return cur; }; /*\ * eve.nts [ method ] ** * Could be used inside event handler to figure out actual name of the event. ** ** = (array) names of the event \*/ eve.nts = function () { return isArray(current_event) ? current_event : current_event.split(separator); }; /*\ * eve.off [ method ] ** * Removes given function from the list of event listeners assigned to given name. * If no arguments specified all the events will be cleared. ** > Arguments ** - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - f (function) event handler function \*/ /*\ * eve.unbind [ method ] ** * See @eve.off \*/ eve.off = eve.unbind = function (name, f) { if (!name) { eve._events = events = {n: {}}; return; } var names = isArray(name) ? (isArray(name[0]) ? name : [name]) : Str(name).split(comaseparator); if (names.length > 1) { for (var i = 0, ii = names.length; i < ii; i++) { eve.off(names[i], f); } return; } names = isArray(name) ? name : Str(name).split(separator); var e, key, splice, i, ii, j, jj, cur = [events], inodes = []; for (i = 0, ii = names.length; i < ii; i++) { for (j = 0; j < cur.length; j += splice.length - 2) { splice = [j, 1]; e = cur[j].n; if (names[i] != wildcard) { if (e[names[i]]) { splice.push(e[names[i]]); inodes.unshift({ n: e, name: names[i] }); } } else { for (key in e) if (e[has](key)) { splice.push(e[key]); inodes.unshift({ n: e, name: key }); } } cur.splice.apply(cur, splice); } } for (i = 0, ii = cur.length; i < ii; i++) { e = cur[i]; while (e.n) { if (f) { if (e.f) { for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) { e.f.splice(j, 1); break; } !e.f.length && delete e.f; } for (key in e.n) if (e.n[has](key) && e.n[key].f) { var funcs = e.n[key].f; for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) { funcs.splice(j, 1); break; } !funcs.length && delete e.n[key].f; } } else { delete e.f; for (key in e.n) if (e.n[has](key) && e.n[key].f) { delete e.n[key].f; } } e = e.n; } } // prune inner nodes in path prune: for (i = 0, ii = inodes.length; i < ii; i++) { e = inodes[i]; for (key in e.n[e.name].f) { // not empty (has listeners) continue prune; } for (key in e.n[e.name].n) { // not empty (has children) continue prune; } // is empty delete e.n[e.name]; } }; /*\ * eve.once [ method ] ** * Binds given event handler with a given name to only run once then unbind itself. | eve.once("login", f); | eve("login"); // triggers f | eve("login"); // no listeners * Use @eve to trigger the listener. ** > Arguments ** - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - f (function) event handler function ** = (function) same return function as @eve.on \*/ eve.once = function (name, f) { var f2 = function () { eve.off(name, f2); return f.apply(this, arguments); }; return eve.on(name, f2); }; /*\ * eve.version [ property (string) ] ** * Current version of the library. \*/ eve.version = version; eve.toString = function () { return "You are running Eve " + version; }; (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve)); })(this); (function (glob, factory) { // AMD support if (typeof define == "function" && define.amd) { // Define as an anonymous module define(["eve"], function (eve) { return factory(glob, eve); }); } else if (typeof exports != "undefined") { // Next for Node.js or CommonJS var eve = require("eve"); module.exports = factory(glob, eve); } else { // Browser globals (glob is window) // Snap adds itself to window factory(glob, glob.eve); } }(window || this, function (window, eve) { // Copyright (c) 2017 Adobe Systems Incorporated. All rights reserved. // // 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. var mina = (function (eve) { var animations = {}, requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { setTimeout(callback, 16, new Date().getTime()); return true; }, requestID, isArray = Array.isArray || function (a) { return a instanceof Array || Object.prototype.toString.call(a) == "[object Array]"; }, idgen = 0, idprefix = "M" + (+new Date).toString(36), ID = function () { return idprefix + (idgen++).toString(36); }, diff = function (a, b, A, B) { if (isArray(a)) { res = []; for (var i = 0, ii = a.length; i < ii; i++) { res[i] = diff(a[i], b, A[i], B); } return res; } var dif = (A - a) / (B - b); return function (bb) { return a + dif * (bb - b); }; }, timer = Date.now || function () { return +new Date; }, sta = function (val) { var a = this; if (val == null) { return a.s; } var ds = a.s - val; a.b += a.dur * ds; a.B += a.dur * ds; a.s = val; }, speed = function (val) { var a = this; if (val == null) { return a.spd; } a.spd = val; }, duration = function (val) { var a = this; if (val == null) { return a.dur; } a.s = a.s * val / a.dur; a.dur = val; }, stopit = function () { var a = this; delete animations[a.id]; a.update(); eve("mina.stop." + a.id, a); }, pause = function () { var a = this; if (a.pdif) { return; } delete animations[a.id]; a.update(); a.pdif = a.get() - a.b; }, resume = function () { var a = this; if (!a.pdif) { return; } a.b = a.get() - a.pdif; delete a.pdif; animations[a.id] = a; frame(); }, update = function () { var a = this, res; if (isArray(a.start)) { res = []; for (var j = 0, jj = a.start.length; j < jj; j++) { res[j] = +a.start[j] + (a.end[j] - a.start[j]) * a.easing(a.s); } } else { res = +a.start + (a.end - a.start) * a.easing(a.s); } a.set(res); }, frame = function (timeStamp) { // Manual invokation? if (!timeStamp) { // Frame loop stopped? if (!requestID) { // Start frame loop... requestID = requestAnimFrame(frame); } return; } var len = 0; for (var i in animations) if (animations.hasOwnProperty(i)) { var a = animations[i], b = a.get(), res; len++; a.s = (b - a.b) / (a.dur / a.spd); if (a.s >= 1) { delete animations[i]; a.s = 1; len--; (function (a) { setTimeout(function () { eve("mina.finish." + a.id, a); }); }(a)); } a.update(); } requestID = len ? requestAnimFrame(frame) : false; }, /*\ * mina [ method ] ** * Generic animation of numbers ** - a (number) start _slave_ number - A (number) end _slave_ number - b (number) start _master_ number (start time in general case) - B (number) end _master_ number (end time in general case) - get (function) getter of _master_ number (see @mina.time) - set (function) setter of _slave_ number - easing (function) #optional easing function, default is @mina.linear = (object) animation descriptor o { o id (string) animation id, o start (number) start _slave_ number, o end (number) end _slave_ number, o b (number) start _master_ number, o s (number) animation status (0..1), o dur (number) animation duration, o spd (number) animation speed, o get (function) getter of _master_ number (see @mina.time), o set (function) setter of _slave_ number, o easing (function) easing function, default is @mina.linear, o status (function) status getter/setter, o speed (function) speed getter/setter, o duration (function) duration getter/setter, o stop (function) animation stopper o pause (function) pauses the animation o resume (function) resumes the animation o update (function) calles setter with the right value of the animation o } \*/ mina = function (a, A, b, B, get, set, easing) { var anim = { id: ID(), start: a, end: A, b: b, s: 0, dur: B - b, spd: 1, get: get, set: set, easing: easing || mina.linear, status: sta, speed: speed, duration: duration, stop: stopit, pause: pause, resume: resume, update: update }; animations[anim.id] = anim; var len = 0, i; for (i in animations) if (animations.hasOwnProperty(i)) { len++; if (len == 2) { break; } } len == 1 && frame(); return anim; }; /*\ * mina.time [ method ] ** * Returns the current time. Equivalent to: | function () { | return (new Date).getTime(); | } \*/ mina.time = timer; /*\ * mina.getById [ method ] ** * Returns an animation by its id - id (string) animation's id = (object) See @mina \*/ mina.getById = function (id) { return animations[id] || null; }; /*\ * mina.linear [ method ] ** * Default linear easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.linear = function (n) { return n; }; /*\ * mina.easeout [ method ] ** * Easeout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easeout = function (n) { return Math.pow(n, 1.7); }; /*\ * mina.easein [ method ] ** * Easein easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easein = function (n) { return Math.pow(n, .48); }; /*\ * mina.easeinout [ method ] ** * Easeinout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easeinout = function (n) { if (n == 1) { return 1; } if (n == 0) { return 0; } var q = .48 - n / 1.04, Q = Math.sqrt(.1734 + q * q), x = Q - q, X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1), y = -Q - q, Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1), t = X + Y + .5; return (1 - t) * 3 * t * t + t * t * t; }; /*\ * mina.backin [ method ] ** * Backin easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.backin = function (n) { if (n == 1) { return 1; } var s = 1.70158; return n * n * ((s + 1) * n - s); }; /*\ * mina.backout [ method ] ** * Backout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.backout = function (n) { if (n == 0) { return 0; } n = n - 1; var s = 1.70158; return n * n * ((s + 1) * n + s) + 1; }; /*\ * mina.elastic [ method ] ** * Elastic easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.elastic = function (n) { if (n == !!n) { return n; } return Math.pow(2, -10 * n) * Math.sin((n - .075) * (2 * Math.PI) / .3) + 1; }; /*\ * mina.bounce [ method ] ** * Bounce easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.bounce = function (n) { var s = 7.5625, p = 2.75, l; if (n < 1 / p) { l = s * n * n; } else { if (n < 2 / p) { n -= 1.5 / p; l = s * n * n + .75; } else { if (n < 2.5 / p) { n -= 2.25 / p; l = s * n * n + .9375; } else { n -= 2.625 / p; l = s * n * n + .984375; } } } return l; }; window.mina = mina; return mina; })(typeof eve == "undefined" ? function () {} : eve); // Copyright (c) 2013 - 2017 Adobe Systems Incorporated. All rights reserved. // // 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. var Snap = (function(root) { Snap.version = "0.5.1"; /*\ * Snap [ method ] ** * Creates a drawing surface or wraps existing SVG element. ** - width (number|string) width of surface - height (number|string) height of surface * or - DOM (SVGElement) element to be wrapped into Snap structure * or - array (array) array of elements (will return set of elements) * or - query (string) CSS query selector = (object) @Element \*/ function Snap(w, h) { if (w) { if (w.nodeType) { return wrap(w); } if (is(w, "array") && Snap.set) { return Snap.set.apply(Snap, w); } if (w instanceof Element) { return w; } if (h == null) { try { w = glob.doc.querySelector(String(w)); return wrap(w); } catch (e) { return null; } } } w = w == null ? "100%" : w; h = h == null ? "100%" : h; return new Paper(w, h); } Snap.toString = function () { return "Snap v" + this.version; }; Snap._ = {}; var glob = { win: root.window, doc: root.window.document }; Snap._.glob = glob; var has = "hasOwnProperty", Str = String, toFloat = parseFloat, toInt = parseInt, math = Math, mmax = math.max, mmin = math.min, abs = math.abs, pow = math.pow, PI = math.PI, round = math.round, E = "", S = " ", objectToString = Object.prototype.toString, ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i, colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i, bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, separator = Snap._.separator = /[,\s]+/, whitespace = /[\s]/g, commaSpaces = /[\s]*,[\s]*/, hsrg = {hs: 1, rg: 1}, pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\s]*,?[\s]*/ig, idgen = 0, idprefix = "S" + (+new Date).toString(36), ID = function (el) { return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36); }, xlink = "http://www.w3.org/1999/xlink", xmlns = "http://www.w3.org/2000/svg", hub = {}, /*\ * Snap.url [ method ] ** * Wraps path into `"url('')"`. - value (string) path = (string) wrapped path \*/ URL = Snap.url = function (url) { return "url('#" + url + "')"; }; function $(el, attr) { if (attr) { if (el == "#text") { el = glob.doc.createTextNode(attr.text || attr["#text"] || ""); } if (el == "#comment") { el = glob.doc.createComment(attr.text || attr["#text"] || ""); } if (typeof el == "string") { el = $(el); } if (typeof attr == "string") { if (el.nodeType == 1) { if (attr.substring(0, 6) == "xlink:") { return el.getAttributeNS(xlink, attr.substring(6)); } if (attr.substring(0, 4) == "xml:") { return el.getAttributeNS(xmlns, attr.substring(4)); } return el.getAttribute(attr); } else if (attr == "text") { return el.nodeValue; } else { return null; } } if (el.nodeType == 1) { for (var key in attr) if (attr[has](key)) { var val = Str(attr[key]); if (val) { if (key.substring(0, 6) == "xlink:") { el.setAttributeNS(xlink, key.substring(6), val); } else if (key.substring(0, 4) == "xml:") { el.setAttributeNS(xmlns, key.substring(4), val); } else { el.setAttribute(key, val); } } else { el.removeAttribute(key); } } } else if ("text" in attr) { el.nodeValue = attr.text; } } else { el = glob.doc.createElementNS(xmlns, el); } return el; } Snap._.$ = $; Snap._.id = ID; function getAttrs(el) { var attrs = el.attributes, name, out = {}; for (var i = 0; i < attrs.length; i++) { if (attrs[i].namespaceURI == xlink) { name = "xlink:"; } else { name = ""; } name += attrs[i].name; out[name] = attrs[i].textContent; } return out; } function is(o, type) { type = Str.prototype.toLowerCase.call(type); if (type == "finite") { return isFinite(o); } if (type == "array" && (o instanceof Array || Array.isArray && Array.isArray(o))) { return true; } return type == "null" && o === null || type == typeof o && o !== null || type == "object" && o === Object(o) || objectToString.call(o).slice(8, -1).toLowerCase() == type; } /*\ * Snap.format [ method ] ** * Replaces construction of type `{}` to the corresponding argument ** - token (string) string to format - json (object) object which properties are used as a replacement = (string) formatted string > Usage | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z" | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", { | x: 10, | y: 20, | dim: { | width: 40, | height: 50, | "negative width": -40 | } | })); \*/ Snap.format = (function () { var tokenRegex = /\{([^\}]+)\}/g, objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties replacer = function (all, key, obj) { var res = obj; key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) { name = name || quotedName; if (res) { if (name in res) { res = res[name]; } typeof res == "function" && isFunc && (res = res()); } }); res = (res == null || res == obj ? all : res) + ""; return res; }; return function (str, obj) { return Str(str).replace(tokenRegex, function (all, key) { return replacer(all, key, obj); }); }; })(); function clone(obj) { if (typeof obj == "function" || Object(obj) !== obj) { return obj; } var res = new obj.constructor; for (var key in obj) if (obj[has](key)) { res[key] = clone(obj[key]); } return res; } Snap._.clone = clone; function repush(array, item) { for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) { return array.push(array.splice(i, 1)[0]); } } function cacher(f, scope, postprocessor) { function newf() { var arg = Array.prototype.slice.call(arguments, 0), args = arg.join("\u2400"), cache = newf.cache = newf.cache || {}, count = newf.count = newf.count || []; if (cache[has](args)) { repush(count, args); return postprocessor ? postprocessor(cache[args]) : cache[args]; } count.length >= 1e3 && delete cache[count.shift()]; count.push(args); cache[args] = f.apply(scope, arg); return postprocessor ? postprocessor(cache[args]) : cache[args]; } return newf; } Snap._.cacher = cacher; function angle(x1, y1, x2, y2, x3, y3) { if (x3 == null) { var x = x1 - x2, y = y1 - y2; if (!x && !y) { return 0; } return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360; } else { return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3); } } function rad(deg) { return deg % 360 * PI / 180; } function deg(rad) { return rad * 180 / PI % 360; } function x_y() { return this.x + S + this.y; } function x_y_w_h() { return this.x + S + this.y + S + this.width + " \xd7 " + this.height; } /*\ * Snap.rad [ method ] ** * Transform angle to radians - deg (number) angle in degrees = (number) angle in radians \*/ Snap.rad = rad; /*\ * Snap.deg [ method ] ** * Transform angle to degrees - rad (number) angle in radians = (number) angle in degrees \*/ Snap.deg = deg; /*\ * Snap.sin [ method ] ** * Equivalent to `Math.sin()` only works with degrees, not radians. - angle (number) angle in degrees = (number) sin \*/ Snap.sin = function (angle) { return math.sin(Snap.rad(angle)); }; /*\ * Snap.tan [ method ] ** * Equivalent to `Math.tan()` only works with degrees, not radians. - angle (number) angle in degrees = (number) tan \*/ Snap.tan = function (angle) { return math.tan(Snap.rad(angle)); }; /*\ * Snap.cos [ method ] ** * Equivalent to `Math.cos()` only works with degrees, not radians. - angle (number) angle in degrees = (number) cos \*/ Snap.cos = function (angle) { return math.cos(Snap.rad(angle)); }; /*\ * Snap.asin [ method ] ** * Equivalent to `Math.asin()` only works with degrees, not radians. - num (number) value = (number) asin in degrees \*/ Snap.asin = function (num) { return Snap.deg(math.asin(num)); }; /*\ * Snap.acos [ method ] ** * Equivalent to `Math.acos()` only works with degrees, not radians. - num (number) value = (number) acos in degrees \*/ Snap.acos = function (num) { return Snap.deg(math.acos(num)); }; /*\ * Snap.atan [ method ] ** * Equivalent to `Math.atan()` only works with degrees, not radians. - num (number) value = (number) atan in degrees \*/ Snap.atan = function (num) { return Snap.deg(math.atan(num)); }; /*\ * Snap.atan2 [ method ] ** * Equivalent to `Math.atan2()` only works with degrees, not radians. - num (number) value = (number) atan2 in degrees \*/ Snap.atan2 = function (num) { return Snap.deg(math.atan2(num)); }; /*\ * Snap.angle [ method ] ** * Returns an angle between two or three points - x1 (number) x coord of first point - y1 (number) y coord of first point - x2 (number) x coord of second point - y2 (number) y coord of second point - x3 (number) #optional x coord of third point - y3 (number) #optional y coord of third point = (number) angle in degrees \*/ Snap.angle = angle; /*\ * Snap.len [ method ] ** * Returns distance between two points - x1 (number) x coord of first point - y1 (number) y coord of first point - x2 (number) x coord of second point - y2 (number) y coord of second point = (number) distance \*/ Snap.len = function (x1, y1, x2, y2) { return Math.sqrt(Snap.len2(x1, y1, x2, y2)); }; /*\ * Snap.len2 [ method ] ** * Returns squared distance between two points - x1 (number) x coord of first point - y1 (number) y coord of first point - x2 (number) x coord of second point - y2 (number) y coord of second point = (number) distance \*/ Snap.len2 = function (x1, y1, x2, y2) { return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); }; /*\ * Snap.closestPoint [ method ] ** * Returns closest point to a given one on a given path. - path (Element) path element - x (number) x coord of a point - y (number) y coord of a point = (object) in format { x (number) x coord of the point on the path y (number) y coord of the point on the path length (number) length of the path to the point distance (number) distance from the given point to the path } \*/ // Copied from http://bl.ocks.org/mbostock/8027637 Snap.closestPoint = function (path, x, y) { function distance2(p) { var dx = p.x - x, dy = p.y - y; return dx * dx + dy * dy; } var pathNode = path.node, pathLength = pathNode.getTotalLength(), precision = pathLength / pathNode.pathSegList.numberOfItems * .125, best, bestLength, bestDistance = Infinity; // linear scan for coarse approximation for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) { if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) { best = scan; bestLength = scanLength; bestDistance = scanDistance; } } // binary search for precise estimate precision *= .5; while (precision > .5) { var before, after, beforeLength, afterLength, beforeDistance, afterDistance; if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) { best = before; bestLength = beforeLength; bestDistance = beforeDistance; } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) { best = after; bestLength = afterLength; bestDistance = afterDistance; } else { precision *= .5; } } best = { x: best.x, y: best.y, length: bestLength, distance: Math.sqrt(bestDistance) }; return best; } /*\ * Snap.is [ method ] ** * Handy replacement for the `typeof` operator - o (…) any object or primitive - type (string) name of the type, e.g., `string`, `function`, `number`, etc. = (boolean) `true` if given value is of given type \*/ Snap.is = is; /*\ * Snap.snapTo [ method ] ** * Snaps given value to given grid - values (array|number) given array of values or step of the grid - value (number) value to adjust - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`. = (number) adjusted value \*/ Snap.snapTo = function (values, value, tolerance) { tolerance = is(tolerance, "finite") ? tolerance : 10; if (is(values, "array")) { var i = values.length; while (i--) if (abs(values[i] - value) <= tolerance) { return values[i]; } } else { values = +values; var rem = value % values; if (rem < tolerance) { return value - rem; } if (rem > values - tolerance) { return value - rem + values; } } return value; }; // Colour /*\ * Snap.getRGB [ method ] ** * Parses color string as RGB object - color (string) color string in one of the following formats: #
    #
  • Color name (red, green, cornflowerblue, etc)
  • #
  • #••• — shortened HTML color: (#000, #fc0, etc.)
  • #
  • #•••••• — full length HTML color: (#000000, #bd2300)
  • #
  • rgb(•••, •••, •••) — red, green and blue channels values: (rgb(200, 100, 0))
  • #
  • rgba(•••, •••, •••, •••) — also with opacity
  • #
  • rgb(•••%, •••%, •••%) — same as above, but in %: (rgb(100%, 175%, 0%))
  • #
  • rgba(•••%, •••%, •••%, •••%) — also with opacity
  • #
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (hsb(0.5, 0.25, 1))
  • #
  • hsba(•••, •••, •••, •••) — also with opacity
  • #
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • #
  • hsba(•••%, •••%, •••%, •••%) — also with opacity
  • #
  • hsl(•••, •••, •••) — hue, saturation and luminosity values: (hsb(0.5, 0.25, 0.5))
  • #
  • hsla(•••, •••, •••, •••) — also with opacity
  • #
  • hsl(•••%, •••%, •••%) — same as above, but in %
  • #
  • hsla(•••%, •••%, •••%, •••%) — also with opacity
  • #
* Note that `%` can be used any time: `rgb(20%, 255, 50%)`. = (object) RGB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #••••••, o error (boolean) true if string can't be parsed o } \*/ Snap.getRGB = cacher(function (colour) { if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) { return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; } if (colour == "none") { return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString}; } !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour)); if (!colour) { return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; } var res, red, green, blue, opacity, t, values, rgb = colour.match(colourRegExp); if (rgb) { if (rgb[2]) { blue = toInt(rgb[2].substring(5), 16); green = toInt(rgb[2].substring(3, 5), 16); red = toInt(rgb[2].substring(1, 3), 16); } if (rgb[3]) { blue = toInt((t = rgb[3].charAt(3)) + t, 16); green = toInt((t = rgb[3].charAt(2)) + t, 16); red = toInt((t = rgb[3].charAt(1)) + t, 16); } if (rgb[4]) { values = rgb[4].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red *= 2.55); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green *= 2.55); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue *= 2.55); rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); } if (rgb[5]) { values = rgb[5].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red /= 100); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green /= 100); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue /= 100); (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); return Snap.hsb2rgb(red, green, blue, opacity); } if (rgb[6]) { values = rgb[6].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red /= 100); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green /= 100); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue /= 100); (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); return Snap.hsl2rgb(red, green, blue, opacity); } red = mmin(math.round(red), 255); green = mmin(math.round(green), 255); blue = mmin(math.round(blue), 255); opacity = mmin(mmax(opacity, 0), 1); rgb = {r: red, g: green, b: blue, toString: rgbtoString}; rgb.hex = "#" + (16777216 | blue | green << 8 | red << 16).toString(16).slice(1); rgb.opacity = is(opacity, "finite") ? opacity : 1; return rgb; } return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; }, Snap); /*\ * Snap.hsb [ method ] ** * Converts HSB values to a hex representation of the color - h (number) hue - s (number) saturation - b (number) value or brightness = (string) hex representation of the color \*/ Snap.hsb = cacher(function (h, s, b) { return Snap.hsb2rgb(h, s, b).hex; }); /*\ * Snap.hsl [ method ] ** * Converts HSL values to a hex representation of the color - h (number) hue - s (number) saturation - l (number) luminosity = (string) hex representation of the color \*/ Snap.hsl = cacher(function (h, s, l) { return Snap.hsl2rgb(h, s, l).hex; }); /*\ * Snap.rgb [ method ] ** * Converts RGB values to a hex representation of the color - r (number) red - g (number) green - b (number) blue = (string) hex representation of the color \*/ Snap.rgb = cacher(function (r, g, b, o) { if (is(o, "finite")) { var round = math.round; return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")"; } return "#" + (16777216 | b | g << 8 | r << 16).toString(16).slice(1); }); var toHex = function (color) { var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0], red = "rgb(255, 0, 0)"; toHex = cacher(function (color) { if (color.toLowerCase() == "red") { return red; } i.style.color = red; i.style.color = color; var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color"); return out == red ? null : out; }); return toHex(color); }, hsbtoString = function () { return "hsb(" + [this.h, this.s, this.b] + ")"; }, hsltoString = function () { return "hsl(" + [this.h, this.s, this.l] + ")"; }, rgbtoString = function () { return this.opacity == 1 || this.opacity == null ? this.hex : "rgba(" + [this.r, this.g, this.b, this.opacity] + ")"; }, prepareRGB = function (r, g, b) { if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) { b = r.b; g = r.g; r = r.r; } if (g == null && is(r, string)) { var clr = Snap.getRGB(r); r = clr.r; g = clr.g; b = clr.b; } if (r > 1 || g > 1 || b > 1) { r /= 255; g /= 255; b /= 255; } return [r, g, b]; }, packageRGB = function (r, g, b, o) { r = math.round(r * 255); g = math.round(g * 255); b = math.round(b * 255); var rgb = { r: r, g: g, b: b, opacity: is(o, "finite") ? o : 1, hex: Snap.rgb(r, g, b), toString: rgbtoString }; is(o, "finite") && (rgb.opacity = o); return rgb; }; /*\ * Snap.color [ method ] ** * Parses the color string and returns an object featuring the color's component values - clr (string) color string in one of the supported formats (see @Snap.getRGB) = (object) Combined RGB/HSB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #••••••, o error (boolean) `true` if string can't be parsed, o h (number) hue, o s (number) saturation, o v (number) value (brightness), o l (number) lightness o } \*/ Snap.color = function (clr) { var rgb; if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) { rgb = Snap.hsb2rgb(clr); clr.r = rgb.r; clr.g = rgb.g; clr.b = rgb.b; clr.opacity = 1; clr.hex = rgb.hex; } else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) { rgb = Snap.hsl2rgb(clr); clr.r = rgb.r; clr.g = rgb.g; clr.b = rgb.b; clr.opacity = 1; clr.hex = rgb.hex; } else { if (is(clr, "string")) { clr = Snap.getRGB(clr); } if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) { rgb = Snap.rgb2hsl(clr); clr.h = rgb.h; clr.s = rgb.s; clr.l = rgb.l; rgb = Snap.rgb2hsb(clr); clr.v = rgb.b; } else { clr = {hex: "none"}; clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1; clr.error = 1; } } clr.toString = rgbtoString; return clr; }; /*\ * Snap.hsb2rgb [ method ] ** * Converts HSB values to an RGB object - h (number) hue - s (number) saturation - v (number) value or brightness = (object) RGB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #•••••• o } \*/ Snap.hsb2rgb = function (h, s, v, o) { if (is(h, "object") && "h" in h && "s" in h && "b" in h) { v = h.b; s = h.s; o = h.o; h = h.h; } h *= 360; var R, G, B, X, C; h = h % 360 / 60; C = v * s; X = C * (1 - abs(h % 2 - 1)); R = G = B = v - C; h = ~~h; R += [C, X, 0, 0, X, C][h]; G += [X, C, C, X, 0, 0][h]; B += [0, 0, X, C, C, X][h]; return packageRGB(R, G, B, o); }; /*\ * Snap.hsl2rgb [ method ] ** * Converts HSL values to an RGB object - h (number) hue - s (number) saturation - l (number) luminosity = (object) RGB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #•••••• o } \*/ Snap.hsl2rgb = function (h, s, l, o) { if (is(h, "object") && "h" in h && "s" in h && "l" in h) { l = h.l; s = h.s; h = h.h; } if (h > 1 || s > 1 || l > 1) { h /= 360; s /= 100; l /= 100; } h *= 360; var R, G, B, X, C; h = h % 360 / 60; C = 2 * s * (l < .5 ? l : 1 - l); X = C * (1 - abs(h % 2 - 1)); R = G = B = l - C / 2; h = ~~h; R += [C, X, 0, 0, X, C][h]; G += [X, C, C, X, 0, 0][h]; B += [0, 0, X, C, C, X][h]; return packageRGB(R, G, B, o); }; /*\ * Snap.rgb2hsb [ method ] ** * Converts RGB values to an HSB object - r (number) red - g (number) green - b (number) blue = (object) HSB object in the following format: o { o h (number) hue, o s (number) saturation, o b (number) brightness o } \*/ Snap.rgb2hsb = function (r, g, b) { b = prepareRGB(r, g, b); r = b[0]; g = b[1]; b = b[2]; var H, S, V, C; V = mmax(r, g, b); C = V - mmin(r, g, b); H = C == 0 ? null : V == r ? (g - b) / C : V == g ? (b - r) / C + 2 : (r - g) / C + 4; H = (H + 360) % 6 * 60 / 360; S = C == 0 ? 0 : C / V; return {h: H, s: S, b: V, toString: hsbtoString}; }; /*\ * Snap.rgb2hsl [ method ] ** * Converts RGB values to an HSL object - r (number) red - g (number) green - b (number) blue = (object) HSL object in the following format: o { o h (number) hue, o s (number) saturation, o l (number) luminosity o } \*/ Snap.rgb2hsl = function (r, g, b) { b = prepareRGB(r, g, b); r = b[0]; g = b[1]; b = b[2]; var H, S, L, M, m, C; M = mmax(r, g, b); m = mmin(r, g, b); C = M - m; H = C == 0 ? null : M == r ? (g - b) / C : M == g ? (b - r) / C + 2 : (r - g) / C + 4; H = (H + 360) % 6 * 60 / 360; L = (M + m) / 2; S = C == 0 ? 0 : L < .5 ? C / (2 * L) : C / (2 - 2 * L); return {h: H, s: S, l: L, toString: hsltoString}; }; // Transformations /*\ * Snap.parsePathString [ method ] ** * Utility method ** * Parses given path string into an array of arrays of path segments - pathString (string|array) path string or array of segments (in the last case it is returned straight away) = (array) array of segments \*/ Snap.parsePathString = function (pathString) { if (!pathString) { return null; } var pth = Snap.path(pathString); if (pth.arr) { return Snap.path.clone(pth.arr); } var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0}, data = []; if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption data = Snap.path.clone(pathString); } if (!data.length) { Str(pathString).replace(pathCommand, function (a, b, c) { var params = [], name = b.toLowerCase(); c.replace(pathValues, function (a, b) { b && params.push(+b); }); if (name == "m" && params.length > 2) { data.push([b].concat(params.splice(0, 2))); name = "l"; b = b == "m" ? "l" : "L"; } if (name == "o" && params.length == 1) { data.push([b, params[0]]); } if (name == "r") { data.push([b].concat(params)); } else while (params.length >= paramCounts[name]) { data.push([b].concat(params.splice(0, paramCounts[name]))); if (!paramCounts[name]) { break; } } }); } data.toString = Snap.path.toString; pth.arr = Snap.path.clone(data); return data; }; /*\ * Snap.parseTransformString [ method ] ** * Utility method ** * Parses given transform string into an array of transformations - TString (string|array) transform string or array of transformations (in the last case it is returned straight away) = (array) array of transformations \*/ var parseTransformString = Snap.parseTransformString = function (TString) { if (!TString) { return null; } var paramCounts = {r: 3, s: 4, t: 2, m: 6}, data = []; if (is(TString, "array") && is(TString[0], "array")) { // rough assumption data = Snap.path.clone(TString); } if (!data.length) { Str(TString).replace(tCommand, function (a, b, c) { var params = [], name = b.toLowerCase(); c.replace(pathValues, function (a, b) { b && params.push(+b); }); data.push([b].concat(params)); }); } data.toString = Snap.path.toString; return data; }; function svgTransform2string(tstr) { var res = []; tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) { params = params.split(/\s*,\s*|\s+/); if (name == "rotate" && params.length == 1) { params.push(0, 0); } if (name == "scale") { if (params.length > 2) { params = params.slice(0, 2); } else if (params.length == 2) { params.push(0, 0); } if (params.length == 1) { params.push(params[0], 0, 0); } } if (name == "skewX") { res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]); } else if (name == "skewY") { res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]); } else { res.push([name.charAt(0)].concat(params)); } return all; }); return res; } Snap._.svgTransform2string = svgTransform2string; Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i; function transform2matrix(tstr, bbox) { var tdata = parseTransformString(tstr), m = new Snap.Matrix; if (tdata) { for (var i = 0, ii = tdata.length; i < ii; i++) { var t = tdata[i], tlen = t.length, command = Str(t[0]).toLowerCase(), absolute = t[0] != command, inver = absolute ? m.invert() : 0, x1, y1, x2, y2, bb; if (command == "t" && tlen == 2){ m.translate(t[1], 0); } else if (command == "t" && tlen == 3) { if (absolute) { x1 = inver.x(0, 0); y1 = inver.y(0, 0); x2 = inver.x(t[1], t[2]); y2 = inver.y(t[1], t[2]); m.translate(x2 - x1, y2 - y1); } else { m.translate(t[1], t[2]); } } else if (command == "r") { if (tlen == 2) { bb = bb || bbox; m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2); } else if (tlen == 4) { if (absolute) { x2 = inver.x(t[2], t[3]); y2 = inver.y(t[2], t[3]); m.rotate(t[1], x2, y2); } else { m.rotate(t[1], t[2], t[3]); } } } else if (command == "s") { if (tlen == 2 || tlen == 3) { bb = bb || bbox; m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2); } else if (tlen == 4) { if (absolute) { x2 = inver.x(t[2], t[3]); y2 = inver.y(t[2], t[3]); m.scale(t[1], t[1], x2, y2); } else { m.scale(t[1], t[1], t[2], t[3]); } } else if (tlen == 5) { if (absolute) { x2 = inver.x(t[3], t[4]); y2 = inver.y(t[3], t[4]); m.scale(t[1], t[2], x2, y2); } else { m.scale(t[1], t[2], t[3], t[4]); } } } else if (command == "m" && tlen == 7) { m.add(t[1], t[2], t[3], t[4], t[5], t[6]); } } } return m; } Snap._.transform2matrix = transform2matrix; Snap._unit2px = unit2px; var contains = glob.doc.contains || glob.doc.compareDocumentPosition ? function (a, b) { var adown = a.nodeType == 9 ? a.documentElement : a, bup = b && b.parentNode; return a == bup || !!(bup && bup.nodeType == 1 && ( adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16 )); } : function (a, b) { if (b) { while (b) { b = b.parentNode; if (b == a) { return true; } } } return false; }; function getSomeDefs(el) { var p = el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || el.node.parentNode && wrap(el.node.parentNode) || Snap.select("svg") || Snap(0, 0), pdefs = p.select("defs"), defs = pdefs == null ? false : pdefs.node; if (!defs) { defs = make("defs", p.node).node; } return defs; } function getSomeSVG(el) { return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg"); } Snap._.getSomeDefs = getSomeDefs; Snap._.getSomeSVG = getSomeSVG; function unit2px(el, name, value) { var svg = getSomeSVG(el).node, out = {}, mgr = svg.querySelector(".svg---mgr"); if (!mgr) { mgr = $("rect"); $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"}); svg.appendChild(mgr); } function getW(val) { if (val == null) { return E; } if (val == +val) { return val; } $(mgr, {width: val}); try { return mgr.getBBox().width; } catch (e) { return 0; } } function getH(val) { if (val == null) { return E; } if (val == +val) { return val; } $(mgr, {height: val}); try { return mgr.getBBox().height; } catch (e) { return 0; } } function set(nam, f) { if (name == null) { out[nam] = f(el.attr(nam) || 0); } else if (nam == name) { out = f(value == null ? el.attr(nam) || 0 : value); } } switch (el.type) { case "rect": set("rx", getW); set("ry", getH); case "image": set("width", getW); set("height", getH); case "text": set("x", getW); set("y", getH); break; case "circle": set("cx", getW); set("cy", getH); set("r", getW); break; case "ellipse": set("cx", getW); set("cy", getH); set("rx", getW); set("ry", getH); break; case "line": set("x1", getW); set("x2", getW); set("y1", getH); set("y2", getH); break; case "marker": set("refX", getW); set("markerWidth", getW); set("refY", getH); set("markerHeight", getH); break; case "radialGradient": set("fx", getW); set("fy", getH); break; case "tspan": set("dx", getW); set("dy", getH); break; default: set(name, getW); } svg.removeChild(mgr); return out; } /*\ * Snap.select [ method ] ** * Wraps a DOM element specified by CSS selector as @Element - query (string) CSS selector of the element = (Element) the current element \*/ Snap.select = function (query) { query = Str(query).replace(/([^\\]):/g, "$1\\:"); return wrap(glob.doc.querySelector(query)); }; /*\ * Snap.selectAll [ method ] ** * Wraps DOM elements specified by CSS selector as set or array of @Element - query (string) CSS selector of the element = (Element) the current element \*/ Snap.selectAll = function (query) { var nodelist = glob.doc.querySelectorAll(query), set = (Snap.set || Array)(); for (var i = 0; i < nodelist.length; i++) { set.push(wrap(nodelist[i])); } return set; }; function add2group(list) { if (!is(list, "array")) { list = Array.prototype.slice.call(arguments, 0); } var i = 0, j = 0, node = this.node; while (this[i]) delete this[i++]; for (i = 0; i < list.length; i++) { if (list[i].type == "set") { list[i].forEach(function (el) { node.appendChild(el.node); }); } else { node.appendChild(list[i].node); } } var children = node.childNodes; for (i = 0; i < children.length; i++) { this[j++] = wrap(children[i]); } return this; } // Hub garbage collector every 10s setInterval(function () { for (var key in hub) if (hub[has](key)) { var el = hub[key], node = el.node; if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) { delete hub[key]; } } }, 1e4); function Element(el) { if (el.snap in hub) { return hub[el.snap]; } var svg; try { svg = el.ownerSVGElement; } catch(e) {} /*\ * Element.node [ property (object) ] ** * Gives you a reference to the DOM object, so you can assign event handlers or just mess around. > Usage | // draw a circle at coordinate 10,10 with radius of 10 | var c = paper.circle(10, 10, 10); | c.node.onclick = function () { | c.attr("fill", "red"); | }; \*/ this.node = el; if (svg) { this.paper = new Paper(svg); } /*\ * Element.type [ property (string) ] ** * SVG tag name of the given element. \*/ this.type = el.tagName || el.nodeName; var id = this.id = ID(this); this.anims = {}; this._ = { transform: [] }; el.snap = id; hub[id] = this; if (this.type == "g") { this.add = add2group; } if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) { for (var method in Paper.prototype) if (Paper.prototype[has](method)) { this[method] = Paper.prototype[method]; } } } /*\ * Element.attr [ method ] ** * Gets or sets given attributes of the element. ** - params (object) contains key-value pairs of attributes you want to set * or - param (string) name of the attribute = (Element) the current element * or = (string) value of attribute > Usage | el.attr({ | fill: "#fc0", | stroke: "#000", | strokeWidth: 2, // CamelCase... | "fill-opacity": 0.5, // or dash-separated names | width: "*=2" // prefixed values | }); | console.log(el.attr("fill")); // #fc0 * Prefixed values in format `"+=10"` supported. All four operations * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+` * and `-`: `"+=2em"`. \*/ Element.prototype.attr = function (params, value) { var el = this, node = el.node; if (!params) { if (node.nodeType != 1) { return { text: node.nodeValue }; } var attr = node.attributes, out = {}; for (var i = 0, ii = attr.length; i < ii; i++) { out[attr[i].nodeName] = attr[i].nodeValue; } return out; } if (is(params, "string")) { if (arguments.length > 1) { var json = {}; json[params] = value; params = json; } else { return eve("snap.util.getattr." + params, el).firstDefined(); } } for (var att in params) { if (params[has](att)) { eve("snap.util.attr." + att, el, params[att]); } } return el; }; /*\ * Snap.parse [ method ] ** * Parses SVG fragment and converts it into a @Fragment ** - svg (string) SVG string = (Fragment) the @Fragment \*/ Snap.parse = function (svg) { var f = glob.doc.createDocumentFragment(), full = true, div = glob.doc.createElement("div"); svg = Str(svg); if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) { svg = "" + svg + ""; full = false; } div.innerHTML = svg; svg = div.getElementsByTagName("svg")[0]; if (svg) { if (full) { f = svg; } else { while (svg.firstChild) { f.appendChild(svg.firstChild); } } } return new Fragment(f); }; function Fragment(frag) { this.node = frag; } /*\ * Snap.fragment [ method ] ** * Creates a DOM fragment from a given list of elements or strings ** - varargs (…) SVG string = (Fragment) the @Fragment \*/ Snap.fragment = function () { var args = Array.prototype.slice.call(arguments, 0), f = glob.doc.createDocumentFragment(); for (var i = 0, ii = args.length; i < ii; i++) { var item = args[i]; if (item.node && item.node.nodeType) { f.appendChild(item.node); } if (item.nodeType) { f.appendChild(item); } if (typeof item == "string") { f.appendChild(Snap.parse(item).node); } } return new Fragment(f); }; function make(name, parent) { var res = $(name); parent.appendChild(res); var el = wrap(res); return el; } function Paper(w, h) { var res, desc, defs, proto = Paper.prototype; if (w && w.tagName && w.tagName.toLowerCase() == "svg") { if (w.snap in hub) { return hub[w.snap]; } var doc = w.ownerDocument; res = new Element(w); desc = w.getElementsByTagName("desc")[0]; defs = w.getElementsByTagName("defs")[0]; if (!desc) { desc = $("desc"); desc.appendChild(doc.createTextNode("Created with Snap")); res.node.appendChild(desc); } if (!defs) { defs = $("defs"); res.node.appendChild(defs); } res.defs = defs; for (var key in proto) if (proto[has](key)) { res[key] = proto[key]; } res.paper = res.root = res; } else { res = make("svg", glob.doc.body); $(res.node, { height: h, version: 1.1, width: w, xmlns: xmlns }); } return res; } function wrap(dom) { if (!dom) { return dom; } if (dom instanceof Element || dom instanceof Fragment) { return dom; } if (dom.tagName && dom.tagName.toLowerCase() == "svg") { return new Paper(dom); } if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") { return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]); } return new Element(dom); } Snap._.make = make; Snap._.wrap = wrap; /*\ * Paper.el [ method ] ** * Creates an element on paper with a given name and no attributes ** - name (string) tag name - attr (object) attributes = (Element) the current element > Usage | var c = paper.circle(10, 10, 10); // is the same as... | var c = paper.el("circle").attr({ | cx: 10, | cy: 10, | r: 10 | }); | // and the same as | var c = paper.el("circle", { | cx: 10, | cy: 10, | r: 10 | }); \*/ Paper.prototype.el = function (name, attr) { var el = make(name, this.node); attr && el.attr(attr); return el; }; /*\ * Element.children [ method ] ** * Returns array of all the children of the element. = (array) array of Elements \*/ Element.prototype.children = function () { var out = [], ch = this.node.childNodes; for (var i = 0, ii = ch.length; i < ii; i++) { out[i] = Snap(ch[i]); } return out; }; function jsonFiller(root, o) { for (var i = 0, ii = root.length; i < ii; i++) { var item = { type: root[i].type, attr: root[i].attr() }, children = root[i].children(); o.push(item); if (children.length) { jsonFiller(children, item.childNodes = []); } } } /*\ * Element.toJSON [ method ] ** * Returns object representation of the given element and all its children. = (object) in format o { o type (string) this.type, o attr (object) attributes map, o childNodes (array) optional array of children in the same format o } \*/ Element.prototype.toJSON = function () { var out = []; jsonFiller([this], out); return out[0]; }; // default eve.on("snap.util.getattr", function () { var att = eve.nt(); att = att.substring(att.lastIndexOf(".") + 1); var css = att.replace(/[A-Z]/g, function (letter) { return "-" + letter.toLowerCase(); }); if (cssAttr[has](css)) { return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css); } else { return $(this.node, att); } }); var cssAttr = { "alignment-baseline": 0, "baseline-shift": 0, "clip": 0, "clip-path": 0, "clip-rule": 0, "color": 0, "color-interpolation": 0, "color-interpolation-filters": 0, "color-profile": 0, "color-rendering": 0, "cursor": 0, "direction": 0, "display": 0, "dominant-baseline": 0, "enable-background": 0, "fill": 0, "fill-opacity": 0, "fill-rule": 0, "filter": 0, "flood-color": 0, "flood-opacity": 0, "font": 0, "font-family": 0, "font-size": 0, "font-size-adjust": 0, "font-stretch": 0, "font-style": 0, "font-variant": 0, "font-weight": 0, "glyph-orientation-horizontal": 0, "glyph-orientation-vertical": 0, "image-rendering": 0, "kerning": 0, "letter-spacing": 0, "lighting-color": 0, "marker": 0, "marker-end": 0, "marker-mid": 0, "marker-start": 0, "mask": 0, "opacity": 0, "overflow": 0, "pointer-events": 0, "shape-rendering": 0, "stop-color": 0, "stop-opacity": 0, "stroke": 0, "stroke-dasharray": 0, "stroke-dashoffset": 0, "stroke-linecap": 0, "stroke-linejoin": 0, "stroke-miterlimit": 0, "stroke-opacity": 0, "stroke-width": 0, "text-anchor": 0, "text-decoration": 0, "text-rendering": 0, "unicode-bidi": 0, "visibility": 0, "word-spacing": 0, "writing-mode": 0 }; eve.on("snap.util.attr", function (value) { var att = eve.nt(), attr = {}; att = att.substring(att.lastIndexOf(".") + 1); attr[att] = value; var style = att.replace(/-(\w)/gi, function (all, letter) { return letter.toUpperCase(); }), css = att.replace(/[A-Z]/g, function (letter) { return "-" + letter.toLowerCase(); }); if (cssAttr[has](css)) { this.node.style[style] = value == null ? E : value; } else { $(this.node, attr); } }); (function (proto) {}(Paper.prototype)); // simple ajax /*\ * Snap.ajax [ method ] ** * Simple implementation of Ajax ** - url (string) URL - postData (object|string) data for post request - callback (function) callback - scope (object) #optional scope of callback * or - url (string) URL - callback (function) callback - scope (object) #optional scope of callback = (XMLHttpRequest) the XMLHttpRequest object, just in case \*/ Snap.ajax = function (url, postData, callback, scope){ var req = new XMLHttpRequest, id = ID(); if (req) { if (is(postData, "function")) { scope = callback; callback = postData; postData = null; } else if (is(postData, "object")) { var pd = []; for (var key in postData) if (postData.hasOwnProperty(key)) { pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key])); } postData = pd.join("&"); } req.open(postData ? "POST" : "GET", url, true); if (postData) { req.setRequestHeader("X-Requested-With", "XMLHttpRequest"); req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); } if (callback) { eve.once("snap.ajax." + id + ".0", callback); eve.once("snap.ajax." + id + ".200", callback); eve.once("snap.ajax." + id + ".304", callback); } req.onreadystatechange = function() { if (req.readyState != 4) return; eve("snap.ajax." + id + "." + req.status, scope, req); }; if (req.readyState == 4) { return req; } req.send(postData); return req; } }; /*\ * Snap.load [ method ] ** * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX) ** - url (string) URL - callback (function) callback - scope (object) #optional scope of callback \*/ Snap.load = function (url, callback, scope) { Snap.ajax(url, function (req) { var f = Snap.parse(req.responseText); scope ? callback.call(scope, f) : callback(f); }); }; var getOffset = function (elem) { var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement, clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop, left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft; return { y: top, x: left }; }; /*\ * Snap.getElementByPoint [ method ] ** * Returns you topmost element under given point. ** = (object) Snap element object - x (number) x coordinate from the top left corner of the window - y (number) y coordinate from the top left corner of the window > Usage | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); \*/ Snap.getElementByPoint = function (x, y) { var paper = this, svg = paper.canvas, target = glob.doc.elementFromPoint(x, y); if (glob.win.opera && target.tagName == "svg") { var so = getOffset(target), sr = target.createSVGRect(); sr.x = x - so.x; sr.y = y - so.y; sr.width = sr.height = 1; var hits = target.getIntersectionList(sr, null); if (hits.length) { target = hits[hits.length - 1]; } } if (!target) { return null; } return wrap(target); }; /*\ * Snap.plugin [ method ] ** * Let you write plugins. You pass in a function with five arguments, like this: | Snap.plugin(function (Snap, Element, Paper, global, Fragment) { | Snap.newmethod = function () {}; | Element.prototype.newmethod = function () {}; | Paper.prototype.newmethod = function () {}; | }); * Inside the function you have access to all main objects (and their * prototypes). This allow you to extend anything you want. ** - f (function) your plugin body \*/ Snap.plugin = function (f) { f(Snap, Element, Paper, glob, Fragment); }; glob.win.Snap = Snap; return Snap; }(window || this)); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var elproto = Element.prototype, is = Snap.is, Str = String, unit2px = Snap._unit2px, $ = Snap._.$, make = Snap._.make, getSomeDefs = Snap._.getSomeDefs, has = "hasOwnProperty", wrap = Snap._.wrap; /*\ * Element.getBBox [ method ] ** * Returns the bounding box descriptor for the given element ** = (object) bounding box descriptor: o { o cx: (number) x of the center, o cy: (number) x of the center, o h: (number) height, o height: (number) height, o path: (string) path command for the box, o r0: (number) radius of a circle that fully encloses the box, o r1: (number) radius of the smallest circle that can be enclosed, o r2: (number) radius of the largest circle that can be enclosed, o vb: (string) box as a viewbox command, o w: (number) width, o width: (number) width, o x2: (number) x of the right side, o x: (number) x of the left side, o y2: (number) y of the bottom edge, o y: (number) y of the top edge o } \*/ elproto.getBBox = function (isWithoutTransform) { if (this.type == "tspan") { return Snap._.box(this.node.getClientRects().item(0)); } if (!Snap.Matrix || !Snap.path) { return this.node.getBBox(); } var el = this, m = new Snap.Matrix; if (el.removed) { return Snap._.box(); } while (el.type == "use") { if (!isWithoutTransform) { m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0)); } if (el.original) { el = el.original; } else { var href = el.attr("xlink:href"); el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1)); } } var _ = el._, pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt; try { if (isWithoutTransform) { _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox()); return Snap._.box(_.bboxwt); } else { el.realPath = pathfinder(el); el.matrix = el.transform().localMatrix; _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix))); return Snap._.box(_.bbox); } } catch (e) { // Firefox doesn’t give you bbox of hidden element return Snap._.box(); } }; var propString = function () { return this.string; }; function extractTransform(el, tstr) { if (tstr == null) { var doReturn = true; if (el.type == "linearGradient" || el.type == "radialGradient") { tstr = el.node.getAttribute("gradientTransform"); } else if (el.type == "pattern") { tstr = el.node.getAttribute("patternTransform"); } else { tstr = el.node.getAttribute("transform"); } if (!tstr) { return new Snap.Matrix; } tstr = Snap._.svgTransform2string(tstr); } else { if (!Snap._.rgTransform.test(tstr)) { tstr = Snap._.svgTransform2string(tstr); } else { tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || ""); } if (is(tstr, "array")) { tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr); } el._.transform = tstr; } var m = Snap._.transform2matrix(tstr, el.getBBox(1)); if (doReturn) { return m; } else { el.matrix = m; } } /*\ * Element.transform [ method ] ** * Gets or sets transformation of the element ** - tstr (string) transform string in Snap or SVG format = (Element) the current element * or = (object) transformation descriptor: o { o string (string) transform string, o globalMatrix (Matrix) matrix of all transformations applied to element or its parents, o localMatrix (Matrix) matrix of transformations applied only to the element, o diffMatrix (Matrix) matrix of difference between global and local transformations, o global (string) global transformation as string, o local (string) local transformation as string, o toString (function) returns `string` property o } \*/ elproto.transform = function (tstr) { var _ = this._; if (tstr == null) { var papa = this, global = new Snap.Matrix(this.node.getCTM()), local = extractTransform(this), ms = [local], m = new Snap.Matrix, i, localString = local.toTransformString(), string = Str(local) == Str(this.matrix) ? Str(_.transform) : localString; while (papa.type != "svg" && (papa = papa.parent())) { ms.push(extractTransform(papa)); } i = ms.length; while (i--) { m.add(ms[i]); } return { string: string, globalMatrix: global, totalMatrix: m, localMatrix: local, diffMatrix: global.clone().add(local.invert()), global: global.toTransformString(), total: m.toTransformString(), local: localString, toString: propString }; } if (tstr instanceof Snap.Matrix) { this.matrix = tstr; this._.transform = tstr.toTransformString(); } else { extractTransform(this, tstr); } if (this.node) { if (this.type == "linearGradient" || this.type == "radialGradient") { $(this.node, {gradientTransform: this.matrix}); } else if (this.type == "pattern") { $(this.node, {patternTransform: this.matrix}); } else { $(this.node, {transform: this.matrix}); } } return this; }; /*\ * Element.parent [ method ] ** * Returns the element's parent ** = (Element) the parent element \*/ elproto.parent = function () { return wrap(this.node.parentNode); }; /*\ * Element.append [ method ] ** * Appends the given element to current one ** - el (Element|Set) element to append = (Element) the parent element \*/ /*\ * Element.add [ method ] ** * See @Element.append \*/ elproto.append = elproto.add = function (el) { if (el) { if (el.type == "set") { var it = this; el.forEach(function (el) { it.add(el); }); return this; } el = wrap(el); this.node.appendChild(el.node); el.paper = this.paper; } return this; }; /*\ * Element.appendTo [ method ] ** * Appends the current element to the given one ** - el (Element) parent element to append to = (Element) the child element \*/ elproto.appendTo = function (el) { if (el) { el = wrap(el); el.append(this); } return this; }; /*\ * Element.prepend [ method ] ** * Prepends the given element to the current one ** - el (Element) element to prepend = (Element) the parent element \*/ elproto.prepend = function (el) { if (el) { if (el.type == "set") { var it = this, first; el.forEach(function (el) { if (first) { first.after(el); } else { it.prepend(el); } first = el; }); return this; } el = wrap(el); var parent = el.parent(); this.node.insertBefore(el.node, this.node.firstChild); this.add && this.add(); el.paper = this.paper; this.parent() && this.parent().add(); parent && parent.add(); } return this; }; /*\ * Element.prependTo [ method ] ** * Prepends the current element to the given one ** - el (Element) parent element to prepend to = (Element) the child element \*/ elproto.prependTo = function (el) { el = wrap(el); el.prepend(this); return this; }; /*\ * Element.before [ method ] ** * Inserts given element before the current one ** - el (Element) element to insert = (Element) the parent element \*/ elproto.before = function (el) { if (el.type == "set") { var it = this; el.forEach(function (el) { var parent = el.parent(); it.node.parentNode.insertBefore(el.node, it.node); parent && parent.add(); }); this.parent().add(); return this; } el = wrap(el); var parent = el.parent(); this.node.parentNode.insertBefore(el.node, this.node); this.parent() && this.parent().add(); parent && parent.add(); el.paper = this.paper; return this; }; /*\ * Element.after [ method ] ** * Inserts given element after the current one ** - el (Element) element to insert = (Element) the parent element \*/ elproto.after = function (el) { el = wrap(el); var parent = el.parent(); if (this.node.nextSibling) { this.node.parentNode.insertBefore(el.node, this.node.nextSibling); } else { this.node.parentNode.appendChild(el.node); } this.parent() && this.parent().add(); parent && parent.add(); el.paper = this.paper; return this; }; /*\ * Element.insertBefore [ method ] ** * Inserts the element after the given one ** - el (Element) element next to whom insert to = (Element) the parent element \*/ elproto.insertBefore = function (el) { el = wrap(el); var parent = this.parent(); el.node.parentNode.insertBefore(this.node, el.node); this.paper = el.paper; parent && parent.add(); el.parent() && el.parent().add(); return this; }; /*\ * Element.insertAfter [ method ] ** * Inserts the element after the given one ** - el (Element) element next to whom insert to = (Element) the parent element \*/ elproto.insertAfter = function (el) { el = wrap(el); var parent = this.parent(); el.node.parentNode.insertBefore(this.node, el.node.nextSibling); this.paper = el.paper; parent && parent.add(); el.parent() && el.parent().add(); return this; }; /*\ * Element.remove [ method ] ** * Removes element from the DOM = (Element) the detached element \*/ elproto.remove = function () { var parent = this.parent(); this.node.parentNode && this.node.parentNode.removeChild(this.node); delete this.paper; this.removed = true; parent && parent.add(); return this; }; /*\ * Element.select [ method ] ** * Gathers the nested @Element matching the given set of CSS selectors ** - query (string) CSS selector = (Element) result of query selection \*/ elproto.select = function (query) { return wrap(this.node.querySelector(query)); }; /*\ * Element.selectAll [ method ] ** * Gathers nested @Element objects matching the given set of CSS selectors ** - query (string) CSS selector = (Set|array) result of query selection \*/ elproto.selectAll = function (query) { var nodelist = this.node.querySelectorAll(query), set = (Snap.set || Array)(); for (var i = 0; i < nodelist.length; i++) { set.push(wrap(nodelist[i])); } return set; }; /*\ * Element.asPX [ method ] ** * Returns given attribute of the element as a `px` value (not %, em, etc.) ** - attr (string) attribute name - value (string) #optional attribute value = (Element) result of query selection \*/ elproto.asPX = function (attr, value) { if (value == null) { value = this.attr(attr); } return +unit2px(this, attr, value); }; // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned instantiates. It's a part of SVG with which ordinary web developers may be least familiar. /*\ * Element.use [ method ] ** * Creates a `` element linked to the current element ** = (Element) the `` element \*/ elproto.use = function () { var use, id = this.node.id; if (!id) { id = this.id; $(this.node, { id: id }); } if (this.type == "linearGradient" || this.type == "radialGradient" || this.type == "pattern") { use = make(this.type, this.node.parentNode); } else { use = make("use", this.node.parentNode); } $(use.node, { "xlink:href": "#" + id }); use.original = this; return use; }; function fixids(el) { var els = el.selectAll("*"), it, url = /^\s*url\(("|'|)(.*)\1\)\s*$/, ids = [], uses = {}; function urltest(it, name) { var val = $(it.node, name); val = val && val.match(url); val = val && val[2]; if (val && val.charAt() == "#") { val = val.substring(1); } else { return; } if (val) { uses[val] = (uses[val] || []).concat(function (id) { var attr = {}; attr[name] = Snap.url(id); $(it.node, attr); }); } } function linktest(it) { var val = $(it.node, "xlink:href"); if (val && val.charAt() == "#") { val = val.substring(1); } else { return; } if (val) { uses[val] = (uses[val] || []).concat(function (id) { it.attr("xlink:href", "#" + id); }); } } for (var i = 0, ii = els.length; i < ii; i++) { it = els[i]; urltest(it, "fill"); urltest(it, "stroke"); urltest(it, "filter"); urltest(it, "mask"); urltest(it, "clip-path"); linktest(it); var oldid = $(it.node, "id"); if (oldid) { $(it.node, {id: it.id}); ids.push({ old: oldid, id: it.id }); } } for (i = 0, ii = ids.length; i < ii; i++) { var fs = uses[ids[i].old]; if (fs) { for (var j = 0, jj = fs.length; j < jj; j++) { fs[j](ids[i].id); } } } } /*\ * Element.clone [ method ] ** * Creates a clone of the element and inserts it after the element ** = (Element) the clone \*/ elproto.clone = function () { var clone = wrap(this.node.cloneNode(true)); if ($(clone.node, "id")) { $(clone.node, {id: clone.id}); } fixids(clone); clone.insertAfter(this); return clone; }; /*\ * Element.toDefs [ method ] ** * Moves element to the shared `` area ** = (Element) the element \*/ elproto.toDefs = function () { var defs = getSomeDefs(this); defs.appendChild(this.node); return this; }; /*\ * Element.toPattern [ method ] ** * Creates a `` element from the current element ** * To create a pattern you have to specify the pattern rect: - x (string|number) - y (string|number) - width (string|number) - height (string|number) = (Element) the `` element * You can use pattern later on as an argument for `fill` attribute: | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ | fill: "none", | stroke: "#bada55", | strokeWidth: 5 | }).pattern(0, 0, 10, 10), | c = paper.circle(200, 200, 100); | c.attr({ | fill: p | }); \*/ elproto.pattern = elproto.toPattern = function (x, y, width, height) { var p = make("pattern", getSomeDefs(this)); if (x == null) { x = this.getBBox(); } if (is(x, "object") && "x" in x) { y = x.y; width = x.width; height = x.height; x = x.x; } $(p.node, { x: x, y: y, width: width, height: height, patternUnits: "userSpaceOnUse", id: p.id, viewBox: [x, y, width, height].join(" ") }); p.node.appendChild(this.node); return p; }; // SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path. // SIERRA Element.marker(): I suggest the method should accept default reference point values. Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where? Couldn't they also be assigned default values? /*\ * Element.marker [ method ] ** * Creates a `` element from the current element ** * To create a marker you have to specify the bounding rect and reference point: - x (number) - y (number) - width (number) - height (number) - refX (number) - refY (number) = (Element) the `` element * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end. \*/ // TODO add usage for markers elproto.marker = function (x, y, width, height, refX, refY) { var p = make("marker", getSomeDefs(this)); if (x == null) { x = this.getBBox(); } if (is(x, "object") && "x" in x) { y = x.y; width = x.width; height = x.height; refX = x.refX || x.cx; refY = x.refY || x.cy; x = x.x; } $(p.node, { viewBox: [x, y, width, height].join(" "), markerWidth: width, markerHeight: height, orient: "auto", refX: refX || 0, refY: refY || 0, id: p.id }); p.node.appendChild(this.node); return p; }; var eldata = {}; /*\ * Element.data [ method ] ** * Adds or retrieves given value associated with given key. (Don’t confuse * with `data-` attributes) * * See also @Element.removeData - key (string) key to store data - value (any) #optional value to store = (object) @Element * or, if value is not specified: = (any) value > Usage | for (var i = 0, i < 5, i++) { | paper.circle(10 + 15 * i, 10, 10) | .attr({fill: "#000"}) | .data("i", i) | .click(function () { | alert(this.data("i")); | }); | } \*/ elproto.data = function (key, value) { var data = eldata[this.id] = eldata[this.id] || {}; if (arguments.length == 0){ eve("snap.data.get." + this.id, this, data, null); return data; } if (arguments.length == 1) { if (Snap.is(key, "object")) { for (var i in key) if (key[has](i)) { this.data(i, key[i]); } return this; } eve("snap.data.get." + this.id, this, data[key], key); return data[key]; } data[key] = value; eve("snap.data.set." + this.id, this, value, key); return this; }; /*\ * Element.removeData [ method ] ** * Removes value associated with an element by given key. * If key is not provided, removes all the data of the element. - key (string) #optional key = (object) @Element \*/ elproto.removeData = function (key) { if (key == null) { eldata[this.id] = {}; } else { eldata[this.id] && delete eldata[this.id][key]; } return this; }; /*\ * Element.outerSVG [ method ] ** * Returns SVG code for the element, equivalent to HTML's `outerHTML`. * * See also @Element.innerSVG = (string) SVG code for the element \*/ /*\ * Element.toString [ method ] ** * See @Element.outerSVG \*/ elproto.outerSVG = elproto.toString = toString(1); /*\ * Element.innerSVG [ method ] ** * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML` = (string) SVG code for the element \*/ elproto.innerSVG = toString(); function toString(type) { return function () { var res = type ? "<" + this.type : "", attr = this.node.attributes, chld = this.node.childNodes; if (type) { for (var i = 0, ii = attr.length; i < ii; i++) { res += " " + attr[i].name + '="' + attr[i].value.replace(/"/g, '\\"') + '"'; } } if (chld.length) { type && (res += ">"); for (i = 0, ii = chld.length; i < ii; i++) { if (chld[i].nodeType == 3) { res += chld[i].nodeValue; } else if (chld[i].nodeType == 1) { res += wrap(chld[i]).toString(); } } type && (res += ""); } else { type && (res += "/>"); } return res; }; } elproto.toDataURL = function () { if (window && window.btoa) { var bb = this.getBBox(), svg = Snap.format('{contents}', { x: +bb.x.toFixed(3), y: +bb.y.toFixed(3), width: +bb.width.toFixed(3), height: +bb.height.toFixed(3), contents: this.outerSVG() }); return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg))); } }; /*\ * Fragment.select [ method ] ** * See @Element.select \*/ Fragment.prototype.select = elproto.select; /*\ * Fragment.selectAll [ method ] ** * See @Element.selectAll \*/ Fragment.prototype.selectAll = elproto.selectAll; }); // Copyright (c) 2016 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var elproto = Element.prototype, is = Snap.is, Str = String, has = "hasOwnProperty"; function slice(from, to, f) { return function (arr) { var res = arr.slice(from, to); if (res.length == 1) { res = res[0]; } return f ? f(res) : res; }; } var Animation = function (attr, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } this.attr = attr; this.dur = ms; easing && (this.easing = easing); callback && (this.callback = callback); }; Snap._.Animation = Animation; /*\ * Snap.animation [ method ] ** * Creates an animation object ** - attr (object) attributes of final destination - duration (number) duration of the animation, in milliseconds - easing (function) #optional one of easing functions of @mina or custom one - callback (function) #optional callback function that fires when animation ends = (object) animation object \*/ Snap.animation = function (attr, ms, easing, callback) { return new Animation(attr, ms, easing, callback); }; /*\ * Element.inAnim [ method ] ** * Returns a set of animations that may be able to manipulate the current element ** = (object) in format: o { o anim (object) animation object, o mina (object) @mina object, o curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished, o status (function) gets or sets the status of the animation, o stop (function) stops the animation o } \*/ elproto.inAnim = function () { var el = this, res = []; for (var id in el.anims) if (el.anims[has](id)) { (function (a) { res.push({ anim: new Animation(a._attrs, a.dur, a.easing, a._callback), mina: a, curStatus: a.status(), status: function (val) { return a.status(val); }, stop: function () { a.stop(); } }); }(el.anims[id])); } return res; }; /*\ * Snap.animate [ method ] ** * Runs generic animation of one number into another with a caring function ** - from (number|array) number or array of numbers - to (number|array) number or array of numbers - setter (function) caring function that accepts one number argument - duration (number) duration, in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function to execute when animation ends = (object) animation object in @mina format o { o id (string) animation id, consider it read-only, o duration (function) gets or sets the duration of the animation, o easing (function) easing, o speed (function) gets or sets the speed of the animation, o status (function) gets or sets the status of the animation, o stop (function) stops the animation o } | var rect = Snap().rect(0, 0, 10, 10); | Snap.animate(0, 10, function (val) { | rect.attr({ | x: val | }); | }, 1000); | // in given context is equivalent to | rect.animate({x: 10}, 1000); \*/ Snap.animate = function (from, to, setter, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } var now = mina.time(), anim = mina(from, to, now, now + ms, mina.time, setter, easing); callback && eve.once("mina.finish." + anim.id, callback); return anim; }; /*\ * Element.stop [ method ] ** * Stops all the animations for the current element ** = (Element) the current element \*/ elproto.stop = function () { var anims = this.inAnim(); for (var i = 0, ii = anims.length; i < ii; i++) { anims[i].stop(); } return this; }; /*\ * Element.animate [ method ] ** * Animates the given attributes of the element ** - attrs (object) key-value pairs of destination attributes - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends = (Element) the current element \*/ elproto.animate = function (attrs, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } if (attrs instanceof Animation) { callback = attrs.callback; easing = attrs.easing; ms = attrs.dur; attrs = attrs.attr; } var fkeys = [], tkeys = [], keys = {}, from, to, f, eq, el = this; for (var key in attrs) if (attrs[has](key)) { if (el.equal) { eq = el.equal(key, Str(attrs[key])); from = eq.from; to = eq.to; f = eq.f; } else { from = +el.attr(key); to = +attrs[key]; } var len = is(from, "array") ? from.length : 1; keys[key] = slice(fkeys.length, fkeys.length + len, f); fkeys = fkeys.concat(from); tkeys = tkeys.concat(to); } var now = mina.time(), anim = mina(fkeys, tkeys, now, now + ms, mina.time, function (val) { var attr = {}; for (var key in keys) if (keys[has](key)) { attr[key] = keys[key](val); } el.attr(attr); }, easing); el.anims[anim.id] = anim; anim._attrs = attrs; anim._callback = callback; eve("snap.animcreated." + el.id, anim); eve.once("mina.finish." + anim.id, function () { eve.off("mina.*." + anim.id); delete el.anims[anim.id]; callback && callback.call(el); }); eve.once("mina.stop." + anim.id, function () { eve.off("mina.*." + anim.id); delete el.anims[anim.id]; }); return el; }; }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var objectToString = Object.prototype.toString, Str = String, math = Math, E = ""; function Matrix(a, b, c, d, e, f) { if (b == null && objectToString.call(a) == "[object SVGMatrix]") { this.a = a.a; this.b = a.b; this.c = a.c; this.d = a.d; this.e = a.e; this.f = a.f; return; } if (a != null) { this.a = +a; this.b = +b; this.c = +c; this.d = +d; this.e = +e; this.f = +f; } else { this.a = 1; this.b = 0; this.c = 0; this.d = 1; this.e = 0; this.f = 0; } } (function (matrixproto) { /*\ * Matrix.add [ method ] ** * Adds the given matrix to existing one - a (number) - b (number) - c (number) - d (number) - e (number) - f (number) * or - matrix (object) @Matrix \*/ matrixproto.add = function (a, b, c, d, e, f) { if (a && a instanceof Matrix) { return this.add(a.a, a.b, a.c, a.d, a.e, a.f); } var aNew = a * this.a + b * this.c, bNew = a * this.b + b * this.d; this.e += e * this.a + f * this.c; this.f += e * this.b + f * this.d; this.c = c * this.a + d * this.c; this.d = c * this.b + d * this.d; this.a = aNew; this.b = bNew; return this; }; /*\ * Matrix.multLeft [ method ] ** * Multiplies a passed affine transform to the left: M * this. - a (number) - b (number) - c (number) - d (number) - e (number) - f (number) * or - matrix (object) @Matrix \*/ Matrix.prototype.multLeft = function (a, b, c, d, e, f) { if (a && a instanceof Matrix) { return this.multLeft(a.a, a.b, a.c, a.d, a.e, a.f); } var aNew = a * this.a + c * this.b, cNew = a * this.c + c * this.d, eNew = a * this.e + c * this.f + e; this.b = b * this.a + d * this.b; this.d = b * this.c + d * this.d; this.f = b * this.e + d * this.f + f; this.a = aNew; this.c = cNew; this.e = eNew; return this; }; /*\ * Matrix.invert [ method ] ** * Returns an inverted version of the matrix = (object) @Matrix \*/ matrixproto.invert = function () { var me = this, x = me.a * me.d - me.b * me.c; return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x); }; /*\ * Matrix.clone [ method ] ** * Returns a copy of the matrix = (object) @Matrix \*/ matrixproto.clone = function () { return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f); }; /*\ * Matrix.translate [ method ] ** * Translate the matrix - x (number) horizontal offset distance - y (number) vertical offset distance \*/ matrixproto.translate = function (x, y) { this.e += x * this.a + y * this.c; this.f += x * this.b + y * this.d; return this; }; /*\ * Matrix.scale [ method ] ** * Scales the matrix - x (number) amount to be scaled, with `1` resulting in no change - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.) - cx (number) #optional horizontal origin point from which to scale - cy (number) #optional vertical origin point from which to scale * Default cx, cy is the middle point of the element. \*/ matrixproto.scale = function (x, y, cx, cy) { y == null && (y = x); (cx || cy) && this.translate(cx, cy); this.a *= x; this.b *= x; this.c *= y; this.d *= y; (cx || cy) && this.translate(-cx, -cy); return this; }; /*\ * Matrix.rotate [ method ] ** * Rotates the matrix - a (number) angle of rotation, in degrees - x (number) horizontal origin point from which to rotate - y (number) vertical origin point from which to rotate \*/ matrixproto.rotate = function (a, x, y) { a = Snap.rad(a); x = x || 0; y = y || 0; var cos = +math.cos(a).toFixed(9), sin = +math.sin(a).toFixed(9); this.add(cos, sin, -sin, cos, x, y); return this.add(1, 0, 0, 1, -x, -y); }; /*\ * Matrix.skewX [ method ] ** * Skews the matrix along the x-axis - x (number) Angle to skew along the x-axis (in degrees). \*/ matrixproto.skewX = function (x) { return this.skew(x, 0); }; /*\ * Matrix.skewY [ method ] ** * Skews the matrix along the y-axis - y (number) Angle to skew along the y-axis (in degrees). \*/ matrixproto.skewY = function (y) { return this.skew(0, y); }; /*\ * Matrix.skew [ method ] ** * Skews the matrix - y (number) Angle to skew along the y-axis (in degrees). - x (number) Angle to skew along the x-axis (in degrees). \*/ matrixproto.skew = function (x, y) { x = x || 0; y = y || 0; x = Snap.rad(x); y = Snap.rad(y); var c = math.tan(x).toFixed(9); var b = math.tan(y).toFixed(9); return this.add(1, b, c, 1, 0, 0); }; /*\ * Matrix.x [ method ] ** * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y - x (number) - y (number) = (number) x \*/ matrixproto.x = function (x, y) { return x * this.a + y * this.c + this.e; }; /*\ * Matrix.y [ method ] ** * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x - x (number) - y (number) = (number) y \*/ matrixproto.y = function (x, y) { return x * this.b + y * this.d + this.f; }; matrixproto.get = function (i) { return +this[Str.fromCharCode(97 + i)].toFixed(4); }; matrixproto.toString = function () { return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")"; }; matrixproto.offset = function () { return [this.e.toFixed(4), this.f.toFixed(4)]; }; function norm(a) { return a[0] * a[0] + a[1] * a[1]; } function normalize(a) { var mag = math.sqrt(norm(a)); a[0] && (a[0] /= mag); a[1] && (a[1] /= mag); } /*\ * Matrix.determinant [ method ] ** * Finds determinant of the given matrix. = (number) determinant \*/ matrixproto.determinant = function () { return this.a * this.d - this.b * this.c; }; /*\ * Matrix.split [ method ] ** * Splits matrix into primitive transformations = (object) in format: o dx (number) translation by x o dy (number) translation by y o scalex (number) scale by x o scaley (number) scale by y o shear (number) shear o rotate (number) rotation in deg o isSimple (boolean) could it be represented via simple transformations \*/ matrixproto.split = function () { var out = {}; // translation out.dx = this.e; out.dy = this.f; // scale and shear var row = [[this.a, this.b], [this.c, this.d]]; out.scalex = math.sqrt(norm(row[0])); normalize(row[0]); out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1]; row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear]; out.scaley = math.sqrt(norm(row[1])); normalize(row[1]); out.shear /= out.scaley; if (this.determinant() < 0) { out.scalex = -out.scalex; } // rotation var sin = row[0][1], cos = row[1][1]; if (cos < 0) { out.rotate = Snap.deg(math.acos(cos)); if (sin < 0) { out.rotate = 360 - out.rotate; } } else { out.rotate = Snap.deg(math.asin(sin)); } out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate); out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate; out.noRotation = !+out.shear.toFixed(9) && !out.rotate; return out; }; /*\ * Matrix.toTransformString [ method ] ** * Returns transform string that represents given matrix = (string) transform string \*/ matrixproto.toTransformString = function (shorter) { var s = shorter || this.split(); if (!+s.shear.toFixed(9)) { s.scalex = +s.scalex.toFixed(4); s.scaley = +s.scaley.toFixed(4); s.rotate = +s.rotate.toFixed(4); return (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) + (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E) + (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E); } else { return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)]; } }; })(Matrix.prototype); /*\ * Snap.Matrix [ method ] ** * Matrix constructor, extend on your own risk. * To create matrices use @Snap.matrix. \*/ Snap.Matrix = Matrix; /*\ * Snap.matrix [ method ] ** * Utility method ** * Returns a matrix based on the given parameters - a (number) - b (number) - c (number) - d (number) - e (number) - f (number) * or - svgMatrix (SVGMatrix) = (object) @Matrix \*/ Snap.matrix = function (a, b, c, d, e, f) { return new Matrix(a, b, c, d, e, f); }; }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var has = "hasOwnProperty", make = Snap._.make, wrap = Snap._.wrap, is = Snap.is, getSomeDefs = Snap._.getSomeDefs, reURLValue = /^url\((['"]?)([^)]+)\1\)$/, $ = Snap._.$, URL = Snap.url, Str = String, separator = Snap._.separator, E = ""; /*\ * Snap.deurl [ method ] ** * Unwraps path from `"url()"`. - value (string) url path = (string) unwrapped path \*/ Snap.deurl = function (value) { var res = String(value).match(reURLValue); return res ? res[2] : value; } // Attributes event handlers eve.on("snap.util.attr.mask", function (value) { if (value instanceof Element || value instanceof Fragment) { eve.stop(); if (value instanceof Fragment && value.node.childNodes.length == 1) { value = value.node.firstChild; getSomeDefs(this).appendChild(value); value = wrap(value); } if (value.type == "mask") { var mask = value; } else { mask = make("mask", getSomeDefs(this)); mask.node.appendChild(value.node); } !mask.node.id && $(mask.node, { id: mask.id }); $(this.node, { mask: URL(mask.id) }); } }); (function (clipIt) { eve.on("snap.util.attr.clip", clipIt); eve.on("snap.util.attr.clip-path", clipIt); eve.on("snap.util.attr.clipPath", clipIt); }(function (value) { if (value instanceof Element || value instanceof Fragment) { eve.stop(); var clip, node = value.node; while (node) { if (node.nodeName === "clipPath") { clip = new Element(node); break; } if (node.nodeName === "svg") { clip = undefined; break; } node = node.parentNode; } if (!clip) { clip = make("clipPath", getSomeDefs(this)); clip.node.appendChild(value.node); !clip.node.id && $(clip.node, { id: clip.id }); } $(this.node, { "clip-path": URL(clip.node.id || clip.id) }); } })); function fillStroke(name) { return function (value) { eve.stop(); if (value instanceof Fragment && value.node.childNodes.length == 1 && (value.node.firstChild.tagName == "radialGradient" || value.node.firstChild.tagName == "linearGradient" || value.node.firstChild.tagName == "pattern")) { value = value.node.firstChild; getSomeDefs(this).appendChild(value); value = wrap(value); } if (value instanceof Element) { if (value.type == "radialGradient" || value.type == "linearGradient" || value.type == "pattern") { if (!value.node.id) { $(value.node, { id: value.id }); } var fill = URL(value.node.id); } else { fill = value.attr(name); } } else { fill = Snap.color(value); if (fill.error) { var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value); if (grad) { if (!grad.node.id) { $(grad.node, { id: grad.id }); } fill = URL(grad.node.id); } else { fill = value; } } else { fill = Str(fill); } } var attrs = {}; attrs[name] = fill; $(this.node, attrs); this.node.style[name] = E; }; } eve.on("snap.util.attr.fill", fillStroke("fill")); eve.on("snap.util.attr.stroke", fillStroke("stroke")); var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i; eve.on("snap.util.grad.parse", function parseGrad(string) { string = Str(string); var tokens = string.match(gradrg); if (!tokens) { return null; } var type = tokens[1], params = tokens[2], stops = tokens[3]; params = params.split(/\s*,\s*/).map(function (el) { return +el == el ? +el : el; }); if (params.length == 1 && params[0] == 0) { params = []; } stops = stops.split("-"); stops = stops.map(function (el) { el = el.split(":"); var out = { color: el[0] }; if (el[1]) { out.offset = parseFloat(el[1]); } return out; }); var len = stops.length, start = 0, j = 0; function seed(i, end) { var step = (end - start) / (i - j); for (var k = j; k < i; k++) { stops[k].offset = +(+start + step * (k - j)).toFixed(2); } j = i; start = end; } len--; for (var i = 0; i < len; i++) if ("offset" in stops[i]) { seed(i, stops[i].offset); } stops[len].offset = stops[len].offset || 100; seed(len, stops[len].offset); return { type: type, params: params, stops: stops }; }); eve.on("snap.util.attr.d", function (value) { eve.stop(); if (is(value, "array") && is(value[0], "array")) { value = Snap.path.toString.call(value); } value = Str(value); if (value.match(/[ruo]/i)) { value = Snap.path.toAbsolute(value); } $(this.node, {d: value}); })(-1); eve.on("snap.util.attr.#text", function (value) { eve.stop(); value = Str(value); var txt = glob.doc.createTextNode(value); while (this.node.firstChild) { this.node.removeChild(this.node.firstChild); } this.node.appendChild(txt); })(-1); eve.on("snap.util.attr.path", function (value) { eve.stop(); this.attr({d: value}); })(-1); eve.on("snap.util.attr.class", function (value) { eve.stop(); this.node.className.baseVal = value; })(-1); eve.on("snap.util.attr.viewBox", function (value) { var vb; if (is(value, "object") && "x" in value) { vb = [value.x, value.y, value.width, value.height].join(" "); } else if (is(value, "array")) { vb = value.join(" "); } else { vb = value; } $(this.node, { viewBox: vb }); eve.stop(); })(-1); eve.on("snap.util.attr.transform", function (value) { this.transform(value); eve.stop(); })(-1); eve.on("snap.util.attr.r", function (value) { if (this.type == "rect") { eve.stop(); $(this.node, { rx: value, ry: value }); } })(-1); eve.on("snap.util.attr.textpath", function (value) { eve.stop(); if (this.type == "text") { var id, tp, node; if (!value && this.textPath) { tp = this.textPath; while (tp.node.firstChild) { this.node.appendChild(tp.node.firstChild); } tp.remove(); delete this.textPath; return; } if (is(value, "string")) { var defs = getSomeDefs(this), path = wrap(defs.parentNode).path(value); defs.appendChild(path.node); id = path.id; path.attr({id: id}); } else { value = wrap(value); if (value instanceof Element) { id = value.attr("id"); if (!id) { id = value.id; value.attr({id: id}); } } } if (id) { tp = this.textPath; node = this.node; if (tp) { tp.attr({"xlink:href": "#" + id}); } else { tp = $("textPath", { "xlink:href": "#" + id }); while (node.firstChild) { tp.appendChild(node.firstChild); } node.appendChild(tp); this.textPath = wrap(tp); } } } })(-1); eve.on("snap.util.attr.text", function (value) { if (this.type == "text") { var i = 0, node = this.node, tuner = function (chunk) { var out = $("tspan"); if (is(chunk, "array")) { for (var i = 0; i < chunk.length; i++) { out.appendChild(tuner(chunk[i])); } } else { out.appendChild(glob.doc.createTextNode(chunk)); } out.normalize && out.normalize(); return out; }; while (node.firstChild) { node.removeChild(node.firstChild); } var tuned = tuner(value); while (tuned.firstChild) { node.appendChild(tuned.firstChild); } } eve.stop(); })(-1); function setFontSize(value) { eve.stop(); if (value == +value) { value += "px"; } this.node.style.fontSize = value; } eve.on("snap.util.attr.fontSize", setFontSize)(-1); eve.on("snap.util.attr.font-size", setFontSize)(-1); eve.on("snap.util.getattr.transform", function () { eve.stop(); return this.transform(); })(-1); eve.on("snap.util.getattr.textpath", function () { eve.stop(); return this.textPath; })(-1); // Markers (function () { function getter(end) { return function () { eve.stop(); var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end); if (style == "none") { return style; } else { return Snap(glob.doc.getElementById(style.match(reURLValue)[1])); } }; } function setter(end) { return function (value) { eve.stop(); var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1); if (value == "" || !value) { this.node.style[name] = "none"; return; } if (value.type == "marker") { var id = value.node.id; if (!id) { $(value.node, {id: value.id}); } this.node.style[name] = URL(id); return; } }; } eve.on("snap.util.getattr.marker-end", getter("end"))(-1); eve.on("snap.util.getattr.markerEnd", getter("end"))(-1); eve.on("snap.util.getattr.marker-start", getter("start"))(-1); eve.on("snap.util.getattr.markerStart", getter("start"))(-1); eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1); eve.on("snap.util.getattr.markerMid", getter("mid"))(-1); eve.on("snap.util.attr.marker-end", setter("end"))(-1); eve.on("snap.util.attr.markerEnd", setter("end"))(-1); eve.on("snap.util.attr.marker-start", setter("start"))(-1); eve.on("snap.util.attr.markerStart", setter("start"))(-1); eve.on("snap.util.attr.marker-mid", setter("mid"))(-1); eve.on("snap.util.attr.markerMid", setter("mid"))(-1); }()); eve.on("snap.util.getattr.r", function () { if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) { eve.stop(); return $(this.node, "rx"); } })(-1); function textExtract(node) { var out = []; var children = node.childNodes; for (var i = 0, ii = children.length; i < ii; i++) { var chi = children[i]; if (chi.nodeType == 3) { out.push(chi.nodeValue); } if (chi.tagName == "tspan") { if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) { out.push(chi.firstChild.nodeValue); } else { out.push(textExtract(chi)); } } } return out; } eve.on("snap.util.getattr.text", function () { if (this.type == "text" || this.type == "tspan") { eve.stop(); var out = textExtract(this.node); return out.length == 1 ? out[0] : out; } })(-1); eve.on("snap.util.getattr.#text", function () { return this.node.textContent; })(-1); eve.on("snap.util.getattr.fill", function (internal) { if (internal) { return; } eve.stop(); var value = eve("snap.util.getattr.fill", this, true).firstDefined(); return Snap(Snap.deurl(value)) || value; })(-1); eve.on("snap.util.getattr.stroke", function (internal) { if (internal) { return; } eve.stop(); var value = eve("snap.util.getattr.stroke", this, true).firstDefined(); return Snap(Snap.deurl(value)) || value; })(-1); eve.on("snap.util.getattr.viewBox", function () { eve.stop(); var vb = $(this.node, "viewBox"); if (vb) { vb = vb.split(separator); return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]); } else { return; } })(-1); eve.on("snap.util.getattr.points", function () { var p = $(this.node, "points"); eve.stop(); if (p) { return p.split(separator); } else { return; } })(-1); eve.on("snap.util.getattr.path", function () { var p = $(this.node, "d"); eve.stop(); return p; })(-1); eve.on("snap.util.getattr.class", function () { return this.node.className.baseVal; })(-1); function getFontSize() { eve.stop(); return this.node.style.fontSize; } eve.on("snap.util.getattr.fontSize", getFontSize)(-1); eve.on("snap.util.getattr.font-size", getFontSize)(-1); }); // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var rgNotSpace = /\S+/g, rgBadSpace = /[\t\r\n\f]/g, rgTrim = /(^\s+|\s+$)/g, Str = String, elproto = Element.prototype; /*\ * Element.addClass [ method ] ** * Adds given class name or list of class names to the element. - value (string) class name or space separated list of class names ** = (Element) original element. \*/ elproto.addClass = function (value) { var classes = Str(value || "").match(rgNotSpace) || [], elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || [], j, pos, clazz, finalValue; if (classes.length) { j = 0; while (clazz = classes[j++]) { pos = curClasses.indexOf(clazz); if (!~pos) { curClasses.push(clazz); } } finalValue = curClasses.join(" "); if (className != finalValue) { elem.className.baseVal = finalValue; } } return this; }; /*\ * Element.removeClass [ method ] ** * Removes given class name or list of class names from the element. - value (string) class name or space separated list of class names ** = (Element) original element. \*/ elproto.removeClass = function (value) { var classes = Str(value || "").match(rgNotSpace) || [], elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || [], j, pos, clazz, finalValue; if (curClasses.length) { j = 0; while (clazz = classes[j++]) { pos = curClasses.indexOf(clazz); if (~pos) { curClasses.splice(pos, 1); } } finalValue = curClasses.join(" "); if (className != finalValue) { elem.className.baseVal = finalValue; } } return this; }; /*\ * Element.hasClass [ method ] ** * Checks if the element has a given class name in the list of class names applied to it. - value (string) class name ** = (boolean) `true` if the element has given class \*/ elproto.hasClass = function (value) { var elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || []; return !!~curClasses.indexOf(value); }; /*\ * Element.toggleClass [ method ] ** * Add or remove one or more classes from the element, depending on either * the class’s presence or the value of the `flag` argument. - value (string) class name or space separated list of class names - flag (boolean) value to determine whether the class should be added or removed ** = (Element) original element. \*/ elproto.toggleClass = function (value, flag) { if (flag != null) { if (flag) { return this.addClass(value); } else { return this.removeClass(value); } } var classes = (value || "").match(rgNotSpace) || [], elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || [], j, pos, clazz, finalValue; j = 0; while (clazz = classes[j++]) { pos = curClasses.indexOf(clazz); if (~pos) { curClasses.splice(pos, 1); } else { curClasses.push(clazz); } } finalValue = curClasses.join(" "); if (className != finalValue) { elem.className.baseVal = finalValue; } return this; }; }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var operators = { "+": function (x, y) { return x + y; }, "-": function (x, y) { return x - y; }, "/": function (x, y) { return x / y; }, "*": function (x, y) { return x * y; } }, Str = String, reUnit = /[a-z]+$/i, reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/; function getNumber(val) { return val; } function getUnit(unit) { return function (val) { return +val.toFixed(3) + unit; }; } eve.on("snap.util.attr", function (val) { var plus = Str(val).match(reAddon); if (plus) { var evnt = eve.nt(), name = evnt.substring(evnt.lastIndexOf(".") + 1), a = this.attr(name), atr = {}; eve.stop(); var unit = plus[3] || "", aUnit = a.match(reUnit), op = operators[plus[1]]; if (aUnit && aUnit == unit) { val = op(parseFloat(a), +plus[2]); } else { a = this.asPX(name); val = op(this.asPX(name), this.asPX(name, plus[2] + unit)); } if (isNaN(a) || isNaN(val)) { return; } atr[name] = val; this.attr(atr); } })(-10); eve.on("snap.util.equal", function (name, b) { var A, B, a = Str(this.attr(name) || ""), el = this, bplus = Str(b).match(reAddon); if (bplus) { eve.stop(); var unit = bplus[3] || "", aUnit = a.match(reUnit), op = operators[bplus[1]]; if (aUnit && aUnit == unit) { return { from: parseFloat(a), to: op(parseFloat(a), +bplus[2]), f: getUnit(aUnit) }; } else { a = this.asPX(name); return { from: a, to: op(a, this.asPX(name, bplus[2] + unit)), f: getNumber }; } } })(-10); }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var proto = Paper.prototype, is = Snap.is; /*\ * Paper.rect [ method ] * * Draws a rectangle ** - x (number) x coordinate of the top left corner - y (number) y coordinate of the top left corner - width (number) width - height (number) height - rx (number) #optional horizontal radius for rounded corners, default is 0 - ry (number) #optional vertical radius for rounded corners, default is rx or 0 = (object) the `rect` element ** > Usage | // regular rectangle | var c = paper.rect(10, 10, 50, 50); | // rectangle with rounded corners | var c = paper.rect(40, 40, 50, 50, 10); \*/ proto.rect = function (x, y, w, h, rx, ry) { var attr; if (ry == null) { ry = rx; } if (is(x, "object") && x == "[object Object]") { attr = x; } else if (x != null) { attr = { x: x, y: y, width: w, height: h }; if (rx != null) { attr.rx = rx; attr.ry = ry; } } return this.el("rect", attr); }; /*\ * Paper.circle [ method ] ** * Draws a circle ** - x (number) x coordinate of the centre - y (number) y coordinate of the centre - r (number) radius = (object) the `circle` element ** > Usage | var c = paper.circle(50, 50, 40); \*/ proto.circle = function (cx, cy, r) { var attr; if (is(cx, "object") && cx == "[object Object]") { attr = cx; } else if (cx != null) { attr = { cx: cx, cy: cy, r: r }; } return this.el("circle", attr); }; var preload = (function () { function onerror() { this.parentNode.removeChild(this); } return function (src, f) { var img = glob.doc.createElement("img"), body = glob.doc.body; img.style.cssText = "position:absolute;left:-9999em;top:-9999em"; img.onload = function () { f.call(img); img.onload = img.onerror = null; body.removeChild(img); }; img.onerror = onerror; body.appendChild(img); img.src = src; }; }()); /*\ * Paper.image [ method ] ** * Places an image on the surface ** - src (string) URI of the source image - x (number) x offset position - y (number) y offset position - width (number) width of the image - height (number) height of the image = (object) the `image` element * or = (object) Snap element object with type `image` ** > Usage | var c = paper.image("apple.png", 10, 10, 80, 80); \*/ proto.image = function (src, x, y, width, height) { var el = this.el("image"); if (is(src, "object") && "src" in src) { el.attr(src); } else if (src != null) { var set = { "xlink:href": src, preserveAspectRatio: "none" }; if (x != null && y != null) { set.x = x; set.y = y; } if (width != null && height != null) { set.width = width; set.height = height; } else { preload(src, function () { Snap._.$(el.node, { width: this.offsetWidth, height: this.offsetHeight }); }); } Snap._.$(el.node, set); } return el; }; /*\ * Paper.ellipse [ method ] ** * Draws an ellipse ** - x (number) x coordinate of the centre - y (number) y coordinate of the centre - rx (number) horizontal radius - ry (number) vertical radius = (object) the `ellipse` element ** > Usage | var c = paper.ellipse(50, 50, 40, 20); \*/ proto.ellipse = function (cx, cy, rx, ry) { var attr; if (is(cx, "object") && cx == "[object Object]") { attr = cx; } else if (cx != null) { attr ={ cx: cx, cy: cy, rx: rx, ry: ry }; } return this.el("ellipse", attr); }; // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier. /*\ * Paper.path [ method ] ** * Creates a `` element using the given string as the path's definition - pathString (string) #optional path string in SVG format * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example: | "M10,20L30,40" * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates. * #

Here is short list of commands available, for more details see SVG path string format or article about path strings at MDN.

# # # # # # # # # # # #
CommandNameParameters
Mmoveto(x y)+
Zclosepath(none)
Llineto(x y)+
Hhorizontal linetox+
Vvertical linetoy+
Ccurveto(x1 y1 x2 y2 x y)+
Ssmooth curveto(x2 y2 x y)+
Qquadratic Bézier curveto(x1 y1 x y)+
Tsmooth quadratic Bézier curveto(x y)+
Aelliptical arc(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
RCatmull-Rom curveto*x1 y1 (x y)+
* * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier. * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point. > Usage | var c = paper.path("M10 10L90 90"); | // draw a diagonal line: | // move to 10,10, line to 90,90 \*/ proto.path = function (d) { var attr; if (is(d, "object") && !is(d, "array")) { attr = d; } else if (d) { attr = {d: d}; } return this.el("path", attr); }; /*\ * Paper.g [ method ] ** * Creates a group element ** - varargs (…) #optional elements to nest within the group = (object) the `g` element ** > Usage | var c1 = paper.circle(), | c2 = paper.rect(), | g = paper.g(c2, c1); // note that the order of elements is different * or | var c1 = paper.circle(), | c2 = paper.rect(), | g = paper.g(); | g.add(c2, c1); \*/ /*\ * Paper.group [ method ] ** * See @Paper.g \*/ proto.group = proto.g = function (first) { var attr, el = this.el("g"); if (arguments.length == 1 && first && !first.type) { el.attr(first); } else if (arguments.length) { el.add(Array.prototype.slice.call(arguments, 0)); } return el; }; /*\ * Paper.svg [ method ] ** * Creates a nested SVG element. - x (number) @optional X of the element - y (number) @optional Y of the element - width (number) @optional width of the element - height (number) @optional height of the element - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height ** = (object) the `svg` element ** \*/ proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) { var attrs = {}; if (is(x, "object") && y == null) { attrs = x; } else { if (x != null) { attrs.x = x; } if (y != null) { attrs.y = y; } if (width != null) { attrs.width = width; } if (height != null) { attrs.height = height; } if (vbx != null && vby != null && vbw != null && vbh != null) { attrs.viewBox = [vbx, vby, vbw, vbh]; } } return this.el("svg", attrs); }; /*\ * Paper.mask [ method ] ** * Equivalent in behaviour to @Paper.g, except it’s a mask. ** = (object) the `mask` element ** \*/ proto.mask = function (first) { var attr, el = this.el("mask"); if (arguments.length == 1 && first && !first.type) { el.attr(first); } else if (arguments.length) { el.add(Array.prototype.slice.call(arguments, 0)); } return el; }; /*\ * Paper.ptrn [ method ] ** * Equivalent in behaviour to @Paper.g, except it’s a pattern. - x (number) @optional X of the element - y (number) @optional Y of the element - width (number) @optional width of the element - height (number) @optional height of the element - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height ** = (object) the `pattern` element ** \*/ proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) { if (is(x, "object")) { var attr = x; } else { attr = {patternUnits: "userSpaceOnUse"}; if (x) { attr.x = x; } if (y) { attr.y = y; } if (width != null) { attr.width = width; } if (height != null) { attr.height = height; } if (vx != null && vy != null && vw != null && vh != null) { attr.viewBox = [vx, vy, vw, vh]; } else { attr.viewBox = [x || 0, y || 0, width || 0, height || 0]; } } return this.el("pattern", attr); }; /*\ * Paper.use [ method ] ** * Creates a element. - id (string) @optional id of element to link * or - id (Element) @optional element to link ** = (object) the `use` element ** \*/ proto.use = function (id) { if (id != null) { if (id instanceof Element) { if (!id.attr("id")) { id.attr({id: Snap._.id(id)}); } id = id.attr("id"); } if (String(id).charAt() == "#") { id = id.substring(1); } return this.el("use", {"xlink:href": "#" + id}); } else { return Element.prototype.use.call(this); } }; /*\ * Paper.symbol [ method ] ** * Creates a element. - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height = (object) the `symbol` element ** \*/ proto.symbol = function (vx, vy, vw, vh) { var attr = {}; if (vx != null && vy != null && vw != null && vh != null) { attr.viewBox = [vx, vy, vw, vh]; } return this.el("symbol", attr); }; /*\ * Paper.text [ method ] ** * Draws a text string ** - x (number) x coordinate position - y (number) y coordinate position - text (string|array) The text string to draw or array of strings to nest within separate `` elements = (object) the `text` element ** > Usage | var t1 = paper.text(50, 50, "Snap"); | var t2 = paper.text(50, 50, ["S","n","a","p"]); | // Text path usage | t1.attr({textpath: "M10,10L100,100"}); | // or | var pth = paper.path("M10,10L100,100"); | t1.attr({textpath: pth}); \*/ proto.text = function (x, y, text) { var attr = {}; if (is(x, "object")) { attr = x; } else if (x != null) { attr = { x: x, y: y, text: text || "" }; } return this.el("text", attr); }; /*\ * Paper.line [ method ] ** * Draws a line ** - x1 (number) x coordinate position of the start - y1 (number) y coordinate position of the start - x2 (number) x coordinate position of the end - y2 (number) y coordinate position of the end = (object) the `line` element ** > Usage | var t1 = paper.line(50, 50, 100, 100); \*/ proto.line = function (x1, y1, x2, y2) { var attr = {}; if (is(x1, "object")) { attr = x1; } else if (x1 != null) { attr = { x1: x1, x2: x2, y1: y1, y2: y2 }; } return this.el("line", attr); }; /*\ * Paper.polyline [ method ] ** * Draws a polyline ** - points (array) array of points * or - varargs (…) points = (object) the `polyline` element ** > Usage | var p1 = paper.polyline([10, 10, 100, 100]); | var p2 = paper.polyline(10, 10, 100, 100); \*/ proto.polyline = function (points) { if (arguments.length > 1) { points = Array.prototype.slice.call(arguments, 0); } var attr = {}; if (is(points, "object") && !is(points, "array")) { attr = points; } else if (points != null) { attr = {points: points}; } return this.el("polyline", attr); }; /*\ * Paper.polygon [ method ] ** * Draws a polygon. See @Paper.polyline \*/ proto.polygon = function (points) { if (arguments.length > 1) { points = Array.prototype.slice.call(arguments, 0); } var attr = {}; if (is(points, "object") && !is(points, "array")) { attr = points; } else if (points != null) { attr = {points: points}; } return this.el("polygon", attr); }; // gradients (function () { var $ = Snap._.$; // gradients' helpers /*\ * Element.stops [ method ] ** * Only for gradients! * Returns array of gradient stops elements. = (array) the stops array. \*/ function Gstops() { return this.selectAll("stop"); } /*\ * Element.addStop [ method ] ** * Only for gradients! * Adds another stop to the gradient. - color (string) stops color - offset (number) stops offset 0..100 = (object) gradient element \*/ function GaddStop(color, offset) { var stop = $("stop"), attr = { offset: +offset + "%" }; color = Snap.color(color); attr["stop-color"] = color.hex; if (color.opacity < 1) { attr["stop-opacity"] = color.opacity; } $(stop, attr); var stops = this.stops(), inserted; for (var i = 0; i < stops.length; i++) { var stopOffset = parseFloat(stops[i].attr("offset")); if (stopOffset > offset) { this.node.insertBefore(stop, stops[i].node); inserted = true; break; } } if (!inserted) { this.node.appendChild(stop); } return this; } function GgetBBox() { if (this.type == "linearGradient") { var x1 = $(this.node, "x1") || 0, x2 = $(this.node, "x2") || 1, y1 = $(this.node, "y1") || 0, y2 = $(this.node, "y2") || 0; return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)); } else { var cx = this.node.cx || .5, cy = this.node.cy || .5, r = this.node.r || 0; return Snap._.box(cx - r, cy - r, r * 2, r * 2); } } /*\ * Element.setStops [ method ] ** * Only for gradients! * Updates stops of the gradient based on passed gradient descriptor. See @Ppaer.gradient - str (string) gradient descriptor part after `()`. = (object) gradient element | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); | g.setStops("#fff-#000-#f00-#fc0"); \*/ function GsetStops(str) { var grad = str, stops = this.stops(); if (typeof str == "string") { grad = eve("snap.util.grad.parse", null, "l(0,0,0,1)" + str).firstDefined().stops; } if (!Snap.is(grad, "array")) { return; } for (var i = 0; i < stops.length; i++) { if (grad[i]) { var color = Snap.color(grad[i].color), attr = {"offset": grad[i].offset + "%"}; attr["stop-color"] = color.hex; if (color.opacity < 1) { attr["stop-opacity"] = color.opacity; } stops[i].attr(attr); } else { stops[i].remove(); } } for (i = stops.length; i < grad.length; i++) { this.addStop(grad[i].color, grad[i].offset); } return this; } function gradient(defs, str) { var grad = eve("snap.util.grad.parse", null, str).firstDefined(), el; if (!grad) { return null; } grad.params.unshift(defs); if (grad.type.toLowerCase() == "l") { el = gradientLinear.apply(0, grad.params); } else { el = gradientRadial.apply(0, grad.params); } if (grad.type != grad.type.toLowerCase()) { $(el.node, { gradientUnits: "userSpaceOnUse" }); } var stops = grad.stops, len = stops.length; for (var i = 0; i < len; i++) { var stop = stops[i]; el.addStop(stop.color, stop.offset); } return el; } function gradientLinear(defs, x1, y1, x2, y2) { var el = Snap._.make("linearGradient", defs); el.stops = Gstops; el.addStop = GaddStop; el.getBBox = GgetBBox; el.setStops = GsetStops; if (x1 != null) { $(el.node, { x1: x1, y1: y1, x2: x2, y2: y2 }); } return el; } function gradientRadial(defs, cx, cy, r, fx, fy) { var el = Snap._.make("radialGradient", defs); el.stops = Gstops; el.addStop = GaddStop; el.getBBox = GgetBBox; if (cx != null) { $(el.node, { cx: cx, cy: cy, r: r }); } if (fx != null && fy != null) { $(el.node, { fx: fx, fy: fy }); } return el; } /*\ * Paper.gradient [ method ] ** * Creates a gradient element ** - gradient (string) gradient descriptor > Gradient Descriptor * The gradient descriptor is an expression formatted as * follows: `()`. The `` can be * either linear or radial. The uppercase `L` or `R` letters * indicate absolute coordinates offset from the SVG surface. * Lowercase `l` or `r` letters indicate coordinates * calculated relative to the element to which the gradient is * applied. Coordinates specify a linear gradient vector as * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`, * `r` and optional `fx`, `fy` specifying a focal point away * from the center of the circle. Specify `` as a list * of dash-separated CSS color values. Each color may be * followed by a custom offset value, separated with a colon * character. > Examples * Linear gradient, relative from top-left corner to bottom-right * corner, from black through red to white: | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); * Linear gradient, absolute from (0, 0) to (100, 100), from black * through red at 25% to white: | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff"); * Radial gradient, relative from the center of the element with radius * half the width, from black to white: | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff"); * To apply the gradient: | paper.circle(50, 50, 40).attr({ | fill: g | }); = (object) the `gradient` element \*/ proto.gradient = function (str) { return gradient(this.defs, str); }; proto.gradientLinear = function (x1, y1, x2, y2) { return gradientLinear(this.defs, x1, y1, x2, y2); }; proto.gradientRadial = function (cx, cy, r, fx, fy) { return gradientRadial(this.defs, cx, cy, r, fx, fy); }; /*\ * Paper.toString [ method ] ** * Returns SVG code for the @Paper = (string) SVG code for the @Paper \*/ proto.toString = function () { var doc = this.node.ownerDocument, f = doc.createDocumentFragment(), d = doc.createElement("div"), svg = this.node.cloneNode(true), res; f.appendChild(d); d.appendChild(svg); Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"}); res = d.innerHTML; f.removeChild(f.firstChild); return res; }; /*\ * Paper.toDataURL [ method ] ** * Returns SVG code for the @Paper as Data URI string. = (string) Data URI string \*/ proto.toDataURL = function () { if (window && window.btoa) { return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this))); } }; /*\ * Paper.clear [ method ] ** * Removes all child nodes of the paper, except . \*/ proto.clear = function () { var node = this.node.firstChild, next; while (node) { next = node.nextSibling; if (node.tagName != "defs") { node.parentNode.removeChild(node); } else { proto.clear.call({node: node}); } node = next; } }; }()); }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var elproto = Element.prototype, is = Snap.is, clone = Snap._.clone, has = "hasOwnProperty", p2s = /,?([a-z]),?/gi, toFloat = parseFloat, math = Math, PI = math.PI, mmin = math.min, mmax = math.max, pow = math.pow, abs = math.abs; function paths(ps) { var p = paths.ps = paths.ps || {}; if (p[ps]) { p[ps].sleep = 100; } else { p[ps] = { sleep: 100 }; } setTimeout(function () { for (var key in p) if (p[has](key) && key != ps) { p[key].sleep--; !p[key].sleep && delete p[key]; } }); return p[ps]; } function box(x, y, width, height) { if (x == null) { x = y = width = height = 0; } if (y == null) { y = x.y; width = x.width; height = x.height; x = x.x; } return { x: x, y: y, width: width, w: width, height: height, h: height, x2: x + width, y2: y + height, cx: x + width / 2, cy: y + height / 2, r1: math.min(width, height) / 2, r2: math.max(width, height) / 2, r0: math.sqrt(width * width + height * height) / 2, path: rectPath(x, y, width, height), vb: [x, y, width, height].join(" ") }; } function toString() { return this.join(",").replace(p2s, "$1"); } function pathClone(pathArray) { var res = clone(pathArray); res.toString = toString; return res; } function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) { if (length == null) { return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y); } else { return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length)); } } function getLengthFactory(istotal, subpath) { function O(val) { return +(+val).toFixed(3); } return Snap._.cacher(function (path, length, onlystart) { if (path instanceof Element) { path = path.attr("d"); } path = path2curve(path); var x, y, p, l, sp = "", subpaths = {}, point, len = 0; for (var 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 (subpath && !subpaths.start) { point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len); sp += [ "C" + O(point.start.x), O(point.start.y), O(point.m.x), O(point.m.y), O(point.x), O(point.y) ]; if (onlystart) {return sp;} subpaths.start = sp; sp = [ "M" + O(point.x), O(point.y) + "C" + O(point.n.x), O(point.n.y), O(point.end.x), O(point.end.y), O(p[5]), O(p[6]) ].join(); len += l; x = +p[5]; y = +p[6]; continue; } if (!istotal && !subpath) { point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len); return point; } } len += l; x = +p[5]; y = +p[6]; } sp += p.shift() + p; } subpaths.end = sp; point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1); return point; }, null, Snap._.clone); } var getTotalLength = getLengthFactory(1), getPointAtLength = getLengthFactory(), getSubpathsAtLength = getLengthFactory(0, 1); function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { var t1 = 1 - t, t13 = pow(t1, 3), t12 = pow(t1, 2), t2 = t * t, t3 = t2 * t, x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y, mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), ax = t1 * p1x + t * c1x, ay = t1 * p1y + t * c1y, cx = t1 * c2x + t * p2x, cy = t1 * c2y + t * p2y, alpha = 90 - math.atan2(mx - nx, my - ny) * 180 / PI; // (mx > nx || my < ny) && (alpha += 180); return { x: x, y: y, m: {x: mx, y: my}, n: {x: nx, y: ny}, start: {x: ax, y: ay}, end: {x: cx, y: cy}, alpha: alpha }; } function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { if (!Snap.is(p1x, "array")) { p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y]; } var bbox = curveDim.apply(null, p1x); return box( bbox.min.x, bbox.min.y, bbox.max.x - bbox.min.x, bbox.max.y - bbox.min.y ); } function isPointInsideBBox(bbox, x, y) { return x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height; } function isBBoxIntersect(bbox1, bbox2) { bbox1 = box(bbox1); bbox2 = box(bbox2); return isPointInsideBBox(bbox2, bbox1.x, bbox1.y) || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y) || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2) || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2) || isPointInsideBBox(bbox1, bbox2.x, bbox2.y) || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y) || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2) || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2) || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x) && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y); } function base3(t, p1, p2, p3, p4) { var 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 bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { if (z == null) { z = 1; } z = z > 1 ? 1 : z < 0 ? 0 : z; var z2 = z / 2, n = 12, Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816], 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], sum = 0; for (var i = 0; i < n; i++) { var 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 getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { return; } var t = 1, step = t / 2, t2 = t - step, l, e = .01; l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); while (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 intersect(x1, y1, x2, y2, x3, y3, x4, y4) { if ( mmax(x1, x2) < mmin(x3, x4) || mmin(x1, x2) > mmax(x3, x4) || mmax(y1, y2) < mmin(y3, y4) || mmin(y1, y2) > mmax(y3, y4) ) { return; } var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); if (!denominator) { return; } var px = nx / denominator, py = ny / denominator, px2 = +px.toFixed(2), py2 = +py.toFixed(2); if ( px2 < +mmin(x1, x2).toFixed(2) || px2 > +mmax(x1, x2).toFixed(2) || px2 < +mmin(x3, x4).toFixed(2) || px2 > +mmax(x3, x4).toFixed(2) || py2 < +mmin(y1, y2).toFixed(2) || py2 > +mmax(y1, y2).toFixed(2) || py2 < +mmin(y3, y4).toFixed(2) || py2 > +mmax(y3, y4).toFixed(2) ) { return; } return {x: px, y: py}; } function inter(bez1, bez2) { return interHelper(bez1, bez2); } function interCount(bez1, bez2) { return interHelper(bez1, bez2, 1); } function interHelper(bez1, bez2, justCount) { var bbox1 = bezierBBox(bez1), bbox2 = bezierBBox(bez2); if (!isBBoxIntersect(bbox1, bbox2)) { return justCount ? 0 : []; } var l1 = bezlen.apply(0, bez1), l2 = bezlen.apply(0, bez2), n1 = ~~(l1 / 8), n2 = ~~(l2 / 8), dots1 = [], dots2 = [], xy = {}, res = justCount ? 0 : []; for (var i = 0; i < n1 + 1; i++) { var p = findDotsAtSegment.apply(0, bez1.concat(i / n1)); dots1.push({x: p.x, y: p.y, t: i / n1}); } for (i = 0; i < n2 + 1; i++) { p = findDotsAtSegment.apply(0, bez2.concat(i / n2)); dots2.push({x: p.x, y: p.y, t: i / n2}); } for (i = 0; i < n1; i++) { for (var j = 0; j < n2; j++) { var di = dots1[i], di1 = dots1[i + 1], dj = dots2[j], dj1 = dots2[j + 1], ci = abs(di1.x - di.x) < .001 ? "y" : "x", cj = abs(dj1.x - dj.x) < .001 ? "y" : "x", is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y); if (is) { if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) { continue; } xy[is.x.toFixed(4)] = is.y.toFixed(4); var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t); if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) { if (justCount) { res++; } else { res.push({ x: is.x, y: is.y, t1: t1, t2: t2 }); } } } } } return res; } function pathIntersection(path1, path2) { return interPathHelper(path1, path2); } function pathIntersectionNumber(path1, path2) { return interPathHelper(path1, path2, 1); } function interPathHelper(path1, path2, justCount) { path1 = path2curve(path1); path2 = path2curve(path2); var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2, res = justCount ? 0 : []; for (var i = 0, ii = path1.length; i < ii; i++) { var pi = path1[i]; if (pi[0] == "M") { x1 = x1m = pi[1]; y1 = y1m = pi[2]; } else { if (pi[0] == "C") { bez1 = [x1, y1].concat(pi.slice(1)); x1 = bez1[6]; y1 = bez1[7]; } else { bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m]; x1 = x1m; y1 = y1m; } for (var j = 0, jj = path2.length; j < jj; j++) { var pj = path2[j]; if (pj[0] == "M") { x2 = x2m = pj[1]; y2 = y2m = pj[2]; } else { if (pj[0] == "C") { bez2 = [x2, y2].concat(pj.slice(1)); x2 = bez2[6]; y2 = bez2[7]; } else { bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m]; x2 = x2m; y2 = y2m; } var intr = interHelper(bez1, bez2, justCount); if (justCount) { res += intr; } else { for (var k = 0, kk = intr.length; k < kk; k++) { intr[k].segment1 = i; intr[k].segment2 = j; intr[k].bez1 = bez1; intr[k].bez2 = bez2; } res = res.concat(intr); } } } } } return res; } function isPointInsidePath(path, x, y) { var bbox = pathBBox(path); return isPointInsideBBox(bbox, x, y) && interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1; } function pathBBox(path) { var pth = paths(path); if (pth.bbox) { return clone(pth.bbox); } if (!path) { return box(); } path = path2curve(path); var x = 0, y = 0, X = [], Y = [], p; for (var i = 0, ii = path.length; i < ii; i++) { p = path[i]; if (p[0] == "M") { x = p[1]; y = p[2]; X.push(x); Y.push(y); } else { var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]); X = X.concat(dim.min.x, dim.max.x); Y = Y.concat(dim.min.y, dim.max.y); x = p[5]; y = p[6]; } } var xmin = mmin.apply(0, X), ymin = mmin.apply(0, Y), xmax = mmax.apply(0, X), ymax = mmax.apply(0, Y), bb = box(xmin, ymin, xmax - xmin, ymax - ymin); pth.bbox = clone(bb); return bb; } function rectPath(x, y, w, h, r) { if (r) { return [ ["M", +x + +r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"] ]; } var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]]; res.toString = toString; return res; } function ellipsePath(x, y, rx, ry, a) { if (a == null && ry == null) { ry = rx; } x = +x; y = +y; rx = +rx; ry = +ry; if (a != null) { var rad = Math.PI / 180, x1 = x + rx * Math.cos(-ry * rad), x2 = x + rx * Math.cos(-a * rad), y1 = y + rx * Math.sin(-ry * rad), y2 = y + rx * Math.sin(-a * rad), res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]]; } else { res = [ ["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"] ]; } res.toString = toString; return res; } var unit2px = Snap._unit2px, getPath = { path: function (el) { return el.attr("path"); }, circle: function (el) { var attr = unit2px(el); return ellipsePath(attr.cx, attr.cy, attr.r); }, ellipse: function (el) { var attr = unit2px(el); return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry); }, rect: function (el) { var attr = unit2px(el); return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry); }, image: function (el) { var attr = unit2px(el); return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height); }, line: function (el) { return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")]; }, polyline: function (el) { return "M" + el.attr("points"); }, polygon: function (el) { return "M" + el.attr("points") + "z"; }, deflt: function (el) { var bbox = el.node.getBBox(); return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); } }; function pathToRelative(pathArray) { var pth = paths(pathArray), lowerCase = String.prototype.toLowerCase; if (pth.rel) { return pathClone(pth.rel); } if (!Snap.is(pathArray, "array") || !Snap.is(pathArray && pathArray[0], "array")) { pathArray = Snap.parsePathString(pathArray); } var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0; if (pathArray[0][0] == "M") { x = pathArray[0][1]; y = pathArray[0][2]; mx = x; my = y; start++; res.push(["M", x, y]); } for (var i = start, ii = pathArray.length; i < ii; i++) { var r = res[i] = [], pa = pathArray[i]; if (pa[0] != lowerCase.call(pa[0])) { r[0] = lowerCase.call(pa[0]); 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).toFixed(3); r[7] = +(pa[7] - y).toFixed(3); break; case "v": r[1] = +(pa[1] - y).toFixed(3); break; case "m": mx = pa[1]; my = pa[2]; default: for (var j = 1, jj = pa.length; j < jj; j++) { r[j] = +(pa[j] - (j % 2 ? x : y)).toFixed(3); } } } else { r = res[i] = []; if (pa[0] == "m") { mx = pa[1] + x; my = pa[2] + y; } for (var k = 0, kk = pa.length; k < kk; k++) { res[i][k] = pa[k]; } } var len = res[i].length; switch (res[i][0]) { case "z": x = mx; y = my; break; case "h": x += +res[i][len - 1]; break; case "v": y += +res[i][len - 1]; break; default: x += +res[i][len - 2]; y += +res[i][len - 1]; } } res.toString = toString; pth.rel = pathClone(res); return res; } function pathToAbsolute(pathArray) { var pth = paths(pathArray); if (pth.abs) { return pathClone(pth.abs); } if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption pathArray = Snap.parsePathString(pathArray); } if (!pathArray || !pathArray.length) { return [["M", 0, 0]]; } var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0, pa0; if (pathArray[0][0] == "M") { x = +pathArray[0][1]; y = +pathArray[0][2]; mx = x; my = y; start++; res[0] = ["M", x, y]; } var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z"; for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { res.push(r = []); pa = pathArray[i]; pa0 = pa[0]; if (pa0 != pa0.toUpperCase()) { r[0] = pa0.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": var dots = [x, y].concat(pa.slice(1)); for (var 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 "O": res.pop(); dots = ellipsePath(x, y, pa[1], pa[2]); dots.push(dots[0]); res = res.concat(dots); break; case "U": res.pop(); res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])); r = ["U"].concat(res[res.length - 1].slice(-2)); break; case "M": mx = +pa[1] + x; my = +pa[2] + y; default: for (j = 1, jj = pa.length; j < jj; j++) { r[j] = +pa[j] + (j % 2 ? x : y); } } } else if (pa0 == "R") { dots = [x, y].concat(pa.slice(1)); res.pop(); res = res.concat(catmullRom2bezier(dots, crz)); r = ["R"].concat(pa.slice(-2)); } else if (pa0 == "O") { res.pop(); dots = ellipsePath(x, y, pa[1], pa[2]); dots.push(dots[0]); res = res.concat(dots); } else if (pa0 == "U") { res.pop(); res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])); r = ["U"].concat(res[res.length - 1].slice(-2)); } else { for (var k = 0, kk = pa.length; k < kk; k++) { r[k] = pa[k]; } } pa0 = pa0.toUpperCase(); if (pa0 != "O") { 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]; default: x = r[r.length - 2]; y = r[r.length - 1]; } } } res.toString = toString; pth.abs = pathClone(res); return res; } function l2c(x1, y1, x2, y2) { return [x1, y1, x2, y2, x2, y2]; } function q2c(x1, y1, ax, ay, x2, y2) { var _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 a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { // for more information of where this math came from visit: // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes var _120 = PI * 120 / 180, rad = PI / 180 * (+angle || 0), res = [], xy, rotate = Snap._.cacher(function (x, y, rad) { var X = x * math.cos(rad) - y * math.sin(rad), Y = x * math.sin(rad) + y * math.cos(rad); return {x: X, y: Y}; }); if (!rx || !ry) { return [x1, y1, x2, y2, x2, y2]; } if (!recursive) { xy = rotate(x1, y1, -rad); x1 = xy.x; y1 = xy.y; xy = rotate(x2, y2, -rad); x2 = xy.x; y2 = xy.y; var cos = math.cos(PI / 180 * angle), sin = math.sin(PI / 180 * angle), x = (x1 - x2) / 2, y = (y1 - y2) / 2; var h = x * x / (rx * rx) + y * y / (ry * ry); if (h > 1) { h = math.sqrt(h); rx = h * rx; ry = h * ry; } var rx2 = rx * rx, ry2 = ry * ry, k = (large_arc_flag == sweep_flag ? -1 : 1) * math.sqrt(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; f1 < 0 && (f1 = PI * 2 + f1); 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]; } var df = f2 - f1; if (abs(df) > _120) { var 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; var 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(","); var newres = []; for (var 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; } } function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { var t1 = 1 - t; return { x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x, y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y }; } // Returns bounding box of cubic bezier curve. // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html // Original version: NISHIO Hirokazu // Modifications: https://github.com/timo22345 function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) { var tvalues = [], bounds = [[], []], a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (var i = 0; i < 2; ++i) { if (i == 0) { b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; } else { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (abs(a) < 1e-12) { if (abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; sqrtb2ac = math.sqrt(b2ac); if (b2ac < 0) { continue; } t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var x, y, j = tvalues.length, jlen = j, mt; while (j--) { t = tvalues[j]; mt = 1 - t; bounds[0][j] = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3; bounds[1][j] = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3; } bounds[0][jlen] = x0; bounds[1][jlen] = y0; bounds[0][jlen + 1] = x3; bounds[1][jlen + 1] = y3; bounds[0].length = bounds[1].length = jlen + 2; return { min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])}, max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])} }; } function path2curve(path, path2) { var pth = !path2 && paths(path); if (!path2 && pth.curve) { return pathClone(pth.curve); } var p = pathToAbsolute(path), p2 = path2 && pathToAbsolute(path2), attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, processPath = function (path, d, pcom) { var nx, ny; if (!path) { return ["C", d.x, d.y, d.x, d.y, d.x, d.y]; } !(path[0] in {T: 1, Q: 1}) && (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") { // In "S" case we have to take into account, if the previous command is C/S. nx = d.x * 2 - d.bx; // And reflect the previous ny = d.y * 2 - d.by; // command's control point relative to the current point. } else { // or some else or nothing nx = d.x; ny = d.y; } path = ["C", nx, ny].concat(path.slice(1)); break; case "T": if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T. d.qx = d.x * 2 - d.qx; // And make a reflection similar d.qy = d.y * 2 - d.qy; // to case "S". } else { // or something else or nothing 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 = function (pp, i) { if (pp[i].length > 7) { pp[i].shift(); var pi = pp[i]; while (pi.length) { pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved p2 && (pcoms2[i] = "A"); // the same as above pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6))); } pp.splice(i, 1); ii = mmax(p.length, p2 && p2.length || 0); } }, fixM = function (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 = mmax(p.length, p2 && p2.length || 0); } }, pcoms1 = [], // path commands of original path p pcoms2 = [], // path commands of original path p2 pfirst = "", // temporary holder for original path command pcom = ""; // holder for previous path command of original path for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) { p[i] && (pfirst = p[i][0]); // save current path command if (pfirst != "C") // C is not saved yet, because it may be result of conversion { pcoms1[i] = pfirst; // Save current path command i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom } p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command // which may produce multiple C:s // so we have to make sure that C is also C in original path fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1 if (p2) { // the same procedures is done to p2 p2[i] && (pfirst = p2[i][0]); if (pfirst != "C") { pcoms2[i] = pfirst; 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); var 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 = toFloat(seg[seglen - 4]) || attrs.x; attrs.by = toFloat(seg[seglen - 3]) || attrs.y; attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x); attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y); attrs2.x = p2 && seg2[seg2len - 2]; attrs2.y = p2 && seg2[seg2len - 1]; } if (!p2) { pth.curve = pathClone(p); } return p2 ? [p, p2] : p; } function mapPath(path, matrix) { if (!matrix) { return path; } var x, y, i, j, ii, jj, pathi; path = path2curve(path); for (i = 0, ii = path.length; i < ii; i++) { pathi = path[i]; for (j = 1, jj = pathi.length; j < jj; j += 2) { x = matrix.x(pathi[j], pathi[j + 1]); y = matrix.y(pathi[j], pathi[j + 1]); pathi[j] = x; pathi[j + 1] = y; } } return path; } // http://schepers.cc/getting-to-the-point function catmullRom2bezier(crp, z) { var d = []; for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { var 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; } // export Snap.path = paths; /*\ * Snap.path.getTotalLength [ method ] ** * Returns the length of the given path in pixels ** - path (string) SVG path string ** = (number) length \*/ Snap.path.getTotalLength = getTotalLength; /*\ * Snap.path.getPointAtLength [ method ] ** * Returns the coordinates of the point located at the given length along the given path ** - path (string) SVG path string - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps ** = (object) representation of the point: o { o x: (number) x coordinate, o y: (number) y coordinate, o alpha: (number) angle of derivative o } \*/ Snap.path.getPointAtLength = getPointAtLength; /*\ * Snap.path.getSubpath [ method ] ** * Returns the subpath of a given path between given start and end lengths ** - path (string) SVG path string - from (number) length, in pixels, from the start of the path to the start of the segment - to (number) length, in pixels, from the start of the path to the end of the segment ** = (string) path string definition for the segment \*/ Snap.path.getSubpath = function (path, from, to) { if (this.getTotalLength(path) - to < 1e-6) { return getSubpathsAtLength(path, from).end; } var a = getSubpathsAtLength(path, to, 1); return from ? getSubpathsAtLength(a, from).end : a; }; /*\ * Element.getTotalLength [ method ] ** * Returns the length of the path in pixels (only works for `path` elements) = (number) length \*/ elproto.getTotalLength = function () { if (this.node.getTotalLength) { return this.node.getTotalLength(); } }; // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length? /*\ * Element.getPointAtLength [ method ] ** * Returns coordinates of the point located at the given length on the given path (only works for `path` elements) ** - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps ** = (object) representation of the point: o { o x: (number) x coordinate, o y: (number) y coordinate, o alpha: (number) angle of derivative o } \*/ elproto.getPointAtLength = function (length) { return getPointAtLength(this.attr("d"), length); }; // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear. /*\ * Element.getSubpath [ method ] ** * Returns subpath of a given element from given start and end lengths (only works for `path` elements) ** - from (number) length, in pixels, from the start of the path to the start of the segment - to (number) length, in pixels, from the start of the path to the end of the segment ** = (string) path string definition for the segment \*/ elproto.getSubpath = function (from, to) { return Snap.path.getSubpath(this.attr("d"), from, to); }; Snap._.box = box; /*\ * Snap.path.findDotsAtSegment [ method ] ** * Utility method ** * Finds dot coordinates on the given cubic beziér curve at the given t - p1x (number) x of the first point of the curve - p1y (number) y of the first point of the curve - c1x (number) x of the first anchor of the curve - c1y (number) y of the first anchor of the curve - c2x (number) x of the second anchor of the curve - c2y (number) y of the second anchor of the curve - p2x (number) x of the second point of the curve - p2y (number) y of the second point of the curve - t (number) position on the curve (0..1) = (object) point information in format: o { o x: (number) x coordinate of the point, o y: (number) y coordinate of the point, o m: { o x: (number) x coordinate of the left anchor, o y: (number) y coordinate of the left anchor o }, o n: { o x: (number) x coordinate of the right anchor, o y: (number) y coordinate of the right anchor o }, o start: { o x: (number) x coordinate of the start of the curve, o y: (number) y coordinate of the start of the curve o }, o end: { o x: (number) x coordinate of the end of the curve, o y: (number) y coordinate of the end of the curve o }, o alpha: (number) angle of the curve derivative at the point o } \*/ Snap.path.findDotsAtSegment = findDotsAtSegment; /*\ * Snap.path.bezierBBox [ method ] ** * Utility method ** * Returns the bounding box of a given cubic beziér curve - p1x (number) x of the first point of the curve - p1y (number) y of the first point of the curve - c1x (number) x of the first anchor of the curve - c1y (number) y of the first anchor of the curve - c2x (number) x of the second anchor of the curve - c2y (number) y of the second anchor of the curve - p2x (number) x of the second point of the curve - p2y (number) y of the second point of the curve * or - bez (array) array of six points for beziér curve = (object) bounding box o { o x: (number) x coordinate of the left top point of the box, o y: (number) y coordinate of the left top point of the box, o x2: (number) x coordinate of the right bottom point of the box, o y2: (number) y coordinate of the right bottom point of the box, o width: (number) width of the box, o height: (number) height of the box o } \*/ Snap.path.bezierBBox = bezierBBox; /*\ * Snap.path.isPointInsideBBox [ method ] ** * Utility method ** * Returns `true` if given point is inside bounding box - bbox (string) bounding box - x (string) x coordinate of the point - y (string) y coordinate of the point = (boolean) `true` if point is inside \*/ Snap.path.isPointInsideBBox = isPointInsideBBox; Snap.closest = function (x, y, X, Y) { var r = 100, b = box(x - r / 2, y - r / 2, r, r), inside = [], getter = X[0].hasOwnProperty("x") ? function (i) { return { x: X[i].x, y: X[i].y }; } : function (i) { return { x: X[i], y: Y[i] }; }, found = 0; while (r <= 1e6 && !found) { for (var i = 0, ii = X.length; i < ii; i++) { var xy = getter(i); if (isPointInsideBBox(b, xy.x, xy.y)) { found++; inside.push(xy); break; } } if (!found) { r *= 2; b = box(x - r / 2, y - r / 2, r, r) } } if (r == 1e6) { return; } var len = Infinity, res; for (i = 0, ii = inside.length; i < ii; i++) { var l = Snap.len(x, y, inside[i].x, inside[i].y); if (len > l) { len = l; inside[i].len = l; res = inside[i]; } } return res; }; /*\ * Snap.path.isBBoxIntersect [ method ] ** * Utility method ** * Returns `true` if two bounding boxes intersect - bbox1 (string) first bounding box - bbox2 (string) second bounding box = (boolean) `true` if bounding boxes intersect \*/ Snap.path.isBBoxIntersect = isBBoxIntersect; /*\ * Snap.path.intersection [ method ] ** * Utility method ** * Finds intersections of two paths - path1 (string) path string - path2 (string) path string = (array) dots of intersection o [ o { o x: (number) x coordinate of the point, o y: (number) y coordinate of the point, o t1: (number) t value for segment of path1, o t2: (number) t value for segment of path2, o segment1: (number) order number for segment of path1, o segment2: (number) order number for segment of path2, o bez1: (array) eight coordinates representing beziér curve for the segment of path1, o bez2: (array) eight coordinates representing beziér curve for the segment of path2 o } o ] \*/ Snap.path.intersection = pathIntersection; Snap.path.intersectionNumber = pathIntersectionNumber; /*\ * Snap.path.isPointInside [ method ] ** * Utility method ** * Returns `true` if given point is inside a given closed path. * * Note: fill mode doesn’t affect the result of this method. - path (string) path string - x (number) x of the point - y (number) y of the point = (boolean) `true` if point is inside the path \*/ Snap.path.isPointInside = isPointInsidePath; /*\ * Snap.path.getBBox [ method ] ** * Utility method ** * Returns the bounding box of a given path - path (string) path string = (object) bounding box o { o x: (number) x coordinate of the left top point of the box, o y: (number) y coordinate of the left top point of the box, o x2: (number) x coordinate of the right bottom point of the box, o y2: (number) y coordinate of the right bottom point of the box, o width: (number) width of the box, o height: (number) height of the box o } \*/ Snap.path.getBBox = pathBBox; Snap.path.get = getPath; /*\ * Snap.path.toRelative [ method ] ** * Utility method ** * Converts path coordinates into relative values - path (string) path string = (array) path string \*/ Snap.path.toRelative = pathToRelative; /*\ * Snap.path.toAbsolute [ method ] ** * Utility method ** * Converts path coordinates into absolute values - path (string) path string = (array) path string \*/ Snap.path.toAbsolute = pathToAbsolute; /*\ * Snap.path.toCubic [ method ] ** * Utility method ** * Converts path to a new path where all segments are cubic beziér curves - pathString (string|array) path string or array of segments = (array) array of segments \*/ Snap.path.toCubic = path2curve; /*\ * Snap.path.map [ method ] ** * Transform the path string with the given matrix - path (string) path string - matrix (object) see @Matrix = (string) transformed path string \*/ Snap.path.map = mapPath; Snap.path.toString = toString; Snap.path.clone = pathClone; }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var mmax = Math.max, mmin = Math.min; // Set var Set = function (items) { this.items = []; this.bindings = {}; this.length = 0; this.type = "set"; if (items) { for (var i = 0, ii = items.length; i < ii; i++) { if (items[i]) { this[this.items.length] = this.items[this.items.length] = items[i]; this.length++; } } } }, setproto = Set.prototype; /*\ * Set.push [ method ] ** * Adds each argument to the current set = (object) original element \*/ setproto.push = function () { var item, len; for (var i = 0, ii = arguments.length; i < ii; i++) { item = arguments[i]; if (item) { len = this.items.length; this[len] = this.items[len] = item; this.length++; } } return this; }; /*\ * Set.pop [ method ] ** * Removes last element and returns it = (object) element \*/ setproto.pop = function () { this.length && delete this[this.length--]; return this.items.pop(); }; /*\ * Set.forEach [ method ] ** * Executes given function for each element in the set * * If the function returns `false`, the loop stops running. ** - callback (function) function to run - thisArg (object) context object for the callback = (object) Set object \*/ setproto.forEach = function (callback, thisArg) { for (var i = 0, ii = this.items.length; i < ii; i++) { if (callback.call(thisArg, this.items[i], i) === false) { return this; } } return this; }; /*\ * Set.animate [ method ] ** * Animates each element in set in sync. * ** - attrs (object) key-value pairs of destination attributes - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends * or - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]` > Usage | // animate all elements in set to radius 10 | set.animate({r: 10}, 500, mina.easein); | // or | // animate first element to radius 10, but second to radius 20 and in different time | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]); = (Element) the current element \*/ setproto.animate = function (attrs, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } if (attrs instanceof Snap._.Animation) { callback = attrs.callback; easing = attrs.easing; ms = easing.dur; attrs = attrs.attr; } var args = arguments; if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) { var each = true; } var begin, handler = function () { if (begin) { this.b = begin; } else { begin = this.b; } }, cb = 0, set = this, callbacker = callback && function () { if (++cb == set.length) { callback.call(this); } }; return this.forEach(function (el, i) { eve.once("snap.animcreated." + el.id, handler); if (each) { args[i] && el.animate.apply(el, args[i]); } else { el.animate(attrs, ms, easing, callbacker); } }); }; /*\ * Set.remove [ method ] ** * Removes all children of the set. * = (object) Set object \*/ setproto.remove = function () { while (this.length) { this.pop().remove(); } return this; }; /*\ * Set.bind [ method ] ** * Specifies how to handle a specific attribute when applied * to a set. * ** - attr (string) attribute name - callback (function) function to run * or - attr (string) attribute name - element (Element) specific element in the set to apply the attribute to * or - attr (string) attribute name - element (Element) specific element in the set to apply the attribute to - eattr (string) attribute on the element to bind the attribute to = (object) Set object \*/ setproto.bind = function (attr, a, b) { var data = {}; if (typeof a == "function") { this.bindings[attr] = a; } else { var aname = b || attr; this.bindings[attr] = function (v) { data[aname] = v; a.attr(data); }; } return this; }; /*\ * Set.attr [ method ] ** * Equivalent of @Element.attr. = (object) Set object \*/ setproto.attr = function (value) { var unbound = {}; for (var k in value) { if (this.bindings[k]) { this.bindings[k](value[k]); } else { unbound[k] = value[k]; } } for (var i = 0, ii = this.items.length; i < ii; i++) { this.items[i].attr(unbound); } return this; }; /*\ * Set.clear [ method ] ** * Removes all elements from the set \*/ setproto.clear = function () { while (this.length) { this.pop(); } }; /*\ * Set.splice [ method ] ** * Removes range of elements from the set ** - index (number) position of the deletion - count (number) number of element to remove - insertion… (object) #optional elements to insert = (object) set elements that were deleted \*/ setproto.splice = function (index, count, insertion) { index = index < 0 ? mmax(this.length + index, 0) : index; count = mmax(0, mmin(this.length - index, count)); var tail = [], todel = [], args = [], i; for (i = 2; i < arguments.length; i++) { args.push(arguments[i]); } for (i = 0; i < count; i++) { todel.push(this[index + i]); } for (; i < this.length - index; i++) { tail.push(this[index + i]); } var arglen = args.length; for (i = 0; i < arglen + tail.length; i++) { this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen]; } i = this.items.length = this.length -= count - arglen; while (this[i]) { delete this[i++]; } return new Set(todel); }; /*\ * Set.exclude [ method ] ** * Removes given element from the set ** - element (object) element to remove = (boolean) `true` if object was found and removed from the set \*/ setproto.exclude = function (el) { for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) { this.splice(i, 1); return true; } return false; }; /*\ * Set.insertAfter [ method ] ** * Inserts set elements after given element. ** - element (object) set will be inserted after this element = (object) Set object \*/ setproto.insertAfter = function (el) { var i = this.items.length; while (i--) { this.items[i].insertAfter(el); } return this; }; /*\ * Set.getBBox [ method ] ** * Union of all bboxes of the set. See @Element.getBBox. = (object) bounding box descriptor. See @Element.getBBox. \*/ setproto.getBBox = function () { var x = [], y = [], x2 = [], y2 = []; for (var i = this.items.length; i--;) if (!this.items[i].removed) { var box = this.items[i].getBBox(); x.push(box.x); y.push(box.y); x2.push(box.x + box.width); y2.push(box.y + box.height); } x = mmin.apply(0, x); y = mmin.apply(0, y); x2 = mmax.apply(0, x2); y2 = mmax.apply(0, y2); return { x: x, y: y, x2: x2, y2: y2, width: x2 - x, height: y2 - y, cx: x + (x2 - x) / 2, cy: y + (y2 - y) / 2 }; }; /*\ * Set.insertAfter [ method ] ** * Creates a clone of the set. ** = (object) New Set object \*/ setproto.clone = function (s) { s = new Set; for (var i = 0, ii = this.items.length; i < ii; i++) { s.push(this.items[i].clone()); } return s; }; setproto.toString = function () { return "Snap\u2018s set"; }; setproto.type = "set"; // export /*\ * Snap.Set [ property ] ** * Set constructor. \*/ Snap.Set = Set; /*\ * Snap.set [ method ] ** * Creates a set and fills it with list of arguments. ** = (object) New Set object | var r = paper.rect(0, 0, 10, 10), | s1 = Snap.set(), // empty set | s2 = Snap.set(r, paper.circle(100, 100, 20)); // prefilled set \*/ Snap.set = function () { var set = new Set; if (arguments.length) { set.push.apply(set, Array.prototype.slice.call(arguments, 0)); } return set; }; }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var names = {}, reUnit = /[%a-z]+$/i, Str = String; names.stroke = names.fill = "colour"; function getEmpty(item) { var l = item[0]; switch (l.toLowerCase()) { case "t": return [l, 0, 0]; case "m": return [l, 1, 0, 0, 1, 0, 0]; case "r": if (item.length == 4) { return [l, 0, item[2], item[3]]; } else { return [l, 0]; } case "s": if (item.length == 5) { return [l, 1, 1, item[3], item[4]]; } else if (item.length == 3) { return [l, 1, 1]; } else { return [l, 1]; } } } function equaliseTransform(t1, t2, getBBox) { t1 = t1 || new Snap.Matrix; t2 = t2 || new Snap.Matrix; t1 = Snap.parseTransformString(t1.toTransformString()) || []; t2 = Snap.parseTransformString(t2.toTransformString()) || []; var maxlength = Math.max(t1.length, t2.length), from = [], to = [], i = 0, j, jj, tt1, tt2; for (; i < maxlength; i++) { tt1 = t1[i] || getEmpty(t2[i]); tt2 = t2[i] || getEmpty(tt1); if (tt1[0] != tt2[0] || tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3]) || tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]) ) { t1 = Snap._.transform2matrix(t1, getBBox()); t2 = Snap._.transform2matrix(t2, getBBox()); from = [["m", t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]]; to = [["m", t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]]; break; } from[i] = []; to[i] = []; for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) { j in tt1 && (from[i][j] = tt1[j]); j in tt2 && (to[i][j] = tt2[j]); } } return { from: path2array(from), to: path2array(to), f: getPath(from) }; } function getNumber(val) { return val; } function getUnit(unit) { return function (val) { return +val.toFixed(3) + unit; }; } function getViewBox(val) { return val.join(" "); } function getColour(clr) { return Snap.rgb(clr[0], clr[1], clr[2], clr[3]); } function getPath(path) { var k = 0, i, ii, j, jj, out, a, b = []; for (i = 0, ii = path.length; i < ii; i++) { out = "["; a = ['"' + path[i][0] + '"']; for (j = 1, jj = path[i].length; j < jj; j++) { a[j] = "val[" + k++ + "]"; } out += a + "]"; b[i] = out; } return Function("val", "return Snap.path.toString.call([" + b + "])"); } function path2array(path) { var out = []; for (var i = 0, ii = path.length; i < ii; i++) { for (var j = 1, jj = path[i].length; j < jj; j++) { out.push(path[i][j]); } } return out; } function isNumeric(obj) { return isFinite(obj); } function arrayEqual(arr1, arr2) { if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) { return false; } return arr1.toString() == arr2.toString(); } Element.prototype.equal = function (name, b) { return eve("snap.util.equal", this, name, b).firstDefined(); }; eve.on("snap.util.equal", function (name, b) { var A, B, a = Str(this.attr(name) || ""), el = this; if (names[name] == "colour") { A = Snap.color(a); B = Snap.color(b); return { from: [A.r, A.g, A.b, A.opacity], to: [B.r, B.g, B.b, B.opacity], f: getColour }; } if (name == "viewBox") { A = this.attr(name).vb.split(" ").map(Number); B = b.split(" ").map(Number); return { from: A, to: B, f: getViewBox }; } if (name == "transform" || name == "gradientTransform" || name == "patternTransform") { if (typeof b == "string") { b = Str(b).replace(/\.{3}|\u2026/g, a); } a = this.matrix; if (!Snap._.rgTransform.test(b)) { b = Snap._.transform2matrix(Snap._.svgTransform2string(b), this.getBBox()); } else { b = Snap._.transform2matrix(b, this.getBBox()); } return equaliseTransform(a, b, function () { return el.getBBox(1); }); } if (name == "d" || name == "path") { A = Snap.path.toCubic(a, b); return { from: path2array(A[0]), to: path2array(A[1]), f: getPath(A[0]) }; } if (name == "points") { A = Str(a).split(Snap._.separator); B = Str(b).split(Snap._.separator); return { from: A, to: B, f: function (val) { return val; } }; } if (isNumeric(a) && isNumeric(b)) { return { from: parseFloat(a), to: parseFloat(b), f: getNumber }; } var aUnit = a.match(reUnit), bUnit = Str(b).match(reUnit); if (aUnit && arrayEqual(aUnit, bUnit)) { return { from: parseFloat(a), to: parseFloat(b), f: getUnit(aUnit) }; } else { return { from: this.asPX(name), to: this.asPX(name, b), f: getNumber }; } }); }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var elproto = Element.prototype, has = "hasOwnProperty", supportsTouch = "createTouch" in glob.doc, events = [ "click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "touchstart", "touchmove", "touchend", "touchcancel" ], touchMap = { mousedown: "touchstart", mousemove: "touchmove", mouseup: "touchend" }, getScroll = function (xy, el) { var name = xy == "y" ? "scrollTop" : "scrollLeft", doc = el && el.node ? el.node.ownerDocument : glob.doc; return doc[name in doc.documentElement ? "documentElement" : "body"][name]; }, preventDefault = function () { this.returnValue = false; }, preventTouch = function () { return this.originalEvent.preventDefault(); }, stopPropagation = function () { this.cancelBubble = true; }, stopTouch = function () { return this.originalEvent.stopPropagation(); }, addEvent = function (obj, type, fn, element) { var realName = supportsTouch && touchMap[type] ? touchMap[type] : type, f = function (e) { var scrollY = getScroll("y", element), scrollX = getScroll("x", element); if (supportsTouch && touchMap[has](type)) { for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) { if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) { var olde = e; e = e.targetTouches[i]; e.originalEvent = olde; e.preventDefault = preventTouch; e.stopPropagation = stopTouch; break; } } } var x = e.clientX + scrollX, y = e.clientY + scrollY; return fn.call(element, e, x, y); }; if (type !== realName) { obj.addEventListener(type, f, false); } obj.addEventListener(realName, f, false); return function () { if (type !== realName) { obj.removeEventListener(type, f, false); } obj.removeEventListener(realName, f, false); return true; }; }, drag = [], dragMove = function (e) { var x = e.clientX, y = e.clientY, scrollY = getScroll("y"), scrollX = getScroll("x"), dragi, j = drag.length; while (j--) { dragi = drag[j]; if (supportsTouch) { var i = e.touches && e.touches.length, touch; while (i--) { touch = e.touches[i]; if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) { x = touch.clientX; y = touch.clientY; (e.originalEvent ? e.originalEvent : e).preventDefault(); break; } } } else { e.preventDefault(); } var node = dragi.el.node, o, next = node.nextSibling, parent = node.parentNode, display = node.style.display; // glob.win.opera && parent.removeChild(node); // node.style.display = "none"; // o = dragi.el.paper.getElementByPoint(x, y); // node.style.display = display; // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node)); // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o); x += scrollX; y += scrollY; eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e); } }, dragUp = function (e) { Snap.unmousemove(dragMove).unmouseup(dragUp); var i = drag.length, dragi; while (i--) { dragi = drag[i]; dragi.el._drag = {}; eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e); eve.off("snap.drag.*." + dragi.el.id); } drag = []; }; /*\ * Element.click [ method ] ** * Adds a click event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unclick [ method ] ** * Removes a click event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.dblclick [ method ] ** * Adds a double click event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.undblclick [ method ] ** * Removes a double click event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mousedown [ method ] ** * Adds a mousedown event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmousedown [ method ] ** * Removes a mousedown event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mousemove [ method ] ** * Adds a mousemove event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmousemove [ method ] ** * Removes a mousemove event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mouseout [ method ] ** * Adds a mouseout event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmouseout [ method ] ** * Removes a mouseout event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mouseover [ method ] ** * Adds a mouseover event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmouseover [ method ] ** * Removes a mouseover event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mouseup [ method ] ** * Adds a mouseup event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmouseup [ method ] ** * Removes a mouseup event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchstart [ method ] ** * Adds a touchstart event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchstart [ method ] ** * Removes a touchstart event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchmove [ method ] ** * Adds a touchmove event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchmove [ method ] ** * Removes a touchmove event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchend [ method ] ** * Adds a touchend event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchend [ method ] ** * Removes a touchend event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchcancel [ method ] ** * Adds a touchcancel event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchcancel [ method ] ** * Removes a touchcancel event handler from the element - handler (function) handler for the event = (object) @Element \*/ for (var i = events.length; i--;) { (function (eventName) { Snap[eventName] = elproto[eventName] = function (fn, scope) { if (Snap.is(fn, "function")) { this.events = this.events || []; this.events.push({ name: eventName, f: fn, unbind: addEvent(this.node || document, eventName, fn, scope || this) }); } else { for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) { try { this.events[i].f.call(this); } catch (e) {} } } return this; }; Snap["un" + eventName] = elproto["un" + eventName] = function (fn) { var events = this.events || [], l = events.length; while (l--) if (events[l].name == eventName && (events[l].f == fn || !fn)) { events[l].unbind(); events.splice(l, 1); !events.length && delete this.events; return this; } return this; }; })(events[i]); } /*\ * Element.hover [ method ] ** * Adds hover event handlers to the element - f_in (function) handler for hover in - f_out (function) handler for hover out - icontext (object) #optional context for hover in handler - ocontext (object) #optional context for hover out handler = (object) @Element \*/ elproto.hover = function (f_in, f_out, scope_in, scope_out) { return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in); }; /*\ * Element.unhover [ method ] ** * Removes hover event handlers from the element - f_in (function) handler for hover in - f_out (function) handler for hover out = (object) @Element \*/ elproto.unhover = function (f_in, f_out) { return this.unmouseover(f_in).unmouseout(f_out); }; var draggable = []; // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture. // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from? // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason. // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start. on start, drag.end. on end and drag.move. on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID? /*\ * Element.drag [ method ] ** * Adds event handlers for an element's drag gesture ** - onmove (function) handler for moving - onstart (function) handler for drag start - onend (function) handler for drag end - mcontext (object) #optional context for moving handler - scontext (object) #optional context for drag start handler - econtext (object) #optional context for drag end handler * Additionaly following `drag` events are triggered: `drag.start.` on start, * `drag.end.` on end and `drag.move.` on every move. When element is dragged over another element * `drag.over.` fires as well. * * Start event and start handler are called in specified context or in context of the element with following parameters: o x (number) x position of the mouse o y (number) y position of the mouse o event (object) DOM event object * Move event and move handler are called in specified context or in context of the element with following parameters: o dx (number) shift by x from the start point o dy (number) shift by y from the start point o x (number) x position of the mouse o y (number) y position of the mouse o event (object) DOM event object * End event and end handler are called in specified context or in context of the element with following parameters: o event (object) DOM event object = (object) @Element \*/ elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) { var el = this; if (!arguments.length) { var origTransform; return el.drag(function (dx, dy) { this.attr({ transform: origTransform + (origTransform ? "T" : "t") + [dx, dy] }); }, function () { origTransform = this.transform().local; }); } function start(e, x, y) { (e.originalEvent || e).preventDefault(); el._drag.x = x; el._drag.y = y; el._drag.id = e.identifier; !drag.length && Snap.mousemove(dragMove).mouseup(dragUp); drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope}); onstart && eve.on("snap.drag.start." + el.id, onstart); onmove && eve.on("snap.drag.move." + el.id, onmove); onend && eve.on("snap.drag.end." + el.id, onend); eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e); } function init(e, x, y) { eve("snap.draginit." + el.id, el, e, x, y); } eve.on("snap.draginit." + el.id, start); el._drag = {}; draggable.push({el: el, start: start, init: init}); el.mousedown(init); return el; }; /* * Element.onDragOver [ method ] ** * Shortcut to assign event handler for `drag.over.` event, where `id` is the element's `id` (see @Element.id) - f (function) handler for event, first argument would be the element you are dragging over \*/ // elproto.onDragOver = function (f) { // f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id); // }; /*\ * Element.undrag [ method ] ** * Removes all drag event handlers from the given element \*/ elproto.undrag = function () { var i = draggable.length; while (i--) if (draggable[i].el == this) { this.unmousedown(draggable[i].init); draggable.splice(i, 1); eve.unbind("snap.drag.*." + this.id); eve.unbind("snap.draginit." + this.id); } !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp); return this; }; }); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var elproto = Element.prototype, pproto = Paper.prototype, rgurl = /^\s*url\((.+)\)/, Str = String, $ = Snap._.$; Snap.filter = {}; /*\ * Paper.filter [ method ] ** * Creates a `` element ** - filstr (string) SVG fragment of filter provided as a string = (object) @Element * Note: It is recommended to use filters embedded into the page inside an empty SVG element. > Usage | var f = paper.filter(''), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ pproto.filter = function (filstr) { var paper = this; if (paper.type != "svg") { paper = paper.paper; } var f = Snap.parse(Str(filstr)), id = Snap._.id(), width = paper.node.offsetWidth, height = paper.node.offsetHeight, filter = $("filter"); $(filter, { id: id, filterUnits: "userSpaceOnUse" }); filter.appendChild(f.node); paper.defs.appendChild(filter); return new Element(filter); }; eve.on("snap.util.getattr.filter", function () { eve.stop(); var p = $(this.node, "filter"); if (p) { var match = Str(p).match(rgurl); return match && Snap.select(match[1]); } }); eve.on("snap.util.attr.filter", function (value) { if (value instanceof Element && value.type == "filter") { eve.stop(); var id = value.node.id; if (!id) { $(value.node, {id: value.id}); id = value.id; } $(this.node, { filter: Snap.url(id) }); } if (!value || value == "none") { eve.stop(); this.node.removeAttribute("filter"); } }); /*\ * Snap.filter.blur [ method ] ** * Returns an SVG markup string for the blur filter ** - x (number) amount of horizontal blur, in pixels - y (number) #optional amount of vertical blur, in pixels = (string) filter representation > Usage | var f = paper.filter(Snap.filter.blur(5, 10)), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ Snap.filter.blur = function (x, y) { if (x == null) { x = 2; } var def = y == null ? x : [x, y]; return Snap.format('\', { def: def }); }; Snap.filter.blur.toString = function () { return this(); }; /*\ * Snap.filter.shadow [ method ] ** * Returns an SVG markup string for the shadow filter ** - dx (number) #optional horizontal shift of the shadow, in pixels - dy (number) #optional vertical shift of the shadow, in pixels - blur (number) #optional amount of blur - color (string) #optional color of the shadow - opacity (number) #optional `0..1` opacity of the shadow * or - dx (number) #optional horizontal shift of the shadow, in pixels - dy (number) #optional vertical shift of the shadow, in pixels - color (string) #optional color of the shadow - opacity (number) #optional `0..1` opacity of the shadow * which makes blur default to `4`. Or - dx (number) #optional horizontal shift of the shadow, in pixels - dy (number) #optional vertical shift of the shadow, in pixels - opacity (number) #optional `0..1` opacity of the shadow = (string) filter representation > Usage | var f = paper.filter(Snap.filter.shadow(0, 2, .3)), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ Snap.filter.shadow = function (dx, dy, blur, color, opacity) { if (opacity == null) { if (color == null) { opacity = blur; blur = 4; color = "#000"; } else { opacity = color; color = blur; blur = 4; } } if (blur == null) { blur = 4; } if (opacity == null) { opacity = 1; } if (dx == null) { dx = 0; dy = 2; } if (dy == null) { dy = dx; } color = Snap.color(color); return Snap.format('', { color: color, dx: dx, dy: dy, blur: blur, opacity: opacity }); }; Snap.filter.shadow.toString = function () { return this(); }; /*\ * Snap.filter.grayscale [ method ] ** * Returns an SVG markup string for the grayscale filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.grayscale = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { a: 0.2126 + 0.7874 * (1 - amount), b: 0.7152 - 0.7152 * (1 - amount), c: 0.0722 - 0.0722 * (1 - amount), d: 0.2126 - 0.2126 * (1 - amount), e: 0.7152 + 0.2848 * (1 - amount), f: 0.0722 - 0.0722 * (1 - amount), g: 0.2126 - 0.2126 * (1 - amount), h: 0.0722 + 0.9278 * (1 - amount) }); }; Snap.filter.grayscale.toString = function () { return this(); }; /*\ * Snap.filter.sepia [ method ] ** * Returns an SVG markup string for the sepia filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.sepia = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { a: 0.393 + 0.607 * (1 - amount), b: 0.769 - 0.769 * (1 - amount), c: 0.189 - 0.189 * (1 - amount), d: 0.349 - 0.349 * (1 - amount), e: 0.686 + 0.314 * (1 - amount), f: 0.168 - 0.168 * (1 - amount), g: 0.272 - 0.272 * (1 - amount), h: 0.534 - 0.534 * (1 - amount), i: 0.131 + 0.869 * (1 - amount) }); }; Snap.filter.sepia.toString = function () { return this(); }; /*\ * Snap.filter.saturate [ method ] ** * Returns an SVG markup string for the saturate filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.saturate = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { amount: 1 - amount }); }; Snap.filter.saturate.toString = function () { return this(); }; /*\ * Snap.filter.hueRotate [ method ] ** * Returns an SVG markup string for the hue-rotate filter ** - angle (number) angle of rotation = (string) filter representation \*/ Snap.filter.hueRotate = function (angle) { angle = angle || 0; return Snap.format('', { angle: angle }); }; Snap.filter.hueRotate.toString = function () { return this(); }; /*\ * Snap.filter.invert [ method ] ** * Returns an SVG markup string for the invert filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.invert = function (amount) { if (amount == null) { amount = 1; } // return Snap.format('', { amount: amount, amount2: 1 - amount }); }; Snap.filter.invert.toString = function () { return this(); }; /*\ * Snap.filter.brightness [ method ] ** * Returns an SVG markup string for the brightness filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.brightness = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { amount: amount }); }; Snap.filter.brightness.toString = function () { return this(); }; /*\ * Snap.filter.contrast [ method ] ** * Returns an SVG markup string for the contrast filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.contrast = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { amount: amount, amount2: .5 - amount / 2 }); }; Snap.filter.contrast.toString = function () { return this(); }; }); // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var box = Snap._.box, is = Snap.is, firstLetter = /^[^a-z]*([tbmlrc])/i, toString = function () { return "T" + this.dx + "," + this.dy; }; /*\ * Element.getAlign [ method ] ** * Returns shift needed to align the element relatively to given element. * If no elements specified, parent `` container will be used. - el (object) @optional alignment element - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string > Usage | el.transform(el.getAlign(el2, "top")); * or | var dy = el.getAlign(el2, "top").dy; \*/ Element.prototype.getAlign = function (el, way) { if (way == null && is(el, "string")) { way = el; el = null; } el = el || this.paper; var bx = el.getBBox ? el.getBBox() : box(el), bb = this.getBBox(), out = {}; way = way && way.match(firstLetter); way = way ? way[1].toLowerCase() : "c"; switch (way) { case "t": out.dx = 0; out.dy = bx.y - bb.y; break; case "b": out.dx = 0; out.dy = bx.y2 - bb.y2; break; case "m": out.dx = 0; out.dy = bx.cy - bb.cy; break; case "l": out.dx = bx.x - bb.x; out.dy = 0; break; case "r": out.dx = bx.x2 - bb.x2; out.dy = 0; break; default: out.dx = bx.cx - bb.cx; out.dy = 0; break; } out.toString = toString; return out; }; /*\ * Element.align [ method ] ** * Aligns the element relatively to given one via transformation. * If no elements specified, parent `` container will be used. - el (object) @optional alignment element - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` = (object) this element > Usage | el.align(el2, "top"); * or | el.align("middle"); \*/ Element.prototype.align = function (el, way) { return this.transform("..." + this.getAlign(el, way)); }; }); // Copyright (c) 2017 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { // Colours are from https://www.materialui.co var red = "#ffebee#ffcdd2#ef9a9a#e57373#ef5350#f44336#e53935#d32f2f#c62828#b71c1c#ff8a80#ff5252#ff1744#d50000", pink = "#FCE4EC#F8BBD0#F48FB1#F06292#EC407A#E91E63#D81B60#C2185B#AD1457#880E4F#FF80AB#FF4081#F50057#C51162", purple = "#F3E5F5#E1BEE7#CE93D8#BA68C8#AB47BC#9C27B0#8E24AA#7B1FA2#6A1B9A#4A148C#EA80FC#E040FB#D500F9#AA00FF", deeppurple = "#EDE7F6#D1C4E9#B39DDB#9575CD#7E57C2#673AB7#5E35B1#512DA8#4527A0#311B92#B388FF#7C4DFF#651FFF#6200EA", indigo = "#E8EAF6#C5CAE9#9FA8DA#7986CB#5C6BC0#3F51B5#3949AB#303F9F#283593#1A237E#8C9EFF#536DFE#3D5AFE#304FFE", blue = "#E3F2FD#BBDEFB#90CAF9#64B5F6#64B5F6#2196F3#1E88E5#1976D2#1565C0#0D47A1#82B1FF#448AFF#2979FF#2962FF", lightblue = "#E1F5FE#B3E5FC#81D4FA#4FC3F7#29B6F6#03A9F4#039BE5#0288D1#0277BD#01579B#80D8FF#40C4FF#00B0FF#0091EA", cyan = "#E0F7FA#B2EBF2#80DEEA#4DD0E1#26C6DA#00BCD4#00ACC1#0097A7#00838F#006064#84FFFF#18FFFF#00E5FF#00B8D4", teal = "#E0F2F1#B2DFDB#80CBC4#4DB6AC#26A69A#009688#00897B#00796B#00695C#004D40#A7FFEB#64FFDA#1DE9B6#00BFA5", green = "#E8F5E9#C8E6C9#A5D6A7#81C784#66BB6A#4CAF50#43A047#388E3C#2E7D32#1B5E20#B9F6CA#69F0AE#00E676#00C853", lightgreen = "#F1F8E9#DCEDC8#C5E1A5#AED581#9CCC65#8BC34A#7CB342#689F38#558B2F#33691E#CCFF90#B2FF59#76FF03#64DD17", lime = "#F9FBE7#F0F4C3#E6EE9C#DCE775#D4E157#CDDC39#C0CA33#AFB42B#9E9D24#827717#F4FF81#EEFF41#C6FF00#AEEA00", yellow = "#FFFDE7#FFF9C4#FFF59D#FFF176#FFEE58#FFEB3B#FDD835#FBC02D#F9A825#F57F17#FFFF8D#FFFF00#FFEA00#FFD600", amber = "#FFF8E1#FFECB3#FFE082#FFD54F#FFCA28#FFC107#FFB300#FFA000#FF8F00#FF6F00#FFE57F#FFD740#FFC400#FFAB00", orange = "#FFF3E0#FFE0B2#FFCC80#FFB74D#FFA726#FF9800#FB8C00#F57C00#EF6C00#E65100#FFD180#FFAB40#FF9100#FF6D00", deeporange = "#FBE9E7#FFCCBC#FFAB91#FF8A65#FF7043#FF5722#F4511E#E64A19#D84315#BF360C#FF9E80#FF6E40#FF3D00#DD2C00", brown = "#EFEBE9#D7CCC8#BCAAA4#A1887F#8D6E63#795548#6D4C41#5D4037#4E342E#3E2723", grey = "#FAFAFA#F5F5F5#EEEEEE#E0E0E0#BDBDBD#9E9E9E#757575#616161#424242#212121", bluegrey = "#ECEFF1#CFD8DC#B0BEC5#90A4AE#78909C#607D8B#546E7A#455A64#37474F#263238"; /*\ * Snap.mui [ property ] ** * Contain Material UI colours. | Snap().rect(0, 0, 10, 10).attr({fill: Snap.mui.deeppurple, stroke: Snap.mui.amber[600]}); # For colour reference: https://www.materialui.co. \*/ Snap.mui = {}; /*\ * Snap.flat [ property ] ** * Contain Flat UI colours. | Snap().rect(0, 0, 10, 10).attr({fill: Snap.flat.carrot, stroke: Snap.flat.wetasphalt}); # For colour reference: https://www.materialui.co. \*/ Snap.flat = {}; function saveColor(colors) { colors = colors.split(/(?=#)/); var color = new String(colors[5]); color[50] = colors[0]; color[100] = colors[1]; color[200] = colors[2]; color[300] = colors[3]; color[400] = colors[4]; color[500] = colors[5]; color[600] = colors[6]; color[700] = colors[7]; color[800] = colors[8]; color[900] = colors[9]; if (colors[10]) { color.A100 = colors[10]; color.A200 = colors[11]; color.A400 = colors[12]; color.A700 = colors[13]; } return color; } Snap.mui.red = saveColor(red); Snap.mui.pink = saveColor(pink); Snap.mui.purple = saveColor(purple); Snap.mui.deeppurple = saveColor(deeppurple); Snap.mui.indigo = saveColor(indigo); Snap.mui.blue = saveColor(blue); Snap.mui.lightblue = saveColor(lightblue); Snap.mui.cyan = saveColor(cyan); Snap.mui.teal = saveColor(teal); Snap.mui.green = saveColor(green); Snap.mui.lightgreen = saveColor(lightgreen); Snap.mui.lime = saveColor(lime); Snap.mui.yellow = saveColor(yellow); Snap.mui.amber = saveColor(amber); Snap.mui.orange = saveColor(orange); Snap.mui.deeporange = saveColor(deeporange); Snap.mui.brown = saveColor(brown); Snap.mui.grey = saveColor(grey); Snap.mui.bluegrey = saveColor(bluegrey); Snap.flat.turquoise = "#1abc9c"; Snap.flat.greensea = "#16a085"; Snap.flat.sunflower = "#f1c40f"; Snap.flat.orange = "#f39c12"; Snap.flat.emerland = "#2ecc71"; Snap.flat.nephritis = "#27ae60"; Snap.flat.carrot = "#e67e22"; Snap.flat.pumpkin = "#d35400"; Snap.flat.peterriver = "#3498db"; Snap.flat.belizehole = "#2980b9"; Snap.flat.alizarin = "#e74c3c"; Snap.flat.pomegranate = "#c0392b"; Snap.flat.amethyst = "#9b59b6"; Snap.flat.wisteria = "#8e44ad"; Snap.flat.clouds = "#ecf0f1"; Snap.flat.silver = "#bdc3c7"; Snap.flat.wetasphalt = "#34495e"; Snap.flat.midnightblue = "#2c3e50"; Snap.flat.concrete = "#95a5a6"; Snap.flat.asbestos = "#7f8c8d"; /*\ * Snap.importMUIColors [ method ] ** * Imports Material UI colours into global object. | Snap.importMUIColors(); | Snap().rect(0, 0, 10, 10).attr({fill: deeppurple, stroke: amber[600]}); # For colour reference: https://www.materialui.co. \*/ Snap.importMUIColors = function () { for (var color in Snap.mui) { if (Snap.mui.hasOwnProperty(color)) { window[color] = Snap.mui[color]; } } }; }); return Snap; })); ================================================ FILE: doc/css/dr.css ================================================ #content section.code { display: block; font-weight: 400; background: #181818; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; } #content section.code pre code { font-size: 14px; } code { font-family: source-code-pro, Menlo, "Arial Unicode MS", sans-serif; } a.dr-hash, a.dr-sourceline { -webkit-transition: opacity 0.2s linear; color: #333; font-family: Menlo, "Arial Unicode MS", sans-serif; margin: 0 0 0 .3em; opacity: 0; text-decoration: none; } h2:hover a.dr-hash, h3:hover a.dr-hash, h4:hover a.dr-hash, h5:hover a.dr-hash, h2:hover a.dr-sourceline, h3:hover a.dr-sourceline, h4:hover a.dr-sourceline, h5:hover a.dr-sourceline { opacity: 1; } .dr-param { float: left; min-width: 8em; } .dr-type { float: left; } .dr-title { float: left; margin: 0 8px 0 0; } .dr-type em, .dr-returns em, .dr-property em { -moz-border-radius: 5px; -webkit-border-radius: 5px; background: #ccc; border-radius: 5px; float: left; font-size: .75em; font-style: normal; font-weight: 700; margin: 0 8px 0 0; min-width: 80px; padding: 2px 5px; text-align: center; } .dr-type em.amp, .dr-returns em.amp, .dr-property em.amp { float: none; background: none; font-size: 1em; font-weight: 400; font-style: italic; margin: 0; padding: 0; min-width: 0; } .dr-property em.dr-type { margin: 4px 16px 0 0; } em.dr-type-string { background: #e1edb1; color: #3d4c00; } em.dr-type-object { background: #edb1b1; color: #4c0000; } em.dr-type-function { background: #cfb1ed; color: #26004c; } em.dr-type-number { background: #b1c9ed; color: #001e4c; } em.dr-type-boolean { background: #b1edc9; color: #004c1e; } em.dr-type-array { background: #edd5b1; color: #4c2d00; } .dr-optional { display: none; } ol.dr-json { background: #ddd; list-style: none; margin: 0 -30px; padding: 16px 30px; line-height: 1.5; } ol.dr-json .dr-json-key { float: left; min-width: 50px; margin-right: 16px; } ol.dr-json .dr-json-description { display: table; } ol.dr-json ol.dr-json { margin: 0; padding: 0 0 0 50px; } #pageNav li.dr-lvl1 a { padding-left: 1em; } #pageNav li.dr-lvl2 a { padding-left: 2em; } #pageNav li.dr-lvl3 a { padding-left: 3em; } #pageNav li.dr-lvl4 a { padding-left: 4em; } #pageNav li.dr-lvl5 a { padding-left: 5em; } #pageNav li.dr-lvl6 a { padding-left: 6em; } #pageNav ol { list-style: none; margin: 0; padding: 0; } ================================================ FILE: doc/css/main.css ================================================ html,body{ margin:0; padding:0; height: 100%; } body { font-family: source-sans-pro, sans-serif; position: relative; -webkit-font-smoothing: antialiased; } body.light { background: #F4F4F4; } body.dark { color: #F0F1F1; background: #4A4D4E; } body.light { color: #181919; } h1 { font-weight: 600; } #wrapper { width: 100%; overflow-x: hidden; background: inherit; position: relative; } #site { width: 100%; position: relative; z-index: 10; background: inherit; left: 0; transition: all 0.2s ease-out; -webkit-transition: all 0.2s ease-out; transform: translate3d(0, 0, 0); -webkit-transform: translate3d(0, 0, 0); } #site:before{ position: absolute; content: ''; left: -4px; height: 100%; width: 4px; background: #3B3E3E; } #site.open { transform: translate3d(250px, 0, 0); -webkit-transform: translate3d(250px, 0, 0); } pre { font-family: source-code-pro, sans-serif; font-size: 12px; } /* Main Header */ #main-header { color: #373435; background: #fff; height: 98px; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px 20px; position: relative; } #main-header hgroup { text-align: center; } #main-header hgroup h1 { font-size: 40px; margin: 5px 0 0; letter-spacing: -.065em; line-height: 1.1em; } #main-header hgroup a { color: #464646; text-decoration: none; } #main-header hgroup a:hover { color: #000; } #main-header hgroup p { font-size: 13px; color: #999; margin: 0; } #main-header nav { display: none; } #slide-menu-button { position: absolute; top: 20px; left: 20px; display: inline-block; vertical-align: top; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; -webkit-background-clip: padding; -moz-background-clip: padding; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -o-text-overflow: ellipsis; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; padding: 0 0.5rem; line-height: 2rem; letter-spacing: 1px; color: #454545; text-shadow: 0 1px #fff; vertical-align: baseline; -webkit-box-shadow: inset 0 1px #fff; box-shadow: inset 0 1px #fff; -webkit-border-radius: 3px; border-radius: 3px; width: 2.6rem; height: 2.6rem; line-height: 2.6rem; border: 1px solid transparent; -webkit-box-shadow: none; box-shadow: none; } #slide-menu:disabled, #slide-menu.is-disabled { opacity: 0.3; cursor: default; pointer-events: none; } #slide-menu-button:active, #slide-menu-button.is-active { color: #454545; text-shadow: 0 1px #fff; background-color: #d3d7d7; border: 1px solid #a5a8a8; -webkit-box-shadow: inset 0 1px rgba(0,0,0,0.12); box-shadow: inset 0 1px rgba(0,0,0,0.12); } #slide-menu-button span { background-repeat: no-repeat; background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCIgWw0KCTwhRU5USVRZIG5zX2V4dGVuZCAiaHR0cDovL25zLmFkb2JlLmNvbS9FeHRlbnNpYmlsaXR5LzEuMC8iPg0KCTwhRU5USVRZIG5zX2FpICJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlSWxsdXN0cmF0b3IvMTAuMC8iPg0KCTwhRU5USVRZIG5zX2dyYXBocyAiaHR0cDovL25zLmFkb2JlLmNvbS9HcmFwaHMvMS4wLyI+DQoJPCFFTlRJVFkgbnNfdmFycyAiaHR0cDovL25zLmFkb2JlLmNvbS9WYXJpYWJsZXMvMS4wLyI+DQoJPCFFTlRJVFkgbnNfaW1yZXAgImh0dHA6Ly9ucy5hZG9iZS5jb20vSW1hZ2VSZXBsYWNlbWVudC8xLjAvIj4NCgk8IUVOVElUWSBuc19zZncgImh0dHA6Ly9ucy5hZG9iZS5jb20vU2F2ZUZvcldlYi8xLjAvIj4NCgk8IUVOVElUWSBuc19jdXN0b20gImh0dHA6Ly9ucy5hZG9iZS5jb20vR2VuZXJpY0N1c3RvbU5hbWVzcGFjZS8xLjAvIj4NCgk8IUVOVElUWSBuc19hZG9iZV94cGF0aCAiaHR0cDovL25zLmFkb2JlLmNvbS9YUGF0aC8xLjAvIj4NCl0+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zOng9IiZuc19leHRlbmQ7IiB4bWxuczppPSImbnNfYWk7IiB4bWxuczpncmFwaD0iJm5zX2dyYXBoczsiDQoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4Ig0KCSB2aWV3Qm94PSIwIDAgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQwIDQwIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxzd2l0Y2g+DQoJPGZvcmVpZ25PYmplY3QgcmVxdWlyZWRFeHRlbnNpb25zPSImbnNfYWk7IiB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIj4NCgkJPGk6cGdmUmVmICB4bGluazpocmVmPSIjYWRvYmVfaWxsdXN0cmF0b3JfcGdmIj4NCgkJPC9pOnBnZlJlZj4NCgk8L2ZvcmVpZ25PYmplY3Q+DQoJPGcgaTpleHRyYW5lb3VzPSJzZWxmIj4NCgkJPGcgb3BhY2l0eT0iMC43Ij4NCgkJCTxnIG9wYWNpdHk9IjAuNzUiPg0KCQkJCTxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjRkZGRkZGIiBkPSJNMzksMTF2LTFjMC0xLjQ3LTAuNDgtMi0yLTJIM2MtMS41MywwLTIsMC41Mi0yLDJ2MQ0KCQkJCQljMCwxLjU1LDAuNTIsMiwyLDJoMzRDMzguNSwxMywzOSwxMi41MiwzOSwxMXoiLz4NCgkJCTwvZz4NCgkJCTxnPg0KCQkJCTxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMzksMTBWOWMwLTEuNDctMC40OC0yLTItMkgzQzEuNDcsNywxLDcuNTIsMSw5djFjMCwxLjU1LDAuNTIsMiwyLDJoMzQNCgkJCQkJQzM4LjUsMTIsMzksMTEuNTIsMzksMTB6Ii8+DQoJCQk8L2c+DQoJCTwvZz4NCgkJPGcgb3BhY2l0eT0iMC43Ij4NCgkJCTxnIG9wYWNpdHk9IjAuNzUiPg0KCQkJCTxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjRkZGRkZGIiBkPSJNMzksMjJ2LTFjMC0xLjQ3LTAuNDgtMi0yLTJIM2MtMS41MywwLTIsMC41Mi0yLDJ2MQ0KCQkJCQljMCwxLjU1LDAuNTIsMiwyLDJoMzRDMzguNSwyNCwzOSwyMy41MiwzOSwyMnoiLz4NCgkJCTwvZz4NCgkJCTxnPg0KCQkJCTxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMzksMjF2LTFjMC0xLjQ3LTAuNDgtMi0yLTJIM2MtMS41MywwLTIsMC41Mi0yLDJ2MWMwLDEuNTUsMC41MiwyLDIsMmgzNA0KCQkJCQlDMzguNSwyMywzOSwyMi41MiwzOSwyMXoiLz4NCgkJCTwvZz4NCgkJPC9nPg0KCQk8ZyBvcGFjaXR5PSIwLjciPg0KCQkJPGcgb3BhY2l0eT0iMC43NSI+DQoJCQkJPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiNGRkZGRkYiIGQ9Ik0zOSwzM3YtMWMwLTEuNDcxLTAuNDgtMi0yLTJIM2MtMS41MywwLTIsMC41Mi0yLDJ2MQ0KCQkJCQljMCwxLjU1LDAuNTIsMiwyLDJoMzRDMzguNSwzNSwzOSwzNC41MiwzOSwzM3oiLz4NCgkJCTwvZz4NCgkJCTxnPg0KCQkJCTxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMzksMzJ2LTFjMC0xLjQ3MS0wLjQ4LTItMi0ySDNjLTEuNTMsMC0yLDAuNTItMiwydjFjMCwxLjU1LDAuNTIsMiwyLDJoMzQNCgkJCQkJQzM4LjUsMzQsMzksMzMuNTIsMzksMzJ6Ii8+DQoJCQk8L2c+DQoJCTwvZz4NCgk8L2c+DQo8L3N3aXRjaD4NCjxpOnBnZiAgaWQ9ImFkb2JlX2lsbHVzdHJhdG9yX3BnZiI+DQoJPCFbQ0RBVEFbDQoJZUp6c3ZlbVNKTG1SSnZnRTlnNitQMXFFSEprS0dnQ0RBY1lkV1pId09IbzRRM2FYa094anBXV2tKSmtWVGVaMEhpVlpXZVJ5bjM3MQorMVFWZ0xsN1pFVWVSVksySzFDVkVhNE9nNW5oVU9qeHFlTHYvbzh2Zi9QRjlkZHZmdmZ3UmJxYUQ5UGYvZDNOMjRkbjc5NjgvZm1CCjFNTXZYcjc4N3R0M2IwSDZ5YTkvZWdqcjFTeVZybjlSdjdLSy8venc5dHNYYjE3L25GOWRKZm55SGxmLzVCZmZQdnZkdzh1WEQ0ZGYKUG52OTlicy9QSHY1OFBhbmg1LzhWTDcvN1l0M0x4K2t4aCtldmZyZGQyOS8vL0QyNnRzLy92Nm5mbXRwNi9iWk8vbCsrVm44V1VpSAorUE01SHI3OEZiNSs5dnFQejc3OTlzWC8rNENicFlwN0hkOTg5L3JyRjY5L2Yzenovd2p4OEVYS2g3UWR2aWp5MVg5LzhldUhiOS96Ci9lMmI1OSs5ZW5qOTdzdTNiNTQvZlB2dHpadVhiOTUrKy9QRHpaK2Z2VDc4NnRudjVadG5oLzliM3VETm53N0hsOCtlLzhja2I1Mi8KdW4veDhrRmU4Tld6ZDRjUTBSZlh2d2p4cStOM0wxNSsvUS9mdmZyZGc3ejZ1cEdjdm1LVC8vU3R0Q1hONG0rUXkxZS9lQ1dVM3p5OApleWZQSlRkRWYvNzY3NC9qWXdpUjVTZi85dXVIMzc5ZzkwdTMvSytmV3JOdjMzeno2dG5iLzhDMWh5K1crU0QvMlIxLysvRHFtNWZTCmUzemRPRi9sd3hmOHQvOXA5ZVF0V09lTHVGYnBsYkllMGx3UEtWZjl2dmZOd3g5ZlBQenA1NGQvZVBQNlFUdmcrdTI3MytnUUxNczgKNjcvNnphKy9rekgrcDljdjNzbURyU0J0MmdPL2V2UDF3MHVwMzY2L2YvbU1MODRTK3I5YTRiZlBaRTY4azdGNzgvSzdkNXhaMWU4ZwpQZnpMWjM5K3dEQUZ2Y0UvZnZQdytyZHYvcG5QK0VVTTBrN2NyclpEWE9TTlF0eVdRNmhzUCtKUnQ3bmRNL1IvdFdrMGhHYThmY3lRCkwyV2Mvdkh0aTkrL2VQM3pMMUtWdms1eDFpSDgrN2N2dnU0akdBN1YvcGZHcitydy8rYi82OVBLaTc5NzkvRGFubDdtemMydmhua3cKWC8zcU4zTFh1OWRmMzd4NWhkNy9GaE5jSnNCcm1Sc3YzL3hldjJ0Lzh4dTUvTHR2OUEzNCtTc1pxQy9mdm5pTk5xZC80RGYxcXk5ZgpmaWRmL2YzYk45OTk4NHZYLy81bStva3U3WDkrZUM3clY4Ynk2OE0vL3U1L3l3ZFpuNXliaDkrK2ZmWmNHcERQcmM3VnN4ZmYvUFM5Cnpjbkx2WDA0NkpkeUpULzY3KysvK3ZiaDMyVlY5Y3VWZXZmNmp3OHYzM3d6Tk5zb3dsVU8vL0xzN1RmZjMvU1hMNSs5ZnZiMlFIcHIKK1pjdi9pamZQSk9lNm0xMzJoTWFsVm55alhRT0wyR1ZreHU4cDhMdzFSTnU5T3pkSDRRQlBieisrdHZXdG43Y1A3alN2cis5M3p6SApISHg3T0w3OTd0cy9ISDc3NXMzTDF1eitxOWE2a1VsRi9iK05lM3pKQzE3LzQydnRvUE03V1lYVE93bi8rWnU3aTlSKy9BN3k1ZDl5CjZ6ZlBYcjU4OGZ1M3o3NzV3NHZubDI1dzRmdDJKLzN1UXliV24xLzk3czNMRjkrKzZ2TnBvSHo1N08yN0Y4OWZQdnptejkrK2UzajEKNU1FOTNIMzlRcGpjSTh2NHZYVis4NmRuNzU3LzRaY3ZmdmYyMmRzWEQrOWRmUmlBZjMveCttdVorNy81N3NXN2g5NUJiMTU5QXlubAo4SnMvUFB2bWdhL3g3Zy8zclBtYjFtRCtTbGoveU55LytPSTlYRCtzaCtQcjRmdS9mL3ZzNnhleW9ZalE5ZmR2WG43OThQcndhN0QzCmFmZEp0cTl5T0g0OS9kczBXd2tzeStIdnZqcStuZjdiTkovK2hKTVNoNUtzTEZZeS8vZXk0djlKZm8ybHRGTG5iVmV1aDNLVWNtUGwKZGloMzgvMDAzM3ZaUGRqNEU0ZVNockxzU3RZeThWY0pxNVRDMzNVbzI2NWNoMk1yTjYwY3c2MFUvM1E3eVljN2ttN0QvVmpHcnVQRAorVThheWpLVUhGZVVTWC9GTXBRYU55bjY3L1ZRamxxbS8rdlNXRDQybW1rM25zc3dxbjFzcFV4dGVKZGhrSFZVeDlHdFV2QWJvMXAzCjQ3c05JeXovVGpiVVB0akhOc3o2RzZOOE40NjIvS3MvODhXeGp0TnVvSk9NSzM1alpESEdLMHZsS0plVEViNjJzaDliR1V2OHVtTzUKUFJsRnYybmlHUFl4UTlGQjhpSGJiTEJrZ0NZYkk0elZqWlZibGpzcjl5aStzdkF5NDFnR2p0SmlIVi9ab1VjdWtudjJUZVRMcjN5OQpqYTl5eXljTzlvUXJwNHc4d3NUNzRqNHhwYlNrTlJWUnRyWjBsSEtiN21XMHc1S1daY2xMV2VxeUxkZkxjYmxkN3BaNzZjaVlVMTd5Cm1rdmU4blUrNXB0OGwrOG5lYUFnczNSWjg3cXVkZDNXNi9XNDNxeTM2NzNNaFNEZHNKUmMxbExMVm83bHB0eVdlNWtub2NhNjFGelgKV3VwV3IrdXgzdGE3ZWkvekpVNWIycFpOMnRucXRtM0g3V2E3Mis1bDBvVHJkTDFjNSt2MXVsNXYxOWZYUnlrMzE3ZlhkNXhKVVY1ZwpPZVpqT2RiakpsOGRqemZIdStPOXpLNHdzYmZUelhLVGIrUUJicmFiNjV2anpjM043YzI5VExiQVVWaHU4KzE2S3c5M1cyKzMyK1B0CnplM2Q3ZjNkTE9PUzdwYTc5YTdjMWJ2dFR1NTFkM04zTzkzZDNkMWpSdHhMaDkzbmUzblRlM24yKzJzcGNzdDd1ZkwrNUVmR2NwYlIKYkovdmhuSTdsSnVoSEhmbGVpaWJsMG4rcVVNcFExbDNKUTlsR1VvYWlrekE2VDYyTXJLd3puem44ZEh2eHAvYlhiblJNdkhYY1NqWApROWwycFE2bERHVWRTcDd1Y2l2THJxU2h4S0dFb2N4ajBTNmZyTy9IcHg5L2JvWnkxTUtWdVJ2SC9lanR4OHhIYXo5RSsyR1JvWmlHCjBkZ1B3YjdqOTkxdDNYelN0K3pQcVhYcHZpUDN2YmZ2c1pOKzJuZk5kTklqMmh2WFZqYVdhcVZZV1ZteWxZVWxXWWtUMTU1dG1jcjYKMi9UM045TjcrYytSNWRyS3hsS3R5TnFlNUovVlNyYXlzQ1FyeW5xZDBRc0g5Ykc4NC9nZE9WNlY0NU01SUJpRW1kMSt5MjYrWnE4Vwo5dVBDZmd2c3B6dDJ6SkVkVVNlK2VlYTc0Z1ZudnRBdFgrQ2FqMXo0aEF1ZlNKNURac3VkZE9hTjhLMXI0VjlWK05ncTNHd1JyaGFGCm04K1RUS1E3NmVVYllYclh3djdxZFJGR21JVWhKdGxSZ3V5dzl6TGl0OUlkeCsxYTJHYmRpakRRTEl3MHlSWVVaRCsrbDRsd0s5MTAKRkc2NzFUb0o0MTJGQVM4MUNTc09zbkhmeXd5NWxUNDhsbXRoMWJVVVlkcFptSGNTRmg1a283K1h1WE1ySFhzVUJyOEpveS9DN3JPdwovU1RNUDZ6ekpOUDRUc2I0UmphR2E5a2dxbXdUYTg2eVlTVFpPSUxJRHZjeTNXNWxMSTZ5cVd5eXVaUmxsVzFta2UwbXlxWXp5N3kvCmsxbHhJeHZSdFd4SWRaS2RhVTFaOXFna2UxVVFXZVZlWnVpdERCOTJVV3lyMkdpemJHN1lpRU9jQTdoc0UyaXRGdXA1TGRhVEJhV2IKT3VRMzdQd2JKUU9WRkVSMjRIeDQ1SFpzaGczNDVmMWlpQjJ4TTRiN2dkVXJTOWhrVW1XWlVFRW0wNldwRkcyUDRPNkRIOTJCQXRjbApkcUhNMWVzN2tlMUYySXBrdHhFaGdQZkdmdGMydTFWbVdiMnczYVcyM1dHenU4WU5lWEdUR0pMSm9NV2xCZ29OZHhRYmdna091WWtPCjEyTzNveGNncDgzMzJxUjhvREIzeTQ5WUxicFdmRU5zdXlpLzU1dnpyWFVZcmltVjNLWGR5RklrREUzb1VRbFBCUitWNUc0b2lOOUoKMDhHRWJIMGRsNXBOU3FaYXdhYThvZDRNRzJGdjZNVVhlMElmZVJ3b0hhU2RzRUJKSWNuSUY1a0JSODZGRStuQXV5TGJCdUFjWitRNQpmYXAwdmpOeUh2SWU3ZUlMN0dqUGpiNlBGMm5YbjZ6R1QxNk14eWIrZXVtS1ROMnBPTVhVSGkxNXB4bVp0alRaSC82akF2bE9SOTJyCllMN0hOQVd0U2Zvc3gybW4yTjFRZ25hbEFMKzNrMUpQU2xjaTlhOTFjald6bGVXc3BMTVNMeFQ3bVhhYTdwbENoeFUzQ21udDkrM0YKSW54aWFwcjJqU2xqUjlYT0xwYnQwVkpkNzVzR0pWL0wrc1RTVmNvOGxtbi9zUmtiK3QvbkpUMVNPQ21tT1Q2eFhPcmdDMlY2WXNVbgovMXpTM1QvcDU3TTE2UHRpYUQycU1xcUwyYmRXVk1KeW80UWJLYXJwd203RXNKVSttZHJzeTl2TkluMzZuNjVtbDFCOTlZNEw5cWpyCjJGWDY4OVZhbWdsZ3RaS2JnYURiaHRLNFFLZnZYNW55VnpOTGRQUFV5WEwwQlhrN0RVYXQwY2gxdkxnY2p6dlQyUG1DTkdQSzZmcXMKT3l0YnQ4NjRoZWJTRW0yZnA3T1ZlYm9JenhmbGU4dDBaakM4dEZMZnQzNVBsdWMwMkNBL2VRMi9ieDMvOVpmZFoyL3drb2c2YWovbgorby9LSStmNmowc2hNM1RvTzBvYm0ybCtxOGtic1drLzk0UEVVWFlTUnh3MG9CdlJnYWdCVGFJQ1FRbFNGUWhLMEV3TlNIV2dHeHAvClZBK3FUUTlLcGdkQkU3b1JMZWhJUGFqU2xMUk1WSVVDRjhjOXRlSmIzRUd1cjZJSnFTNEViU2lxTmtSOTZOYjBvU1Axb1VwOUNCb1IKT0ZlWVRDbTZwNFlMdFVnVkk2aEdsYW9SbEtPRnlsSGdVcnFuaG5SRERRazZFclNrSW5kRlZWR0VKbEdVRXRsaG9Hbm1qdXJTcmFoTApVSmlnTWtGcGd0cFVsa3pGQ2FwVElxZWFxVDdkbTJKOTFES0ozQWJKRGJKYm9meTJvbjdDVDZEODFMazRaRm9kaXk2bHFXUldxVUNoCkxGUGoxR2RTMXlCanVTVlZmNDhNZUJTUktBOU5neEMwWTZwbmpITW51ZXg0NUk0alRqdVpCT1dFdDExa2ErK1JJTTRsaHFleGlVZloKeCtkY3g2WXRhaG5OVk9QUDNpdzNtQnIzdGpEZlZ5ZjdJKzVLMnBXOTJTMmZsSFZYeXJRejVaV2RrYTlTVWRxWDY1TnlQQzFUczNsNQp1VDByNXovMzU4WG4yRFR1MVBmaFFva1hTN3BZekpKM1h2SWpaWDIwTkZ2dXREUHNGcHFiMzFlMjd5blgwODZjZkxrY24xakkzS2VkCjVmcDk1ZlpwWmJwQXZEU0lUeTdUbzE5OTVNLzVKdnF4RFpuaFptN3FaelNQaFpiRmlwb3ozYnhackxqMWM3Tml0dEhKVEtYKzQ3eWgKTHhzZkQrZXNyaUs3Sk85V1dyWFpMaE9OUmxyV1Z0ek1XMXZackZ5M2NyekFvK1JuT21OUWwxaFROOTFmNWtZREg1b2VZVUtQY1ovSAp1TTdBYVk2MDRUek9hOTdEWTNaTXhSakw5RjZlY29HVGZCOExtZDdQTjU3QU1rNVl3elJ5aVk5bURjTjZmNHd4UEpFTm5LLzh4OWZ4Cis4dGZhQjEva0ExMVowVVZrZTFHaloxMHdrTE9Tc3N5aWFTM0RyN1lvM0FDOWNmTzVwR0ZUemJUSzF0Rm9IVFA3QzE4czdLNXFITTIKVVJTRmc3Wk1sRS9kU3dzLzdSMDl0ZXFyVFpBdTZhL0ZEM3kyMXhTQTRiZkZBbExmTGJ5M0VDenB3WjNveEsyUXRPbklQUW8zdW0zdQozRm5FUDBpUzhPa3U4bmlRa0F2RjhrMUU5R3Y2ZDIrRVg5eVpsM2UrRHBQSThaSHlQTnk5Y1BpdTE0V1Mvalk0ZnVuNmxZTHBDRUV3CjBNQ1k2QWFHSXhnQ2VhRTd1RTdIalQ1aDlRcmZrQmRoYWQvVFB6dzNsdXZNMXRtc3M5Zk9WbzJqVG8yVmRoYnFyTE16emM0ck80L3MKdkhIa2lMZFQ0NE1qLyt0OGI4L3JSaFkzc3JXQm1VMERIOXV6cmhObWRVSHlPZWRIZzB4eldXNTVqTXVjaUIxam1kN0hOYjZYVFR4eApnLy9ZZFd5RzYzK2JSbEJ4T0VUNWF6M1UrV3FSNVhJbzZVb1dTRVRWMzB4Lzk5VlRxaDYvUFcveEtrdkZQQjlxdUJJOWNIMjB1ZE42CmFPc3FyR1ZiZ253Wm9OMUdSeW5uV3VNaWY1UWM3ZXNZOHJvZFVFRWJpVmRwemt1NzJTYzN4S2VaUlRQTnE5U2FaWkd2dUU2VWExRysKMmRhMjVIa2pkbHFZNHBMbGo0amZZV2dyWGMzQ1Z2cERmYWIyOU5uV1dSZ3Eyc3Boem9YWFZXSEUycGFvaUZYYmo4SlIwWmFNVzk1MgpiZVY1M29abit6enRmZndveG5KVlU5clFVczFsKzhTaFBHM3R3a3kxSjkrMksrSG5qMDNUZlNWcDVYamJJSTZPVkx3TVh2ekh0OC8vCjhPTHJuMDcrQjREOURiSjR0YTVCOWttTVk0cWxyQVF3eXVKaTkrSnJqUHh1MmRvVkI3OUM0eERzaW9OZFljdFVIbGlVOWVHVlB1SmkKN2JDblhvaHVXdGFybERDOVArTEcrK3MvcEp0Lzh4OS9sajdHdjNMaG9oMzgzM3ovdWJkZEo5cCtrODJmV1d4M3ViWU41WFowUE50bQpzUmdXU25ZRFNrS3lGd1REZVMwVUVZRDBPbEkwdUtkVlNFV0NsY0xBTmNXQU8rNFNBSHBoMzljOS81cld2dHRUWUtPQ0dOMGpwUFp0Ck5aTmpMNTROQUtvdVhVZHcwaXZjUUhINU1hemtwWi9IMGJDajhUcWJVWVpXUWtWT0VEdlIwQk9uaHNKaUtBckZVUT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJR0pLQ3BzSTdBMVBjWkhYcnZoOGVNcUJEbm9RTkdYMnpUUkFLQmgwWXdRTWRQbkNqMHM0QUlWaWFkTE01ak9CV3h3bGlyQ0VKSWIxVwpSeEZTWmxVTVlhV2NDdmtVMG1tV2ZvQkllaU9UYmFZWW1nMHdlRnZVUDQ1dU5keEZSMTdzZ0J0bU81eWIxL09HMWtFMUJ4WXoreTNtCk1RaG1DSDh5WU9aSmFCa0ZCSFR6dHpyY28yS2JETk4wMjNCTTE0WmVjbk40TnBpU3dwTm1Da1YzT3kvNkFCUnRVRkUxbjZvL1BEV3QKSWxPeldLbGRGR29ZbGRiZ2Erb1pSeG9XYmllS2xmZFVPTHJLb2VoUGpKdnFIYXA1ZE4yRDJvZmhRUlVSQ3YwREdzZzZVUW1wVkVKYwpEYm5kcVNJbnlvanBJaGg2UjQxU0gzR05aRHBSU1RBbk5nT1MzbENxdm10dzBraUZ4RUdsbzBaeVE0M2tEZ0xwWkFwSkdoU1NZc3JJCmtjcklMWlVSQlp4MlBXUVo5QkRvSUUwTG1Xamo2R3JJdVNLU1RSR2hLdEwwa0NOMUVXb2l5b2NNY0dmNDV3R2tTRS9oK2Q5MzdlODkKMWF6bWs4R281WVA1SXUrNlY5TC9Hbjgza1BVSXFsZUR2RUdzUTdQUXA0YXc3Ny9UN3ZkaTdOZmgyTG5oN3RYNEwxeHgyaUh2SFg5Zgo3Ri8xR3VpLzZzenQvMTd2ZkF5M0J0VTIvRDJCUDNBVUQxanRqdFJPNXNKWURhdTltaTlhUFIwZFZFOC95RFRncy91UzYwc3RFUkJ6CnVzdzRZd2lja2RYLy9qMW05QkdjK2dzZTh5Nll0M0owTVlUbWxoZ2RGT2NPMFhPZjZ1SmVqbE5IN1BtbjhmZnAzeU10ZThSQUR3MVoKSHNWdVBJN3NTTHUvMHpSOFdFNWNOcU1qNTdMUHVuL2ZQTmpUbWE5SCs2NjdsSHV2OWg3dWc5QTlNRHRrNnZFQ01yVVlLTFhEVVIySQphaEJVNWZUYzRXV1BuOGdicnJuUjYxWmZhTXpJNUNmSnRuemQ5UGN1MGV2bUVpMWtWdVlXblNnRmROL29MUjlPdHozZCtIenJhNXRmCjIvNThBeVJtbEp1Z2JJT1Q3WVBqVGpqdWhTTjJkSThlM2VOSEc0SjBVc0hIUEtTbkdOTEhVS1R2d1pGT0EzVHRvOEJyUWJueU9WcDgKS044UEdPK1diN09GVHcwbmZZNlVMczJvdmtkTEQzaHBLd05jZWpwRFROL3VFTk9QejhwcS9vTjFQemVuQVMwOVlxWG5ObEh2emE2awpiNmJ0SDl1YzFWbTdjYS9qM0oxcytxN2NDdnNrVGdZSlZGNnVybHlmMEhmV1hUZURyMThudDVUSlpuaXhXYjVTV2FDSW9xeVhIRnczCkN2VUw2N1MvNDlUbjVGY014Q1VuM1dsSXhHbTU3QW1NcDhiOXM2Q0ppOEVUZXp6L1dTREZkQjVOY1FMM3Z4eFVjUjVZWVhFQTA4WG8KaW5MaXNWMVAvTG1QQjFtazZkRTRpL0NlbGZQWXVwR1ZNMTFjUE1mQm1kVFh6N2lHaHBXMHM5NWRiU0prcGt6RkdlSTdyQjdDS0NwMAo2amtJczhxMGpZZ1dYVmVvMTNtTElaZWRHVU5xZHhQZjUycVBWcCs2cmx1Z2RyK0pJRnpOa0NJZm02VkdMVnpkWnJSZWlmeFUrOU44CmRBdThmMW1Tc0Q3WXBsSVIxbS9YaGxCRTdzY2I0aSs4YWwyRVp3NHZrVmU1TkhSRDVTYzNwRGF3T3FmRWZzeHBxelQ2eVQ1YlUwKzcKVUhBOWpHdmF1OWJNZHBYelBCcFJQcjJsRHpLbmZQZnExY05iV0ZUMER4ckJ6R28xU0g0dWtJMVMxUmhHV1Jwa3o3Rit4eFlFZStlaQp1NEtBSWRHNmVKMmJERjJhcE53akdUMFVkUlNGZTlncDFiMXBpRnFzRGFYWkEwb1YwYW40enNkMXpreXRrNEx3TkVqQ3FuSnVnMFI4CnRLM3N6alZQMFQySlhqSUFLTnVoOW9rTm5YcmlSTUJUSWZ4SkZWRlhSbysrUHhwdmNnK0ZDbnZSY05lbURHZHJOZWVKMmlxaFhMUkQKVkZOWnRkZ3U1Y0ZEdVczR3piUHNJS0tHSVowYVdEMjFrbHZwUDZXVjJzcDFLOGRlcHZWbUtOM2J2bmU1akRET3NDdHBLT3k3cVN5dAo1RjA1LzZrblpidFFycWR5ZlZadUhpbTM3eW5OYlRSZDlsbFR1Zi9Rd29rODFmZzV5bmtNYm8vQ0xTME85OWhYYWRqSGsxUXV5bU9QCkpzWlNuRXdkM2NXa2NKbmREL3BsZHcxdkZnZHpPMWhxRmw4ZnNNOU10aVp1NkIyY1c3QnV0bkRkU25QYnRVM25POXBpTUlFVHpZN1oKNWlZc01VZk94RnZNUUhjTFI4Nmo3aEsrcGt2WXhvOVl5VUJ0SU5FKzVyYVhTbU12YkM4UU9HaDNtYzN1a21sMVVSZncxaHpBdDJiOQpCUU9NRk9LNjFRWGkzdGJzTHZUOVR0L2ovdTBPNEcyd3Vkd3dLaGdHenp2REVUYmY3M1RpL0YwcEczZUVqVHVCVmVMdFVKb09vZW5nCkdZN1ZkT0lFWG5kUW1lME1LTE56Q0EvSW1JYUpVVzFrRDRKeHNld1U2M0tPY1RuSDA5MU1GMUF0RjlFc285eDZqbXJwWmJvZ0dWOUUKeUQwQklMZGNSdEk5aHFKN2I2R21Ic2FZWFkvVzlTaGRqOC90c2JuK0RGM3FONUYrRU5vcG91K2piM3NjczB2V1hZcnVnYlpOUUI0RQo0bHNGUmUwRGE3dVlleDVPMjFYREU3VncxQWVuQytyZ1pSUEZtU3A0b2dlYUpqaGRVQVgzeXVCZUhkd3JoTlZNb2FWdGRubHFhbUZYCkRWMDVqQzN0eGp5b2lQZURvbmpYRklNV0ZUejVqYS83ejlaS2JhWHZYWDBienEwc3ZVeW1WM2J0TWc0NlpoaDF6VUhqN0dYVXhEaWUKMDZERDNPeks4YXhjbjVSTFAzVWlPOTJYaXpzM1RUMlBseVlHVGYzUGs3SjhjT0VPTm0zcGM1UVBqOVorZjd3MlF5U25reGpKODVqdApmYVRrUG01N2pOdzI1OXpVREhhanNlN09wbWszMU8zTWRPcXJhOTQ2OTlkZCsvZ1dHeC90MEhnU3kzRFRQRlhiU1VSRDZyWTZzOWJkCmxkdUpLLzdJNmIrWkE2dFFPSFhYcFJydXV2Tnk3Nzdza1E2RnNtMmVMTnhCdlpoeEYvSndSMzYxRDN6WUtKU1VadURMSnA2M0dJZ0oKMWo3YSsrNXRrN3VsM2UvR09NVVlFcUZCRVNvT2VXaUVCa2VvV2tFM3hOU2lKTzVOLzFEdXFVenVTR3ZodFZrTVBYQmlESjFZVFpNdwpKU1dsaWJxUGg2U0dDN0Z4ajBYSGVaekZQaUoybTB6OTJnWUI5TEdnMkY2V1N3R3lIaVo3L25NZXgvWlkrTlFGSVB4MFJycjdtUEkrCmhCWEJLSFVIaXJqOHZRRXU4bHhGdVNQbVJnVGF6UUFUR2JhSDBLMFF6ZVp3Z3ZQNGlLcy94RHJ3VDY5ZlAzdjE4UFhoOTBZNmhKOU8KbDRnZDYzSWg0MDl1dXNhUTlTYzhIcmV1NGUvM0hyMCtHUXoxRklLNkQrTGZnMC9WQjZ6KzMrc2Q1bFRVaXFrQlRyZTllNnViT0pZegpKY2tmM0lQdSsrT2Z2c0J4MGpjd284VlRzeEc4QjBrNzlmZDUrZ01mN1lGN2pvQ3h0M2Y5UFZtSGg5M2pqcHJkWThrVExnTi8wOVM3CnZ5Y08ra0VTbEh5bTlDVFg5MU5QVC9LRFpsVDU4WUgxZ1FjYmQxcEtucEdJTmhhMU9rTVhMelREWHFuaDZjSWZGeG5pWjJoS3VLT20KM3lXbE5iMmpmUm9IbmVlTFBGVEkwblRlY2RGbzREQ3M1eHZDR3hLeEJ4dlhMVGhNSWhaZ282WHpUaGFvbWwwcWw2UkNtRFFWMnZYVQpFRXlSN0svUVZuZzdKRDl6QkpNRDZudnFzMkl3ZW9Mb0hhMHlOYmpLSHFwaVFKVWRjRDZieldRUG5COHRKM2ZiL2RUTUp3cGNpYWF2CkxHWkljU2o5YWtxUHEwRmR0L0FmVTU4bXM3VW8xdjdXZ0M1ajZRRWh1NUQxbml0eFg2YW0xZldTTHBibGtaTDNaVG9ockU4cTVmRXkKUGZwVi9iZ3lmVURsQzhyZmVabWVWdTNwNWNjR0wxUm9Vb0pMWVd2aklpcDdSVW9DWlpkck1SSmRWTGpwcXpHM0MxazFYVStheTZqSgpWOWpleCt5S2lRYmIxZUJ6RnNQVFFITWRNTmRDZDZhR2xmUEluWDNjVG1NNUFNbWRSZTRvVUc1dHhscmhQNVBOeEJ0enFONVpFTWRzCmNUekIxQk9IenVVV3orUEZiUS82Y3owWlovRis3WFlPdDM1MHcwaVBFdW1HbEhCYXBqRi8wTTRxazNiYzdyemtzMEplT0EyMm45TnkKd2NmeFNPazJwVHFOSDU1VXppdzgreko5WDRVUExVOXQ4TWsvMDlPci9xZHA4REVPQXAzaXp1RHBtVnJFdGZxR1dxNngxWlFHVDlJSwovbUVxMmdTaGhCN1QyREswUWplN2I3anF2TXZPcXE0ZWg5eU9icDZMSE1SNWlFTnRMMFQvWGVRaXprY0tzZ29xTTduZWNSSTFpSi95CmtzNVBvcGxjT2xmSlpvOHMwOEJZL0tmUDBUMWYyU0ZEVGdFd3ptR21NeWJ6R0xPSmo1UVRqak05a1FGOUh6dHFaWHJmbHg5UjNzdmoKUHFyODJPQmZvTUVQaVU1NThzLzdHdHpqaHRPWldhVHM3RGhtdzVtYVZXU1hPWEVNczZIZFJzMGdMaFc1ZTNzMTB4TUJJU29ZVFJaNwpVTGxPWUhlNmI5TFJRbHZ4Nk1xK3AzU1VMSUtra0dsQXhpZGFRckVRRTUzWGluUUFLNEhkVzEya3dBb2t1cTJ6Z1IzQVp0UWpCdGVwCnNxclVmTlpaZWRGa2JPakdRcGJnVzUzTmJhM0ZGN21QcGZNcmQwdDFNQnVWcDRrYVZOZVRSa1ZvMUc3MkNzbXAwRHI4S0lMNDlrSTUKejZhaW51RDV2U1ZPTi9ISlpYbEttWjVXN1daOWFwbWVYdlUvVFlOdTFybzlTejU3bERsYjdySXNPbVJqdmJYRVhTc1RkczFNMVhXawpqV3FsZlNwWWFxNmp6REs2dG1DVWdra0s5aWc0cytESVVpZVdnc3h2bVg1TG9lWFo4MjdSVTlYU0Q5ZmkwVlJUQTVFL0pmWHdhZkxoCmxuNjRZOGNuOHpLNWg4bTlTOGtnNUtFNWxXNEhJUG4xa0YycnU1SG9RcHJNZytUdW83M3J5QjFIM1duVVhVYWp3eWgxYjlFME9vdDIKanFKVE45R3BrK2dSLzlCMDRpQjZ6RDIwY3c2OXp6TTBQZVlZdXVRVWVvSWZpTEU1VHkwM1R5blQwNm85WW9tNlVLYW5WLzFQMCtDUApIT1JIRHZJakIvbmJYWjkvK3czK3lFRis1Q0EvY3BDLzNmWDV0OTlnOTNWZlBMbnU2VVM0eWt0Y2F0am91bDYyd29RcVdlWVlLS1ZrCmRYMkhHZEZURi83Z24rc0ZyL2xuYmRVYzZNZ3NFOUloaDZ0WmVOam9Sai81eHV2TFBlV0xOVndoYnYrUWxxczE5UHhPNzZteHY3NUkKcXptdU9GTTR4YTFjdVA2MFJydStsQXMrLzVIOGlXNy9lTm50SCtIMlQrNzJiOW5FN2s2Z3liY25FT1ZIQzNlczY5dXpjdk5oUlpzNQpoU0hqNTMwK2x6UHZrRVloZDZESmJIRHgyNFlUUHo5NnFaN0gyYmd4Y1R4R3JneWdzdkhreGZIVXhaUHlOOWRNVDJKejB6TDdkVHVYCnpRVWRpdjM0N01aV0sxeENRaGdXd3ZPNnRPTnk3SlNhZGk2UHAvRG9TV2Zxa0c3R2s4MThhb1dSa2FXMUtxY3BLZU1ZYk9FOTg4SmMKYVhVSmlYblI1cnhzYzd6d3gyWHN6K2RyVXFOTDh4d0RXeE5aU1p1Rml4eFF5em1zaTZFNkRjK1poSWZXaXV2NzQzemM5WHJ2ei9JcQpuNzF2UHBFRlhtU0E4Y094bzNlR1o3eHc0bEhEalJxTWNYTFk1ZmZoSnNzWmJ2SVMyUE1FcmFwN1AwN2JmRS9ybDkvaU1vaDA5ejZUCnY5TEhRQ2kvRjk0blBOWGpCVDUzVEFMdk1IbEV3dnRhL3dCNDRyNzlGdkd3NXlwMWdPbnRlTVBwRno4NHlpOWRtdXRwTjljTlp4WlAKTXI2T1NaWjBTN2hyYVY1N2t0YzhISSttR2M2T0U3TmtNNlJ2Q09mcnFWelhsc0wxMnVMMGRON1NQYVh4eHBFcVhxYTZWd3pLczlGWgpkYlJnNGp0enlYZFlUeHJpaE9IQXF0T3F3Y0F3NFNQbTk4N2llNVBoQ1RNZFdXTWNwbWRJd3l4djJWMU84N3VNR1Y0aW5lOHpYZktlCjVXWE04eklFa0V5UFJaQzBzekU4Z0tSNkFNbm5ndE5lWG0xUGg5T2V6TzBQelpXd1h3QWZjelgzSXJsdW5wSDlJSVdRbU5CUnJ4UnUKdGRaMXpKazUzTzVETHZxMGRWWXZMclM2U3c3Wk1xQjVEclF4RzdQRFJVMnE0dUNmWlg1MitHb0hjalFJcTE1dzExQ3Y4d0FONlpmNgp4ZkxEQzNaNUJHL01FNXk0Z0k3bSswMjJVdlFPajV4RGVUbDcwRi9pZ2k2Nm9oL0cyTWpUYkRtWDh1V0VDL2x5TEZmTzFBTE1YS1B3CkVFY1BhOVFkYTFmbEF4THFXRURqN1k3VENLK1o3TGdkanpMMFdMWFlZOVgwdnYwZStlS2Q0bnRTOTdTN2RjNDJuY1RGUlp0QWMyTncKT2o2MEpFWmhEWGM4WkdqajZVTHBHS1FQYjVrM2M1V25pSEpQbkI2MDBXeVlwSWs3Sm4vTXdnSm4yZ2MzbVZSWkpsZVllUFRQVVdaYwpFZmFkWkduZU04cnRta2Y2WURMb0FaR1hKYXNubks3NS9jRWhsaFArTkNQODhwL3d2bTA1RGFrY1cxNzVob3QzL05aajdHelAwQWg0Cm4zWnBCanEyZllTdmorRDBFV1BlY04zSzVOSU9kSHNoWC81NytPWXA1MFFXcTQ3OUg0RC9sNkgvYTN2VnNvT2RLZz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJVUhISW5abDNHUno2OHozR29FOVpkTVA5NmhDTVdMd091K3Z3dXZNUWhRN05aU2JaUElLRVRuTEtFaDMweUU2UXovWUNRSC91Sm1KKwpNckUrTjhUNEpPSjZBT2U1STRSbklXQUhJQjFnY2hKeE54dmhOSURKQU4raVR6YWVmZnhwUngvclVhdlRwNXkxNmljUy8vaGtIL1ZrCmc1aTR5dFNQVlBERHZGSEJYK2Q1Z2NSM0pjc2paUVpIYVI2cjh6OTZucThxa3R1UXkvOHpONnVDNVRKSDVnVFBhd2pNUUNiZEoxMUwKcWJHbzhTUWgxWHU4OEVkcmNvMVhtNmpRdzVOKzFtYlZIUE5wOW5Gdk1seGg4LzFzWnZlVFpyVlBaeVFleFRzTFYrR0w1VENuamFNegpyMVZmbnNuOUwveHhVWkQvVEExK2twQy9YSkx4bDUzZG5HbVRrTDJuV3BMMmV5Wm9kK2FJOU96M2xwZ0g3Rm1USU9zZWVXZWJZaklsCkhOdmVOcG4rZmR2MDczRGpCNU12N1hnVnp5L2VEbFlaejFVWkRxV1NoVHp0VDZNNk9ZaHFmd2pWaUFQZkgwQTFIRVUxcGhwOC81bDUKVHl2MzA0Y2RhZlQ5NWEvZm9OcmxOQlh2UHY0Sk5qZFkydlNjZEpYeVhNSTd0a1JXaVZuaENwTWkzOWdHem1oczNjRWh6MW5TcXBaUApmTEhzYkc0NHVlRXVjVzlJMzBpVHlkTFNxUkh2T3pWemlhWkFtaTF0VmFUQkJJNXdUN3hXTGNkWmoxanFnTnJCWFhLU2pPejByRy9tCnU3cGM4c1d5VEhXNVdOYVBMZFBIWC9xWkd0U3AwYk91ZVNvNlQ3V3Y1MjE2WXNOaS8yOEQzVk0xdzhDYlBNdHpwTHRqVE1vOC9vdmYKWTVMcmVVaDNQUUxabjRSMy82aWZIeHQ4d2dWMkNNWXNxL0ZHQkhtY2hvdHpjR2VlZm52a3FiY1E5Q0tGUEloNEVQQWczaTA4em5ibQpRYmFRN0VhNWpvaVZ4OFM2UWJCcm90MnRJUyt1TFl1OG56M3J4MFdrYVRqRTIxUGo5NHo2NHltejQ0bmIyOW1VYnhraDkrZlZ2LzlVCitpY2RXdjE5eDh2LzhHUDUyUnRVVTh5Tm5RT2RhWklad1YzVjBoNW8wb09XOGVEOWg1cE03UUNQZFc5MDJsbTRqME9DSkxkd2U1S2sKMGNnZDZqeVpwZnZlOHVuZG1yMzd4bXpleHhiNzZSdEpUNWpaZjRhTW45TkozczNscktSSFNyeGNwa2UrQ0I5YnBvKy85RE0xcUZpSAo3aE1ZdlFMdUZ3aG5DQWpIUDR3SGovZmNlQ0kzeGwxYXZKNGsvU2taOGZZNThaajV5ME5YVWl0ajlNc2UrWFVTU0hQeHNNeTc2YkdvCm5EUHN5dFBLemZUOUlKY1BLMy85QmtjRDMxOWRLdjRoR255dmJqRTk4c1hsUXdmT3M2ZVBXZFNwRlUwWGp5Qll6N0pMN2pOTWhyT0YKNU12b09Oa2EybXp0ck9hTlczYkhHZGtCRnhkUzNJUVRiczhrLzJEM2lSYjVld1B6cWswK3lXNStMNDk5SkNkZmhYZkhxdnZLWDEwcQovcFFHTDZzSXl5TTZoV2tjMDVrS2NxYXNuR1JVSG1GaFhmdHBHdEhVMGtENzNxVlpwSFYzVVhsSGM4M2VHQmo2U0Myc1dDTEFoYUplCnBLQUZoTUx0UktudnhwTCtGUU5DTThXZlpmYURpVStCenpEd3didzNFOTE4STFxanluOXExVlBoN3pnMXVZOFNuOGw1a084ZzFVR00KZy9CMlRWMERjbGo2aTBmUy90amcyYzllUDdOQTQ2bDk4TE4zNG9uQ3QxY0VIUlNVMmxFK2UwMXptd1pWczU2b21tV25wRm94VmVVVApPK2JzNXkvUzRQdFZnOGVPSzdReVhUd1M2dlJNcDFHZGFWck9XUzlTSzVvTXVUa2VYbkF6NERidkcyQXJ0R1I2eVE0Rlc0ZVVlcFlEClVKUHF1UmR4OUNPK3o1TjRudEN3cHpQa1FRSmI4eHJ0ODNqZE1OTk9wR1VKU2ZsNURxR2VZdm5YbG9vL3BjRkhkSWRIaW1rYzA1a0sKY3E2czdKV1pRYys1ZEpEQTFNOE5zSEswckNMOWVJQzdJYi9JM0hLTXZDZkx5R1BlMDlGMzZtZU5iK1l4N1FmN1JUdGNYQS8wdTU1MgpwdXhvaHV4S3Y5OE43ZGVCbnI4c0R3eDd0VUpLL3VwUzhRL1I0SHQxaSttUkx4NVhZTzR1NlR4ZEk1cUdMUHk5akFwVlY3TThKMERYCnhWWnpJNVNlcG44eUphNGYxMzQwUmU4OFkvOTRZbnNIK2EzbVNqRG53ZVJZdjdQemNmVWs5blYzTnU3TjNlMEo2cXdJUndJK1ROZ1cKVHU1dHVERjM4VUhEejlsY1BhVmU0YURmUTFtdVFpNkRVKzdUbXFGcks4OXhJNDU2RG5IbGNUekNNMFBXM0k1TDJtSWdtaG40NnRHWApGOUpoS1ZmQ2x2dkRmSEpMZXZyUkI3MVNMWWU0WEpYZGMzeDBDNS9rVE11WG5HbDVoMHk5aUhzNTNhK2VuQXgyV1U0Uk1LY1ltRENjCnc0cjhUME9XV3o4bzBrOGc4bi83QVlmNzR5RzdpSlZPUDA4NzBXQTgyZkZVU0lEQUpjSldPN2ZYTVp3ZkR1RTBkZkVrSCtwMDNCOTUKM0pPcFAyWWZKQ0oxUEwvaGZqaG83ZndBaDIwNEpHMS9oTVBTam5DSTA2T25PSFFOL0hvd1QwSFlEUHU4clpFbmlNMnhsdUtuWnEyegpMS0RJdUFJZUdCOGV6ZFQ2NFJmck9WdXlKT2gybGpteHhHSlhWbzlaMkJUSGVobVcraEhYZnRwYXV3Z0R6ei9pd0gvRWdmK0lBLytzCk9QQjF1N1RTaEhvV1hSU2VFRjMwZUc1NjArZytzMG1tN1RSUG5HMVBTT0h0azdjZkYzUnI1Nno2a1VIOTBLRGFEZzFhaDhPTCtvR2gKT0JucGhQZC9NQ2ZkemNLUHVsclBTemc3UzJFMzRSNzcvcFBtVnJrb01wWDh2c2kxUjZPKzNuUHl3UTUzZkRta3JNc3U2NldwT3pUdgpGb3E4QzFoN05FNnROMWhQWXRST1R3NDRlOXJMNFdtUFQ5N0htZVZ1Q2ovT2ZaL0tmOSt6S3A3RzBwK1VKLy8weE5ZZ214SzFDNW16CkVOWkJtZGR0c3lsdWJMYk45ZE16UlUvV3lTZTM5YmtqM2Y3bER5L2VQZnpYdy9IbHMrZi9JY3RpOS9FdmNRckl1ZG5zSTg4Qm1jNFMKem5xNmFrODNPNmFhdlczSENlNXpWWHVPMlJ1Z3J1NHN2V3djOE9oN3JQemo2ZkhINVBqSGpvLy9GUGovelpEeG52ai82ZU1EQUt5MApGSkNlcVBIVTdISmlOZmtoejFjNXM2eCs5QWtyazZxcGo4d1V6OTJaN1NoS25TdzlQN0dMd0QxSHNZaStHME1BWFBpOXN3TlBuNURuCi9DUS9zVmtOMzJjeGZGcTh4ZmxoQ3g4YmNiRlBMZEFQV3poTklYRjdJVW5CazZiQ1I1eGNNMyt1czJ1bUN4TmhGNnBELzk4NEVjWXoKU1gwYURNbXEzemNWdmk5aDlUNWR0Wm1ScC9wcEFTNm5rK0oyK3ZnUWwyR0tEQk5sZXVSWWpuMDZpczhuUFpzTS9aZHI4SkpuNnRMQgoyR1hQM2FZTHpPMkNmbkV4OC9BKzc3QUZGRTJmR0ZGVU5LYW9oeFJOSHg5VFpKNkZveG9mYUhaSXQ4c2thMk9mZHVUdVF1S1JIK3JvCm9vOHgxVjA4dkdqNkVHTmR3L0k1a3M4VHQ5MzIxRzJUbVNXWXZhMGxiN3Q5VXZxMmZmSTJpMGFhUGkwY1NhTjZEYlhLZUtUcDR3T1MKaUZnRm5tRTFwQ3BBcXJLYzdrL1N3NVFMR1dSK25Bby9Ub1VmcDhLUFUrR3h0RkVma0ZUcExEL1VoMS83UTVtY2hQRFZQN3g1L2VYYgpGNi9mdlhqOSt5KytHSFR0OFl2cEg3N0JOMG0vK2ZMWnUzY1BiMStMRW43OThzL2ZmdnRNMUcvNzQ3QnRWOXVhRm5oT2wzVk5oN2dzClY3S055eDlodlpLNXRQSkpyL252di81SmZuM0hQOTNuK0s5LzVzZi9JWC8rYnlIK1NWNys4S3ZEdi8yditmQzFYdkpyK2VYMzhDWVAKcjRSNGRxUERMMGVxUDg4dmgrc3YwWFpYdjViL2YzYjk5dDN0aStmdlhyeDUvZXp0bnc4L3A4bmlaOGMzYjE3SzIvL0NldUtydTY5Zgp2SHZ6OXF2anMrZi9JYjMxMVc5ZnZIejQ2dGNQejkvOTlQQmY1WUwvVS82M1YvMHYzN0dCVzc3TlAzSTZSQndWcURiSUVETW54aUlUCm9sclNQenRBdGZwWmdjdFc1N1FkL3ZVWkc5cDFYQXJoYWs2cmpIdTlTaUxaYThmSXEwVVJ1QTU1dTFwRnBEcklpcitTcHZJaEZ4elcKS3U5Y3BYcU42WkR6VlpDMWRYaU95MnEra3NWYkR6bGVyUWdCRmNuL2FwRlZkVmkyS3hINmNYYnNlaFZrQVI2V2RDVnJMZHBsMjlVcwpYT0dRNU12Q3hvdmVMZGFySEdIUXIvTlYzamFab3VVcXdmakt5OHB5SlNxSTFGb1JtQ3QzVytVdTg3SWVVdFJuQWlIbHVoMlN2SVpJCnQzcVpFRU9vNVNERFhJT00vVnF1SUcvQ1A3NGhTbFMwSUJuT1hFQ0lxZHFycmZNVmxKTERzbDdoN0o5RGxLNEpzaFlQUzdoS290OGMKV28yVXJ4YS9LRnlKb0xxMng0NXJScTQwVFptRzVHVHJlcldFYlRtRUxFTXFnNkJYclZkYlNPc0JOd2gxNHdOSmk5SldYWEFlYms1WAp4Z1NFeWZoRjhqYnNzaS9DbGJBb2RIV3FWM091NGJCSUh3cjdFNEk4QmVaRElEdkplcDMwT1NDZWgxRHNOUlpoS1J1ZVdVWWNuU3VmCkY0VERZcnczRzdDRW1TLzlLYS9LMFVRclhCWnl6N3pLZTZaRngydkpWL0trTmw0cFhNbmZNb2RXNmRjZzd5V1hWMUVuRHVzaTQ1YmwKbGt1VW0waTdxOXhNaGxZdmt5a1hwT01PWlVXMGRtVW5DbmRlY0puc1NFS1FSWmx5a0haV21XV2h0RWtscTJSRlc0ajZQYVJaWG1pVApjYStMakxNTWsweDh6bUNaa3NMdmRjVGtzYTdXV2ZwY1ppQ2NUREtWY0RONXN5cFhaNWxUU1dZYnAzU0pPdXQ0bVF5d0tEYVJrZGRGCk9pQ2g5K1JlOHV4elR2Rnd0c3B3MWI5elFkOXl5Q3RUMmFFKzE4bXJrU2dUQy9rSU9WczJCRjNMdE1JYlJIYkEzRDdyNUpISGtxMjYKMTVHVkkwcmQwSWdSL0ZZK3U1VW9jM2RiVWN1YWtibkdGMnYzY2tLYnFYaWdYc3ZmbzdWejhtTCszaW1nSTJSWVNwSTFML1BxMVk2NAo2THhLTWl0bFA5NUFrTzFWT0lTdzJpeWoyQWc2YURMTGdxeU1YbXZSdS9aMm5HQjMwOHVjR0s5RVk1VkJhKzJFcTBWR2Y3aWJFZlF5CmY2Wld5NSs2dFhQeWJ2N1MvK1dmNUo5L2V2SUc4WXR2di9yVk05bFRiOTU4OCtldjN2dzdONHkvZi92bXUyOTBoN2g4eGE4ZnZubDQKOXU3aDY2L2tGcnVkWkR2ODVLZUhmLzJYQzN1S1RJeTh6c1FVQ1YvWjZQR2NTNUdld1c0aXJLakVQTzRtUmMvbDF0MWtxVmRseTdJeApZc0VzT25NN0Via0o0Q3FWQ1NKNnYweGxtU2hWZUt6MHN0WkFRbHdSOXRoRlFkaEtCRkVtaTE0bXF4RlJCNGVTdGZGRzJLd0dMM05pCm1EZTdMbUtYbHAwNWhOVW93dnZXdUEwVVhybllEdDdyeWNUVmVuUFZCMnVVcmVvbXp3dWRLRXlHODd5OWNRWEQydzVuM2JKZjcyRkIKZndnVHFWRzZWYmpLcXgxUjVBOXdyQ0RzY0JHaDF6bFd3RllqbzJJZjlRMlF2M2p0VmRDRlNYYWwxa1lqMkoxYVI1TVlaREFyN3VUTgp6THBNMjYzbXZtejc0N1JLL3NDdG1aUFgyazM4M1pSTGFhNExrR3F6N0E5MERzMjVoTlV5SGMvTTZWblZ6eTUvYkl2czJEcmhvdXlkCjdORzh3QUdibFZWaTVRSjBsNE9JRmNKdjR5emY1aXdNR1ZNSzIvVXNlNDNzQjNqV09SaXZuSVU3cjdLU3VWV0FXd1ZoMzlqc3RrVzIKMWlxYm42emtUVGFmd3lhN2dFZ1NlaGwyQUhtNVRaaURNUDREOXpYWmZUWVJla1M3a00vQ0I0S0lNYktIYkZ1MDdVaUlKVmZkUjRUcgpIQ0prRzlHWTBKc2l6SUVnMWF1SUc4STE1clRZWlVKYzVJME9zdWZCOXNtYnJWVkVCOWxFNndLSjZMUS9UbllXTEE3c3JYTGZQTXY5ClgrMkllRi9zeFBLZWVVbWNScGcxVVhoY1FKZmI1K2ZleDZYR1hrZjJlT1FvNlkwNHdXNmxWemxSUmxpMHYwTnJwcUJybDdYZnl3bDYKbVQxUXIrV1AzTm81ZWJIZFhBdUg2MjgrbWJVRlNJK3laVUx3a0wxZkYya2paaGtsR1Z4NThxdFFaYTdJc0ZYTVlQQVNVVlJySXlpUApFc2xZNXNCUVMzcHhGY21vdCtNRXU1dGU1c1J5QlFYODBOc1JrVG5MOFBlN0dVRXY4MmRxdGZ5cFd6c243OVo2VDNVcjl0NG5yOVVnClV5VkQzc1dVRWE3SURvU09nRTEzbGJFUmVUTUlLeGZobFk4V1NzUktGVFlqUTRTbHUyRmg2SHlRU1FaR0s0dzNZNWFMNUhzVnNaNWsKK3ErekxJeFE0VUplWXlPd0l5RHJ6MnV2Skt1R2ZEc2w1L2RHRUkxbDRPNUdGT0ZsWFdYMUJ3aVc1T2FMWFNiU1VDM1lGSVR0T25zUAowQnVLek5sQVhzUFdSWW9xM0hXeUNyU2RFbVg2TG41bG80cEVEQ01JZDUwTjR3S1dDbmZJQWExdklsWjJDcStVSFhhck12eTluanhqCnljdkNMWS9xeTBBUmpXRUwyYTUwS2htSWFGL29yTVRueDVyTkFUckJDaTgrbmovb0lQRktXWEdKYzFqMnlCWHlqUT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJak8xVldyRGJ5R3ljWlorUVVmVjlOSlRlUTNHV01STEd5dWxIaFV3K3lBVTFzRjdCcXA2dEorYXRiNXdjZll4am1JRytBcGg0aGxKUQpPQUJsa1hVZFpIdEcyQnk0Y0oyRExSLzVzS3p5Z0tLOUpqQi8yYVNrdTRUamI3UGUvM1NHbm16VDBCMDRTUU51VTB5NjZWU0loL0xXCkt6aFlVVjVBMWgyRVhTTUhTNmY0QkZuQTRZZDZVVjUvRFVOVGpXQjMxQXNiVlRvaUNUOFltaElwZEpWbE05elNLSHBsZTdSV3J6MTkKYSt2MExiMFhSRFBWM3NHTW5uTlVLUjFqV3pPWWlGTkZEeGJCZXdaRnhuL0xPMHBSTFV2SG8xR3hzbW8rREczSmVnZ1lrWDVQbytpVgo2U29LS3gvcnlTNEwrTlhRVnFQczd0bW83ZGxhVyszNVQ5K3o5WUNzRXZhTktMTFVURi90aUVEVVo2eXdwTDI4VkZYQ2crZ0dDYlBRCkNjNk1rSFdtMTFxckRtdHJweEhzYnM5dHVpa3h5UlNCbU52YWlmWkM3VzVHMFBYdHo5UnErVk43TzZmdjlyaTA5aW5tSnVpSXM1QkgKK3hMWUd4UXROeTlCSGNpQVdMcDVLU3dpZ1ltV3RUTXZRU2lWdlc5cjVpVlZHSVFMdUhrSkl1bzhMOHZPdklTMk5naHNibDRDcitUZAozTHdVYUw0UVBqR2FsOGd0d2ZQY3ZDUnZmZ1h6ZFRNdmdSRGozcm9VNHFKU25GbVh3RjFsZXcvTnVrVFdKMHJIenJwRXZpbkNZN011ClljT1FUbHVhZGFuVjZOWWxzTXhscWYycFF4UlJhS3RyTXkrRktITi8yeG1YUUNwVk9MUWJsNW9rNE9ZbHJMQ2E2cll6TDRIeDVpUjgKdXBtWFpCRnRFVlkwc3k1aFVjbnVzVE11eWRMTE0zck9iRXV5L0dYZVJqY3R5Y2UwMENqWVRVdmdCd2tIcEpobFNacElhZDNjc0NSOApXd2FxN094S0ltWXpNNTZibFRaS3lLVlpsV1Jud0p3ZGJVcmtmVnNKemFhRXJvTVE2U1lsdWZjVlhBQTdrMUtRdTNKSnVVa3B5SFROCkFDSzc5aVhjbHJOMk5Da0ZLSytsVzVTd2ZjNTFYWnBGS2RTb3MzaTBLR0dub2xqckZpVzVyMnpLOHNodVVqcGJXQ2Q3RitOdHdySXoKS1RXaVc0TXdIV29XR2RqTVJTRXk5V0FlVFVyWXJTS0VPNjhUWUxTRXVjMGJjY0pvNU9sRU13VzFadHhZMU80MW1wVGFBL1ZhL2g3ZQp6dW1MamJ4YTVLRmxaMUxxUkRNRmdTK21MVGRia2NpY0NKdmFkaFlsY0ZTRSt3eTFacjFwYjhZSmc0Mm5FODBTMU5zeFcxRy8yMkJSCmFvL1VLdmt6dDJaTzN1eHYzYUQwOGRxOUxQQll5WVhNT1BkcXBNcFNwb0d1dVJGazR3YW9WQWhCcHd2WVVNNW1PWVpLc1VScTVtbWIKNFQyUWpoWkpFWmZsWk9aSmJzVlNRN1lOZHhDWS9SL0duU3lNcEpueFJjWGdXaFIySWs4ZFF5T29CUjRDZXVtVllGa3ZJc0xYYW40RgpKMnlidHQvczd5QmlOY3dySGxJMk0rRTRHMVVFQ2lNd3hzWjFJT2pMVmRFMnVJUzlXdXU3WUNicTg5NDhNUTZVV2UyYjJBUkVDRkk3ClZLY0tVeFFaZmxFckxqMEhNWnB6QUh4eHphRlR0T2ZnaEVqcldDK3FwMkJveXlsK1R6ZGJHN1dZUzY2MUJkMkpvOWZ1NlJTOTBwK3QKMWV0djVXMmR2ZWNQSXVvMEl6L2szSFhkbHIwdkRTb0tPWGR6cHFHZWFQT2xlOU1nbSthOHVsL00zR215VTRzSWlDdmRueWJhdmV6RAo5TERaU3NDMkl2SjkzbnZVS09NbDNxRzQwTG5xM0cwK05XZ09JaGI1bWpHbkdsVGN0V0FTdVZjTjNJM1AxdHhxMkpPNmhjdjlhdGk2CkFxWjA4NnlKdkNDRG4zSjNyY0VLSUpQZGg5QThaOURHUkJ3czNia202MEJ1empYb2RXQkJXVXk0Ynl1enZZTjcyS2d5eWExR0YxdFMKMTg3T3gwYkpNeSt4T2RuZzJscmt6czNMRmloNkxIc3ZHNVZycUNITnl3YlJVM2FqT0xqWkZuU0lTUUhkenliN21MNlJPOW9DOWxFWgpyZVpxbzdraEIzZlFtYThOTDY0K0ZYZTJRVDNmSXVhM3U5dlFnZlBpN00vOWJWUnZOcnlqTzl6aW5DQmVidDNqSmt4VUpCL2JrSnJMCkxkSkZVa1AzdWZGS3ZHOXp1c0d5Mm1hUE85M1FYQzF4N1Y0MzJDbXBwRFMvbTJ4dElxaVpnYWM1M21LRVJpZzdaUE84UlhER1FLZWEKODdVdzY5emMrZDVnRXFLWTZjNDNLSmVMOVBmaGZHRSs0bjdEOTF4V3IvYlVLaHR6M0xvRERvWU12a3h6aXpYS3pnazMxRE1IMjlCVwpvOWc5ZDQ0NE1JS2E2ZnkxdHFESTBzRGI3dGtvTzEvY1VLODlmMnZyOUQzUDNIRlJ1aXBMcCszOWNUREFjTzQxNTFjTXN0R1cwRjFrClR0ZzU1SG90MjMrR2hockZicmh6eVdHRU00VGszaFRVa2hDSEd4cGg1NVBydGRxVHQ0Wk8zL0QvdDBKVVNFSE5sU0hCS0dqVEdic2gKOVRwOExkb2hkdFlaUmpxbzM5S24wR1JwYnlaRnVCOHBybWlxZzIxRnZBNGxFV0hvQzJ3dXdpbDBMc3F0MU9EbkZOV1lrbnIwZWoyNAozQ2pHZzZQa0RRelJlRmJJWUwxK3BXZzFkcVZzU0x5blhEa25tRzFnSk1WK2RQYWFKeHBRZ2o0RkcxbWUrM1k4VUlOdU5tZ0hGbWxZClliZEE1ZzRmQTh5VVRsSE5SQlpQbXRlaG5uQ2VDald2TjlVcGMyYzBuU3F2b050M2Eyc1JkaERETXR6VEtjOXRKUGxvdlpvL2ZXdnEKN0MwZmwyMCtoNThYL2h2T2daMmpGM1pjSFYvMzlNS01QOE9EMDF5OVFRUWgyQXIydmw1YWlubWx1M0hCTkhTV05Vckt3NXhxMU1YbgpsTHQ3WTNiTzZPN2VSdG01ZTNzOTkrTkdyb0EwVWxMcGs2cFRNWlI1ZFBCaUY1dXpqZEcraHg3eCtVYUl3c2pwLzJwUGhROGdydDNyCjIzWk1kOFkyd3M3ejI2dTVWN2MzMVNsMnk1MzNseHNyRmZqV0Z1UmpXdGI4bGs3WU9ZQ0hhdTNwVzFPbmIva0QrSURCcjRRdjFMMlcKMktpdUpUWnJvR3VKbUJZMHFveGFJcmlIaVBxeGFZbFFxRUlWSnVoYUlpd090QmVPV21LejQ3bVcyTXh4cmdEQ0RGM1dXSFphSW5TLwplVkFsWVNKYlpESzdrdWlmUngzUmFVMUZoSmw2aFgrdjZYNzArS1Z0M2V1SXNHS1ZCYy9XNnJXZWN4M3h2QzlQN1VpcldpeDJPbUtuCnVsNUhzd3htVmRQOThNZWFjdG5yaUxUNnBDVU85VUpWbTE5dnExRkdmVzJnbWw3WDIzTGRyOTl6MUJIN3MvVjY3YTFhVzZmdmVkbWgKL0NtbThDVWJMQVdHWnpoOVgrMnB4WlNlWlhFbjRhb2FCRmJWREptbFVkd2VUZ3ZuVUE4N09seHp2YTFHc1hzNjh6RHFabGFFMXBacwpSTUxGMXVHZVRuRmp1cmtxVzczMi9LMnQwL2Y4UWJ6TEVldVU3bG5aQ0NqZVUyb1h0V0VyaEJoRkZSeGlvaVc3ME15ZmtDb0pYTHdVCkd2NEIzZGdjMXlHVGc3NG9HTlJqVUxhcElvZDBzR2hKa1dDRkxVWjlXZHJtSE9RUTRYekVacXkyR01JZXNPQ2djbUVxUURMVkxvZkcKMkd4TndLbHNVUkU0OHJLSnV2NmFpbDVadG5XaGQ1V0dYdlNyZEpiTmFhajRDVzhsKzc3cTNmQjBWVkxnaUlZT3Z4VzFHMEtJRXYzUQp0RHRSNFdUVENIUitLNEIycHZ6SHpVOEJ1Q0ttdzl3T0tTeTRnaEhBVm1HdkZxV2JMNENkcDlMbHVHSVJWY1Y5cmh0VTJES3JmcXhhCnN6UnJHbXlBd2hjelR5YUhaNHNXY05qVFJPcE1ORHFrNUpwdnB1MS80V05WSGNsRk1RSVVDbFdIUEowREo1cGRYQlUvZ2l1V3NoaTgKdVZQZGJJQlpRME92OUVrQnNKN2FxYnJpamFKekpDa2VaNmdubTJLb2VXeXJVZXllZW1XakpvTy85cmFpYnBYRFBXUGZQSWRuYS9YYQo4N2UyVHQvekI1RUNZM1pBd0Z5NkZOaW84S3BUR1Y2Q3dlTjhDV0tZU01oWGN6UVhCeVpVWFdGbmhTMWxVUnk5eW9Telh3ZDFrZjVBClVkcWFOQVpoczFEN2dDaW81Z2JYUHFDR3VHNGNYVG5vRWlCOEw1aW92VjdaSENxNDZSMDRLeFc2c0kwU1lEbWhnaE5xWDhpc1ZIbTEKVVdUWk5BbXdVMHR5R1pPK00vaWRrVnNtY3RvckRyR09Jbkl4R0FxTWZmcXdxeitzL0tHM3JMTzlwbE9jTjVRZFZlMkYyQ0ZnTHd5MAp3SGxiMjZBOWdmdndZU0crNjJ0dU5paUFYT2lnekI0RElHd2orVDA1ditlbHFqMUlybEsrcXlKK3RQVUxOcHBpcG9tUUJrMi9VdGxJCkVjMGNGclE1V25lczJhMHIyYlhCMlBWUzJxd1dDQVR3OE5MRUhseHJCRHVsUVFES3JzNkQ4V0VYUXdOU2g2UTFyczNzYUZQMGZLNmYKc0JaTVNRRFo4REkwbmIzYVUyVnJxWXVhUE5hbDZsZ3RzRGxpYzlDdWQ0b3VCK25vQXQ3WjZ4WDFwUTV0TlVvYXpIV2RtbkhVVlRnTQpiY0VxQkl4OHY2ZFI5TXIyYksxZWUvN1cxdWw3Tm9lYlRGdDZ0ekRmdVZXOTJsTTNOV01DckVLM0dPd0htWjZNRldoSTRsMk1va2lICm9OaWRvWjZzMTVoM2JUV0szZE92TkdveEMxZHJDLzVTbURyN1BaMmlWL3F6OVhydCtWdGJwKy9aZWtBR1pzdllja1g1SXl6eDFZNksKcmJuUVhEKzd2UVFqeWIyd0VnSFJDTDdpWjdyL2U3VUZ5ZWJTMEZTaitDMzF5a2FGK0VPZm9iY0ZLRVVKL1k3MldSbUZQNWRYNmkvawo3Wnk5b3I4ODdLMlZxQytndXBNNVhBZnFBdWxYb1hvaVRLcWpnWkkxektLeHB0SXB5bnRnMG9DSjNLa0VkZkd4dksyQll2ZlVLeHMxCm1tRFgyNElmQUs2SGZrK2pQTGVudFdkcjlkcnpXMXZuNzlsNklNRzlHeFhQSklxTCtjOGFGZlkxQ09IZ2hETm1uaXczaW1vQS82WjUKQzUzeTNNUVN1dmlIZW9CQndEdlltbW9VdjZXTENFWTFxMFZ2UzFnbjBKVDlsa1pRRjRFOW1GZnFMK1R0bkwzaUQ2RFBOd3d6ZGdwNgp2SGFnYm14VkszT0pPcW9iVzlDYTRTaHlXRGRNRXFtNi9kMkIzUkQwMWF2Z3lHN2dzRFBzYkEzYUhVVVV5RnNOYlFnQUZZS2tDZG5JCnNkM29LSFZvT3JnYk5WTjJINDJCdTJrR1dkYWxvN3NwNUFCMjB1RGQ0S2RwTzRGMzQ4MElXR3I0YnRqRjdFYW5uWE82RHhrUUduOVEKLzlwQnZLSERKRlZkREZMTm5Sclkwb2E4YnBUbjN1M2c5ME05dzNBUGJUbkY3K2xxa0ZHcjltSnJTdTVFSDJpL3BWTmNFdFJINi9YYQpTM2xUcDI5NVdUMy9ISGh2ZXIyaW96MDdWVkVhRFZ5Tk9TV1RaK3NZN0VaUmM0VkJ0WWQ2aHVidVRUV0MzVkV2YkZUWnhWZkNhbHRUCnhlekEvWlpsTUJmM1IydjEydE8zdGs3ZnNuRXptRzdoN0lQYUxwekNzQmVOS2dKVW5nTWlBa1hJS21ycEMrWkRuR2NQL3dpMnNjSm0KVzVaMUhlcWwyWUNhcmFWRzhUczZVMUlxVEd4QWVmVzJNS0dXYmJ5blUxU3MweWRydGRxenQ1Yk8zdkVITVUwMFdESFdFS0hVSS9TZApzaGhOaG81OXgwTEppVjR1QTc5RHgra2hrSTUrcHljUExLREIzeUhuYkNYR2puOXZGQlZ2RkFEZnF6bTJIY0syMmNDZHNvVlIzekVxCmVTbms0UWFDcDlDbzZvaWg0TUZDbTJ6ZFlQQndscXNLNGZCMjlENGx5RTVaWVdEd0t4dFZwSms1MHFkaE1IamdlK2ltYnpENFJsSHgKeDJEd3ZaN0QyMk14cEVHbnVObkJCU2VqaGl1Q2doMEZIK25tQVVUQVVmQkE1cXp0WVIwRkR6ZUUyc2tjQlEvM1FpcHI3aWg0NkJKZAorWEFVUE56c2hFODBGRHgxamhSVHg4R2o5YVo4TkJ3OFVBZTB0emdNSGlOQXZ0cHc4TmhJa0RqSEJDUUR3a01SWCtzQWhNZEd4V2M0Cm43SW5kbUJIaVdNUUE0SXBYdTJwb29MaDZPS0dPT2M3YndNVzNnaytTWWhmNzdVTTRqNDAxQ2gyUTcyd1VWMnhhMDBCT0JWSDlIMmoKNkpYK1lMMWVlL2pXMXVsTE5rbmZJZUtBZVMzQkF4MGR2dDZvRGVLTzJVOW55VURKUThoSHA4SndCY2RmYnd1Q05tRS83WjVHMFNzTgp2ajdVTTRqNzBGYWo3TzdacU8zWldsdnQrVS9mcy9XQXc4VXhkOHJpMXV0T0ZSWkdJSXdEejBXQXZab0pyM1o4ZXFNNGF5S012ZGR6CnBIdHZxMVBzbnMrbkFSRlAzWVNHbE43V2JHL1c3MmtVWGUzdDJWcTk5dnplMXRsN1BpN3hmZzVjL0E0czVzRDRoaFZyeVBpR0ZXdlEKK0IxV3JHSGpHMWFzZ2VNYlZxejV3M1pZc1FhUGIxaXhobzl2V0xFR2tOOWh4UnBDdm1IRkdrUytZY1VjSTcrRGlqV1FmSU9LTlpoOApnNG8xLzlvT0t0Wmc4QTBxMXBEeURTclc2NHhRc2VhZWE2L1E0UElOSzlidzhnTlNyQUhtRzFLc0krWWRLOVlnOHlOV3JFSG1HMWJNCklmTU5LdFl3OHlOU3JJSG1IU2ptcVBtR0UzUGMvQTRuNXNENUJoTno1SHhEaVRsMmZnY1NjL0I4dzRnNWVyNUJ4QT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJREQ2L0E0ZzEvSHdEaURtQXZ1SERHb0oreEljMUJIM0RoelVJZlhkak80aCtodzl6RkgyRGh6VVlmWU9ITlJ6OURoN1dnUFFOSHRhUQo5QTBmZHI0V1QzMmdCam5mNGNNR3FtRzZHbmk5NGI0YXhuMkhEMnRRK0Y3UDBmSzlyVTRac0ZvRDFUQmRyYTJHKzJyMzNPSEQyck1OCjlkcnplMXRuN3preWYzcVNkdml3Z1dxd0xvZXlPL0NyNGQxSGVGaER4ZmRhaHBzZjJtbVVBYTAxVUEzVjFac3k0RmUvNFFBUGEwL1YKS3JYbmJ1MmN2dDhsOWJKN0VtRnVFeFpoOXA1R2hTV2hxSlNWeVZBS29qZk0rbElEVjZOUlZQMTNMdC9yQ1pkS1N4amJhaFM3WnpNYwpLRlZXRkZqNTBKWnQrY005QnlGZ2VEYXYxNS9mMnpwN3o3WWVQa1hCUGtuWlJLMHVMV3FjS1ZqeHIvYlVvRkUyc01VVUlDWXhXSE5TCllaZTdmYU80RWNhVXFWWXZHTkNsTjlVb2Rzdm5ackF6YXRKZzFhR3RqTDA5ai9jMGlsN3BqOWFxdGFkdlRaMitaZS9MajljdjRRbWMKaStvY0lqT2FOTnFwc0cweGVRV1E1dGloQVRDbjRKOW5VeVdkb3BxWEorcG85UmhTbDllaHJVNnhleXBIZCtvMksvUzN0eVdVc29IUgp0SHM2UmUvcHo5YnIrZlAzdGs3ZnN6R2twWnFrZ1kwSEMrWFZub3JlTmZUWmxyT2xEZ0dDT2VnNVNGdW42SHNBRDFQaldFOTJHaHBNCmUxdU5ZdmYwUGExUjYwb0luN2NGQzFvZDdxaWY5U3AvTHE4enZJKzFjdnFHbjJVUmh0TkZLQktZK3RDU2dvdGU3YWtBUlVIZGh0MlUKbnRNbFdPS0poSEF3V0NxZDB0YlNHdU5ZVHg0Z09pQkQyMm9VdTZlYmZScFZrMlMxcGhiR3Z3eDNWSUl2ZTNzd3J6VzhrYlp6K29xOQpJejlCZ0QvaFpxcStNUXJUUWhkZjdhbUFuQ1dOYjlaWWRCbldBbTgrbFhNSzRFNVJaVzFXQ0VhclZoMy8ycHJxRkx1bE8zK05LbnBWCm9KempUY0hNYWlLQTNkRW96MDNGdFNkcjlkclR0NlpPMy9JSG1aU3drTUJQTEtJL3NqS2FZOG1KaSs1UzhQSFVMWVVEbElpcU9oTTUKclgzV2R4SlpyOGo5V3AxVlhUaTlFU2ZZclZ3NVY2SXM1SVZPY0crbWFMaEV2MWNaSXJYYkF4VVBxdkJIdG5iT1hxeTdVcVBxd0JzQwpXdHlQMklqU1cvT3F6czhVNVRFMlROaWtEbFdxc1U1UXhycXBBdHRxQVk1UVZRUFFkaHJCN3FhWE9SR1RBeXlwdFlQMXMyM0QzWXpnCnJsZDlwbGJMbjlyYk9YMjNIMkw1Y1p1RnlBSjFnMTZqVjNzcTVMdlZRbVVpSEVGUVZPYW8wb1JtUm5PSzg3RTFwVFRXdzl0dmVXeXIKVWV5ZXpzZU1XaFRTUGJSVlRhem85Nnc5c2Nid2JLMWVlLzdXMXVsN2ZnNXg0cVF2WTFZak5PVnRHRWRmN2Fnd3RzQWpEQVZ0aWJPYQp6U2hmUnpldkdFRUZUcmpRbG0yb05hdTNiR2pJS1g1RHZiQlJJOS9rMEp1aUhTZlY0WTVPMFN2OXdYcTk5a3F0cmRPWC9DRmtYRERPCjFSTEdCTWVJZEdJMFVBT2N0VVFMdG5nMm9GRFcxQW1xd3FVclpPa2ZxMkdOTWF0SGE2cFI3STZ1L0JrVnJoRmlIRnRiU0Y0YTZuQkwKSStpRjdjbGFOWC80MXRMSkszNytDVWtiekd6eDFMSkFISVBjcURBMlpaVzNwQTIxQjJyd0hqeVdXMW83UmQrSytYTzNzZDVNODhYUQpsQlBzam43ZDdCTnlKZ3JVR3hLS2FORExjRU9uZVAvcmcvVjY3ZUZiVzZjditYbTJWaUI3a29hK01pengxWTVZUFNuQmF1WkR4dm9zCmFzOUZzdjVPVWVGaThVeDhyWjVJV1VncU5MVFZLSFpMdDhNb1ZmcU11ZFI2VzNIV3dKOStUNmM4dDRmVlordjE3UEY3VXljdjZWMzMKMmNLNkhnL1grbHcrUTBBamsxbDhHNW9ad0s4RnE4N0J6UEtpY3dIT3g3SE1BR1Zsc0xvUnlsd1JlMUZ6UnpJWEdyaFNCeklYakViYQp3NWhsN29zUUVEdUtHZWtiS3h3TURtSmVrWU9CcU9ZQnc0eXdyUlhENHhCbUdFcm5VanVDR1U2SVV0SWV3QXl6OCt6SUp1YmpKVVF4CmRmaHlKZ0I5RDE2V204MGNhY2N1TXp3eXJoMjd2QUl3UU4yM1k1Y0xBVnhMaHk2WFRkWXRzY3lHWEthSVRPUGdBRnlHQ1hjcnk5cVIKeTRHUVF6cFV6WWxJcE15NmJYdnNNdEF5RWJab2h5NURGbHRTQnk2ZmpmYmU0bGhoZEFZYkdtSExuV2dXYk9SZDVDZzZHQmtoM2FtRQpQV2E1d0Erd2JwMVk1TmtxRkZadnB4TUc4SEFuR3NhNHQyTXc1SDYzQWEzY244bHJ0YWUyZHM3ZTdYSC95aWVoT2VJZXBReUNBNVFoCnpqZHdjczA3V0hKaHJFaHFxR1RoejhpQzB6REp6Q01UOTRoaytEK0M1ZGtqSUJtaWN3Y1pZMmFqaVJHS0xPdDNnOTI3RVJOU3hBMzQKWWdRZG5XQ1EwMGlaTy9CNDNpT081dzQxQmc2NFlzY3lwSEdFL25HQ000WUMzVEhHMFI3RGtjTkltclN1ZTN6eHdpeDFuWWFFNmRHdwpwUVFYTDliSUNDMDJkQU0vWiszVEJpa0daSUJaT3djM09lWmZ5VXZIRTFkN0VrY1RZeDdOS2V6QnhLWGF3allzTWZwbFJCSXozK3k2Cnh4RUxDOGtobEE0amhqRjRYVHVJdUJZYnYrSHhBZ1BiNGJweUNERm5uS09IZDlOdnY3YkJjaFp6ZmpYY2NDY2ExSmM1aG9BUmNqUXcKbVBFcFpoaHpjNmtEc1FBSEgwSnZwaE1HOEc0bkdzYTN0Mk13NEhhekFTemNuOGlJL1ptdG1iTTM4eGVIQlMzbnZBY0tkNkpqZTJtWgpIdEMvQU9LWEU0eXdDSnNsYkNORVdIYlJkV3lsRVhZQVlTYzZwdGZiYWJCZnU5a09IR3dQNUxUK3hOYksyWHY1Q3lkRy9BNGczVmNqCjBiRzhXRnRrR3c3M1RjamFXOVk5S2hoV3IxQ0hTdGloNktiMFpwd3dJblE3MFlDODNvd2hmZHV0Qmpod2V4NnYwOTdDMnpoOUxYOWYKNUdJSzg3YUhBbmVpb1hmUmoyR0UvV2JHSGc2SVgyN3h3UHBUWC9SYWNFMVNxL1IyR21FQTVIYWk0WFo3T3didDdYY2JFTUQ5bWJ4VwplMnB2NS9UZC9LVmg1c25NOXptZ2Z4dlJFYnV5T0xiRVBEWUc2b1hwcWRCelBXQi9oWmt0c1F4RXVEZDVpbmhyeHdrakVMY1REYS9iCjJqRkViN3ZaZ1BwdFQrUjEybnQ0STZjdmRza1pCcWQzWk9ENUNCZnNWQWY1SWE0MExCNGFEQ0NnN0w0NHIydVBGNFRmT1pUWWlSWHAKbGJjQmQ5Z0lJM1N2RVIzaDE5cHhFR0M3MjRnVTdBL2wxUDdnM3RMNUMzNTJyZUtqazBVODhnVEw0V2UvZVAzdXdpRWorUFhzZHk4Zgo5RUYrK2ViNVYvL3o0Yy9hZHA0UFAvdjF3N05MUjVQY3ZuajExWmNQYjU4L3ZINDMxSC9zSGwrK2ZmamppNGMvZmZYck4zLzYxbDR2Cndac2t1dTFqTitDWkovLzk0Y1h2Ly9EdTBRN3hxbSsrK1VvNjh2N3RtOWZmVy9lWEQvLys3c21WOFpMU0t5OGV2djJBZDd4NTg5TGYKVWNPNzF2ZS80Nys4K1ByZEh5Nk81WjJla1hQMyttdTc3dUtaT2YvanhSOGZmanJwTCtwWHE4N1BsVEhmVURLMnFxanBXRU9hTHA2WApvd2pwRHpzMngyN2xEVGVNMHU1MlBQZW1VZHRqL2JKZmY0bTJ1L292Y1dvT1RjbXlJOHc0ZGpndU9HVUtta1VOY2RZemlhT2ZTYnpzCmtxbGt6ZFdMVklDTFp3RUhFZUlsc25jamxTVUlBUUVKY0tHck9nNEwvMXlWNEJpa2hmaHMwR0tsSlhXMnRpdHlWS2s5Q2tkYmdkZEIKcGJtWjFNQWVxZHdGUUJJWHVoY1RzamdWWkdHQVVpL2Jjb0g1SElMdzRyWXFwTUFBbEF6RXpZNUFJQ1JMQ01haGhZQWRUd2hFQ3JYTApBSHhFcHJHY0ZiMVVlQUJLTW0wUFFnQ1F5U1NrMk41TTA3d25XSzREUmFRUTRERkJSbFB1blRRZndpa2dleXBmVFBiakxVY2lsVGErClBXQ1kwSjQycGtaTlNtREdOeWhmaXdNZ1IrcXlCcjB3TFp0U01zY2pCOHVSTmRQZzFxL01sWHRTbE02aER1ckNPK3BsQzhqU2pOSm8KZlZsdDVKQWZIRkVIT1BNcGE1Z3dqVjNJTmJGcURpOHEvOGh4c2phaEUvRVFWQVNRMVduVkJLR3NGdVlrMG1wV1ZQVUt4QzhzQ3lVNQprSDlkN2ZRSmhycXV3YXlyVEdFd3cvR21ZZGg2bGdid0RzSFRicTRPZ0E2dW5hL29DcWJXZzN1bExrWlpsQUl2Uzd0eW9TMDA0RkN4CnhZelB6QlNNL0ZrODFCNzNaR1FXS05HTkhaQ1VWdGVKbzQwMkE5QkpLQ3JMemV5S3dQTVhITThUVEVhUVd5NDBCcFZWVlM1MHh4S3AKeUsyVy8ydzJXeGpuRGhDTUplbjRwbG8xbzF4ZE1LUXp6RVVLSzAzSmp2TGdZS3N4RWI2NVJlT25Tb1FVeE1Tc20wbzdoY3V5MnRJaApaZldIQlZUT3JzdXNvMWthMFhyT0NtNWxQQVVvbEVuMEtoaUNZRUxDaUZkRE9Hb3l3aG51T0xpenEybTBHeXpwVXZmR0wxeEVLOTJZCktGNFJlbWxHbnBJTmpuVUZqeEUxaUNqL1pmV0FOc2o5U0hFQ3d4YVJkck9HMUFCd1hLTTZKWm44aExoQ2d4V1F5T1M1Z0JSR2JSdlQKa29BNGpkaWJvZUx4OCtJdWF2aUFnV09Vc1Nyb1NuajRrSWR1WTlwVVRWT29pVHZ4aFkwZHN3MGtwQkNHUGNwRGxMQzRLck56V0xCSwpLcFZLc1Bja2lVaGJCUU5mWkVqUXFtSVpMRExNSXh5Z3BraUhnckNHSHM1RUhBS0lTTElubnd1ODUyaUd3RU1RbHFSWmZaMVRhSEJNCkptZE9jektJUGhLelZXWVZEblFKVjdQa2dRUG9teVZDRUtEK3pveFhxMW9IVm9scUFMUWxiVkN2UXpJWEMybEFrWlpaRnhrSU5HTEQKdkxJQmpENzdVVlpDV0wwN1pvVFV5N2NnSnJ0TWRJRmx1S3hxaGc1ditMbDF2bXpWdEFnSERDUkdkczB5VVdEZUsxa0JDclRzbDAyVgpQWDJ6b0hjcmlGaUpodXFFcmdCblJad1YwWmxnZlFNY2YvYjhqQ0RTbWdHZ0g4NGdtMjN2RW9Md1REWDVzQjBRaHF1WXZYS0ZsMVF0ClFGUkRVQ2RWaS9xck9HNEx1NDIvR0x6TWtYMnJRTmd0Z2FGdVBHbGkwZkJsT3VIaGNGa2RoRlJ3N2dOdUJUZ09RL0ZocXlzOENhdFoKaUFQU0M4SlEyeGdYaUZIbTRySnFYOU9PSFBWNE9OSFJpMEduTnhKd2NHWmo2d1ZyRVVUYVZ0ZFY4MG5pc2hLTXdMdXRnRXZIeGw4MwpwTENVM3l1Vk9SaXB6Wklyb29vNllXS0JwU3JUa0VZR2doRkdzdUNLclhkVHY2MXM5eHpZVEIvQ3V1cThLZ3c2eXJZckY1czE0WXBKCmgyREh0ZVdoVytZaTAzTUxIRUx1MHJyL2J6QVlaUjBpeS9QRzhFV01ZU0U4QjViN2xXTVdRdW9DRWF6dm9JR0Q4Zk9zSXkyclQwRlEKekdzaGhGUWNrdDJhUWk3MHF2anBLdXVObVVyMWtUZlIrZkFLb3RzdCtsN1Nvem8vbUNtNDBMcTNJR0VpWEhMWUVDRENWYVo4WHRlVwpBMTYyTFRoYVlSU2UxYVBCWEZFRjdHSFZKQzA3NlhCdjZxTUVBTVJUZ0tYYmo0OVlORFl2RW1uS25UMXVBQTBIWktQWHdHbHVHWkg1Cm9HT1RKVFFrV0hnbHVEWUpnTVVqYVJLRkpSQVlFeHZBbFQxVUFpbytUWExZREt0NktXWWVMb1VVNWJySmg1bmF1QktlbXpDZnNNQmEKTGFUY1o1TE5ESjViTlVFZ2pLNUpYanpZbHNVMWcyaXRUUmMzd2QwTWg4VldZNEg5QkxkRktFMk82Vjh0a3hBeXFabllNVmZHUHJYUApnY0Y3aTVxblhGclJTRkhJUlVWcmFXd3F6MmhSdVNjdWpHMFJucHNkZExocUFpZ0UvWWFzdWhORlJ6cXY0dkFaWndPRTNDUlZTRm9MCnBNdW81ajZjTTR2TS95SzRhbFRhd25QL3pLeWdqNWhvZ29hbExVZXpWaUVWS2dpMFdVQk13bGpncXRsVDB5UEFEU2RVTWRlTkdibDQKWHVFQ3pxblpGTWo4MHFMN29MNVhWYjh5WmpuTldSbHhYWW5aNFkyelJIV2VZaDNYMGlXM2pkdXNlbVR3RVNPQlBLRTBsNjRRZXlzMwpTM29yL0NJKzlycjVlMFFlZW9iUGVWVmNRMFpPTFJEbUpuc1huVkxJeDB1K2duRVNNUVpwK210U3ZxSkhCVmExVHQrWXhCN1FaNWxLCmtPSlhaMGhQMENMSnhCYUQ3Qyt5V2tTRHM4dG1qZFNqWXBBTVRJbUZpOUN3T0d0V1gxMzlRZTJnejkxZWdhZ0VFQXR0VDFWRGQwSEkKbVBZSlRrcnNscGl6S2JUTFpvQlRzZlhRYmdZQ1lPV0tWOWU0a0FCeHRjejZic3JHZ3ZYMnFqckVzbWlpWlRoVTY2cm52N0dMc1BpMgo1SHpNd1ByY0hDT2RRTVVlVWZSTnkxYVhjZnFnRVhndldjZ2JRazViTGNTWFluOGk5d0dEb0swUHZwbEJMT1FwUE1KSVpXaTRPTUFPCnNKMHYyYlk5NW9TS1BPMGl1V1FDWW9GUEdjU0VaR3VJZ2VlYVFseGtVQUpqN3hHSFZ1YlNMbU5LdkdXeGtRVUIxZz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJSS9aTDBDUjNqRGNGb1hvd1FWQ1pLek4zY3FZQmxUb0ZHUVJkM0ZGUHZnRXZoaExHWGhUWkVaN0ZqRk9HWm0yNkFNbTlVTnRWbTU5TQpQdDNRRzF1TWxmY0NqVXNST1JXd1BFQ29ub2t5ckhtL2ZZT0ljUVlNZUU3WmFnVVNSSWpRM0d3SURNYm42cjJJL0FMZ1o1aTUwQ2d4CnJzd2dMWE9hOGtzbklDallkOVJHcktiK01TNEFtK1ZNK1JkQld5cUI2V2Vkd0NZcXRUcW1vbENKWnk0eUovRG9Fdy8vTWlMc0VWQkkKTkZ1cE5MMUFyV01DZVp1S0N5QldwUjlic2lFMWh4RFZheWdxWVZseFVDM0VIR0JMSVRhRHdhQS9NQmp1VVpkbkJFMGtaOVhxOUlqWgpZajZZdWRxeE9BVWRWOXBWaEFtZ3FaeFVYU051QW9NUnF4RVFOdzM1cmpFcjVHaUFNQXVuN0x3YXdoWkhDU1RxUlNvSmtETXdwV1N5CldSWEJ1bVprdmI2YWJWWWg3MnMwaVJ6TGlWeUlsaGUvRjlZWTVBRllYbGEzUkZma1NDNG1nczZXa0JLWGJRNjhuemROWEFoaXBHSzYKRWIrRjlPTWJkRzljQlFzTXdzLzZhZ25ndGppMDFLM2kwSldnVFNKbjM2cjRMazFzdG5ZNUhrTk5TRklzTHBjRmxRcHhIRTNRcEtYYwpiSG5hVURJdWpEZzNhS3pZYklzbHVTTjZPTklVcFI3eEJkQU9rVm0yWlppTkdrT0VPSExXNFZnQUNMMmdWM0VSeEdHaytVN05ONTRVClRFQXBLbW5lTHoweU9xaWV4MG5PZk9LQnpFeFo4T3laQ01BanpGdkc5TzhJbWl1YTlhYXN1clBQalhzNEVkbGc2NktJLzFVVHB0dVEKTVJ5VG92b1ZqcXR2REorVENLR0lxMlZaMWJ5TnhUZ2xDSHFBbEdwVmZobWxmYzJkWWZsZ0dlcGZiTVBGM3Nma1RnV1NrMGNZVURLRApxVUoySnd0ZzVBR1ZjR3U2U0w0aStBOWhqMUQ5Zk92VWVBK2tmY3FLdEZGZEI5YkNUZkc4ekE3dkJGZHRhTG5zdFRZMWRjR1dvaDViCkl5REdLM3RZWHlQQ0pKbzFsYURzTUh4SW5Xa25JblJ6S1NOeGt5YUJTenJsTkdlN0IzU0ZsVllnaEpQektFL0MweXBQbHkvS0FaajYKczZxeXhEQjdIa3dRZzJLZWtCRlk4NmtqNHBEWitiT2RneEJydC9HQnV1YWdySDlGZjRHU0dZUWR3YkVRaDRSOG1Nd2tBTXJzUnhvdQo4RSt1eS81S1pyelhPMVM5TXVwSlRnZ0x0QVhDTy9CSUw3cXE3Q3dCUm90aDkxdXh4d0J6VEhRcldBNzJBYjVscWhhbmk0U1ZtNTQ0ClFPY2k0S0FsSVdtOGNJdEZVMWxYYllsM0JGT2gyNXh2d0JnT2c4ZVFzZ1E5OTRDSEl1SEN6WlBKQU9hQ0JVNXE1b0VKWEZuUmtvTW0KUFJkbFcycngzYlBmVWZHbXEvSjNWRXVjR1h4V0tGQUlNVTEyZWhQdEszcGxVS2xKeFlYTUNMcUVjRnV5NDhRVEJUSmtSWVY5eHR6eQpmeVl6aTJJOVlESUNSOHk4M1VoM2ptNUdWbFVhYTVGUDE4MmZpTFBUb3luQTloZkdzbTRhNlFJS2hWdUF3dFh5ek9UcmF3OTVVWE1xCnFNdHFvVEUwcGVJdDhxYXdjMEtLUUtHVzdsZFM2Z00xYjhndmlyWjRZQkl6Q0ZXTjR1UE1URHlDd1pQdDRRQ2pXVE9KeHh3dDFRNE4KM3B4anErWUYwc0FCSlBpTTdmelh0cEt3c2RCSUNHc1dQS1VCRTV6V0hNQUtvU0dGVmYwZ3o4MHNwc2Jhc0JydUI1b1JKVU9leWFmUgp4bnhXbVBCcTdOY3RtclNYWmk5V1VuQU5USFl4V3Q1SENxSlpWNExiajNSNEdZU0hidVZwa3BzeUM0TFlFSi9IblFkTFlZVWdvR2F1Cll1dUJ4d3hpR1NDUkdLTVlFTEZjR1NNY2JNV2owZGt0OXJCd01tYUduY0lnTmlna1NUZG0zWkZ4RENWRHUyYUc1dmVVaXlLQmE3MUkKT1hjek9ZcGJlc2xHNFJTR2xMNzU3Z1UvQ1RHNGNGVkE5NGw2Z2JvekNINkFrVlFHZDZWem40NXh2aWZlUGxmMVE2UjUxaXMxSC9ScwpFZzRNYXJTM1NnL1E2dGFzYkJYbmJtNDAxOEV1eUx4WUc3UFJHcUVtaG8xdnZwWm5TejZEb0JreVpiUkNDekJCWjVadGN0Rmc4N1ZOCkd6Z0YwRkpXNlFHcG9MZ1NObU13V01iVU16azJhN1ozUXdwb0pueVpaelVQWVQ0anBuSGpPWWNhU0VwbVR5U1pzemo0UWhpbEg1SHgKSzJzVXVaMGVSZTJMQkdSVVFZUitzY3d6SkJKM2dGMk5TWnV3NUMzK2ZsMDBsY3MybDZLRTBCZFR6WHBvZEt5enZoeWNGWnM1NXhqWQpnRFd6QWF4aEJoWXMxWFhORFBkUksyeWNkUVV5RGFqMUNHWmhTU3FINkpMUHFxUUJGTWk4WG9oSUxmb1p6ampHMU1OMXdwaGpQNWdLCldXZXBNU1owdzhxRVo0VFpBVHFjRnczRnA0a0JhUmFhV1RyaTNFTkNVK2VDOTRMRUNJNVF6VlRDWUppWktkZ0p1dE1oc3h6cU1GOFcKTytXYSt4a01uQ25idG9RRnpIdTUwd0tpTHhRUUVHZWVqRlBVRmtCME16OFdlRHJ3ZlV6OUlnb1loYWNNYUtML1RXWWpDTzFlUE4rQgpXWWg4ejRXcEI2dWVWbVJNdmtVejZXd01EeEk1QS9ZaWZCS0oxMC9ScVhyTVljSkprSmxWbUU4RWVTQUNSRlVRa0xkemcwSElNOUNSCldQVm9NOWdoMlF6Q2pYQW9PUUdSdUJVaXZkVWg1RGZEdDVHckpSWm02ZHJVMUVaemZxSTh3M3hBVzlJS0t2WlVOWDhqeWV2TXpNeVIKbS9oV1RRb2lSSGlqMHltN21RVEVpc1RNSUdZNFI3SmxLTjQyOVJZUkMwQm5hSEY5bGpTNCsrRkI1VjZCcHBtdWVtYWMya2JLdXRveAp2OElJL2MyQ0JaWWhXVUtraFErdVVtRjNZRG96N2dmTk1XN21pRElIR2tTNnlMQmlSSEJ4azBIZVhjbzJ6TGNFRlFQb2NUOStkcXZ0CktKM0lYQlB3U2dwdnJLeEdHQU44cjdCQWtzQXdrVUFvUVd6WDJlbWRzeHBwMlJLVFo0S1hzV3R3UjBaSGc5THVDT3VGWmpPQmZvYVYKdmJqQVFIeDUxTnphVEpXMVZiVkNxa1NYZFlyakphbkI2Q0ZTTVpxbVd6V0ZsQjBTQUVPcU0yVjR0UFVvY0lUSll2WEFaSWpnT3owRgpCZ3VJWnMyc0NTbGxoZmZ6OGJLZSs0aHpXS3VlbmFWcHYxblBLRVhUelVXRWZiUUw5UnlVZ0FRb21GNmdLT2dxS1pEdFhLeHZBais0Cjl1SUtyMldnaUE3ZkFpQVJNUWNnTU1RZ204aUdjNC9odlZnUndPazhPNmg0QnNPYmN2WkF2Q01QbUxPTHVCMkJrTmJVcmlLMEY4UmcKMndnTXMyZ2FXamsvMDAyMWRsV0ZxUWZnUTRlUkppZjFDV3E0dzZLeUZSMXdVanZ4R0dNVFEyaC9wZWtuV01UaEJ1NjN3cDVnc29XNgpyWXhBY1FENVQ4eHJxcldRdlFGOXNVTEsxZUFqQnZjai9mcnN4a3NRMGExNVZyQXJuRUdZaW5sV095M2NJMjVnODlQc1M5U3RFYUgrCk5PaVZxT1l6cTBTM0dseE5hTFRCQkpDSkRkb0hnSm0wK0RMN3JOcW9hWFpDVkN6MkN2dXNNbzZkZU9kMW1PY0dNTTFnRVMxT2dGcm4KUGRGb21QQ1dVSUI3c09nUUZGUGdyS2NPNndUdHdFUTdkcThFWVVmZUlnRmNEeE9URTNCMFc5NWFyeXNOTzNyUndDa0NTUUgyaVlxcwpwLzF4MlpxSEV6U3VidEN5T2NxWjBCUnVNNGJLZ0lBdXhqay84M0RaU2xWOE01c1Aya0ZjTmpZakluZVJ2MkhUeTdxaHVTN3FoUWVSCmJqYmtDZ3c4djdxc2tBMEw3Qmx3Y0ppcmdvWUFnSklnSVM5dUNnR2hiZ3pVS2ZRNEZRckF0TEtYNHNlcEZNcS9CejBpYzJVbDJveHoKTk1CeHNmTVFRU2d0Z2Q5bWJ0ZG8vc095cVdPTkcxTFFaeVFPQkpmTjN2bEZoWWtGY0I0SWdpMkR5YXFpSHQ2VVo5WnVDbjI1c1htNApZRElnQkZMdk5Xc3dRSXFLU0NOaFU0UDE3R0FIbU80aFZnTHZ0M3JVSVNBUnpEdFFOR2ROVXV0MDNQcEY5TXZDOGgxV0RTK2krNTFKCnZQU2pNR28yMm4xMnF5WVRTY21zUy9CSkUrbXp3UE5UdEU4NXl0QlQzZnNHNkF6U1E5RTl0Rm82R2pBcytvOFd2WnM4S1M3YjZPL3gKUjZSZ0E4OW1XdFc1UE5QS0xmMnRjQ0VMa3NRRWIyZHhnd2cyZ2ZTSmpBb0FBYUtvaHNJc1l6dVp5cUhlemR5WWNQSGxUYk4yMGVzego5K1UvR3dCbVZpdEh2dEtWeDM3RDZXK0VhU0VuTURkcEkraTZCNTlrRWtlcnRSWFZ3WkRES2xkTm9FbVZTWGFaa2p5b2tVSTNUS2ZxCkdlTm54TWtqdjFvRkQ5elV4OEF0MXAzUVc5S3NJTEJPWmp1NmsyWitaREtqcTJxemc5WVQ1WDduWnpBMzg2MW1ndmRVOHcwOGtKdUcKNUVhQXM2WDBJenVVdGhsUWtEcFE1Und1SmVpT1VQUkFTZ1dBNno2Q2ljMHNpNHI0aHBiSGhSNGRmb0dVdlp2R1E5RGE1OG9KMVF3egprT0d6bmtRQ1FZS1cyRVY5bExocWJrbEtrUU5uMDZZb3NEQXpHNjhpdStmbnhPUndNbnRyVDIzS1BRQkdGRFVxV040eDV2VW94VUJGCkdXZjZJWjR2dHplclNMR0pFS2pWY3FwU2g0SG1VSG5HQzRGbTdLRE5UNXhnSmhYVVRyb0I4cEFxb0tXaTZVdUVJa0UxNUJIcUxvSEMKOXFnbitITFdzQlk1SWV3akJDeEZneHZDWlQwbnZ4dmkxakxqTThpY296bWRSR2FqL2ZCTW9ybVpIazJ0TFdNYXFpWkt3RkhwczhhVAp0ampTTmNMSndUT2xrRFBCUXRGbE05N25TQ2dha2djWDMyeEhncE5JcitOcUdDWGE4UUpycmVDOHRNL1Y5dEcxUENKMEFJTmNGOHQ4CnpLWVRBNGMwYThKR1R5RVd1cW12UWRWK2VHY1g1cXd5SFJQUldKVFFreDI1dFl5R1Vod3FzNUMyQXFqQnBFdXpFc3lBYU9rSGw5RisKdUtpVkJpbUI0ZDlrMC9EWmd4QldKWkEza0ZDN0RaQmdNTFJOTUZiUTdIVTB6VUs4aHBjSmJoK1lwOXQ3bWN3Sk9TRkZtM3FLalNESQpBRE9CZkFsNnJyUENrWWo5Z3A5aGVPZnhZOFh5SlBESTVFMFhsRisxQXRVVHpTb1RtZTBOWjdwdUNvUWdBYndDUnNoNWRURjBNOXhECjBPVkExQnNNamN4RVVUVGRGblVvV1hmQjhRR3doOUUyU0tzbHN3QXVXZ3ZaekxkcVdTaVlNSFV3aUFLL1JkZExNcUVjK3RhcURodGQKQ0xObWN5SjJaZW5tSG1ZbjRacGNOTHRYMVJNN2RlR3pCdmhRNUVSZisyV0V6OHdHaXd4bVZFWDNMWXZkRFh3WWhPUWFGeTJDUERwRwp6WDlNdU16emUwSFE3QjF4b2ZCOTFYWUtzSjJDUXl2QkpyRHhRdEtrS29xd1NhSk9ZR1RpUTIvcXpPUldodWhpV00wd0pMQlRBUVBLCnpJTVJjRXZJSjBoblNWQlE2c1kyRU1uazBJOUVCZ0Y4dUhLR3lZNjhhZ2o5eWxSdnlPYmJRL1RwZjRocW1XQ1lQVStjVFlvSUpTRXgKcmJaMnRGOUZOekVHRml1YWFUY1pseG9Ndm9JRFkyQW5nU2RzZFZrS2x3VXlhd1VJSTgwbkRJdUlOaU40cDVobEQrdHh6aDExTjhOTQp4VVc2YXBZRldnMnh0TG1kTVN0Q1prUWNoWmwyR2JZemdLNW1WVlVJU2t6R1pFQ2dGNUlFOTB6aEZIakloUERZRWhCUVljZVR3Uk8rCkpsdUdKVU1HeW1IV2RjWVg0em5TOEYxbU5RTEJMMWF4Z3VEdGhyY2RmakVDeW9CVkMzNVVLb2haRVczcTBZUDNEQ1lwd2lJWHZZeG0KQ2RTQWdPeVhFUkVBWXJXazNCVG0xbVIrTVJBZzZSTTQ2VEpwUmhiQVRLNUxwQmloWS9BWE1TYzFVLzh0RGFIQVphN3ZWdFVwQXcyRgorb0NpNnhaRzNCUGdCSWdQdkMvQWUzZWNWcVRXc0FZREpPUHp4cytLZWxrdERuY05LcmZxUlVsZEpraEFIaFlGUFJGK1BGNG1mQ3BxCnV3M3l1REo0WDFURmpNZDJRVDlIUTNtVW9odHVKdGR5alNYb3ZmSmlBYjFBWmNKQUNLOGdnN2JYVE1WNnJicDhuOXRJYzVVQkVNeGcKLzlWMkpRQ0NpWFdEUUw0cVRxbzduUEdFVldHUnE5YWhWN0hCSkVFb0NsZk9jenZTYVRZVmZjWnkzNmpWVUVLam5ZMEtTOVUwRG5sVAoyNFFLamFwVHJXWmQxRlBqWU1OY0ZWeEpOQzRVUXpDSTFOd25XWkdyWUNQMG9PRVVKZHdkekFiVEJRUTZKRUJ3dSs4TVVLVThMMmcwCktNMW1aVWN6MVE1ald2MW1henRVYmxNUElUQmZ0TmE3alI5Mkp0ZURHVFlNRnp2ZVhUa2o1bVdnOXI0UjFLMnVESTQwYlZYY1REQ3IKOHRhZDFKRDJkTTY0M1h2Vy9HUU5JUnd0WG9OQnk5bWxscWl1eDlVUVp0anRaK0JUMXVyaUgxWlg1bHhZSGNjSElrTjVRT1ErRkZOTApCWUJRSVNVczJyRGJJSHBUT0hpQlFnQU1Ld2liNWltaUZqNEZvd21PdG9lblZVM2hhNXNoS2cvVGtWczVJU0xOVGdtcG9BTEJPWTNKClVXaURHd0pFTnA1cys4cW12WjhKZjI1WHV4QjI5Wk12bURGUFJ2Mm5oNS85NXQzYkY2OS9mL2pKOFhqOS9QbDNydz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJZnYzbTNUUFUzY1ZiMlVra2tCWUllb0JWRkZhTVYwYXRqTU1Jczh0UXhiTHVoRmxSZlJRUW1DNGFsbE1INzlCVVJyd0VxSFZSUVlJWQpWNDE1TVZ3K3BWVkd0NFFXenJBcXVnOVd4SVcyZHQ2VFVUQlFDVGN6OHFuUDBTZ3FUbmhpNGw1djArbk8zQitxTkxrakNzZVpGUmNDClZ5Z2U2b3dqMjZEYVJCYzVUbFlGcTFmRDQ2Ymh1bXVLcm00Rm5jdzhNSFpXVVl5cFRFZ0lSaUVPK2VUQ1dYZE5VSW1uSVNXdmFqVzIKSUltZ2dSdWcwQVRodXBxR00xVUZEK1BwS1h6Q0dZbFo1NThCTEFkRGNUbDNnYU8rRW8ra1dYNEpxWUZITE5zNEZzWjdsS3VtOHVyWgowSlZFNHZoQXdHRGpjN0JSWllRU3JscVczSzVLd0RreTlkNmlWeEVSQUoxK0NScmZRQTY3R1hwRzN3eWFYT0JKZzBYZkxLblJvN2pOCkdad0tIQmJiVllOeEVITTFjM0VTNk1wYTVEQlJ6WG9rSUpaanRmUWxUWXJFazRPb1V4dlltc0IyVmtyamlIeUVZUXMxZkhlanNzcTkKSVN2N0FpUU9DdVpxU1VIMHNFaDFaTkVaZStQYTYycEpYbmdjdEdYVDhCaHY5WWNHNXZsTFFLZjdWVXhVd3hRUm1MMVVjVFl5cG9WRwpBUUNqd0dIV2ViQjJKME1acjdPeFJvaDlNSUtnRmtlUi9vaUV6M1gydE1TZ3dWS0YvVzJlRnp1eE0zUDdoeVdOclVBU1hvTyttRjQxClcxOW4wMFdpaFNvV0lBTWhwRU9hWm1ZT1FJeXJPWGlBVjREd3lTMTRVNTZucHVwTndmaVJhRTdZRExlT3pzZXBHNGdQYVpXd3E2NkIKNlN6VWd3dUJCT2R6QU9UdmNremtzQXZEZ1pFOTgraU9XZ3FubWU2dHlCa1BPWmZ4WmY1aUFNbERuYTIrR2lHb2NWa1ZjMFh6UEpxTgpNZml4SFYrRlREbFlSVEJTY3c4RUFSQUJuSlNRWjIxSDB3aXRxZ2k0NTVBcDlwSDlpV1lLcE1TQTFGUm1sV3pnRXl4b0J5bldaN01SCndla21NaGJXaDd5NWVzbVdxbExDRE9nYXozRkJoRUJkMUJYTm15MUpaVWpLRWxpTndDWHprUllGbkpNQWl3ekVoSWF4V2VpcUpWSGgKUUt5MU1WRUJUZHBzZU5FMENUUXQ2R1ZaK1JyVGVLSzdnV1dEL1I2eVBGMDRUaERCa0NZWnZjeUp5WFQreFVJcmNEb0U0SStSR0d6RgpSYmY0RXNDa0tKUzFXbjcrZkNHNHRnNEVoSHU2U3VuRW10VmtIK2ttbUl1NjZ6SDdmVDdDSGJINXdiQUF3aU9lRmhZOFFyRFdXZk9pClFSUmtaQmQwQnh5NFNjM09XUSs4Y0VrSFFKY3huSENyaW1VMHI5R1ZWakxIcmNXUmNMYmpmSGEweFNxeUNEaldhOVMxUU13Z3hxeHgKSzZ6d2pUN2l4VmVWMm5CbjErY2hrbURoYmJScTM5aEFpd1FWV0FuR2pJelpGUFVpWFRGTVdhamU3NmJSZ01pejl1QVM1enNnTVhwbQpSS0ZLdW93bmluUnJ0N2pxU0tXb3Npa0Nxa2xBQWhxNHlNbTlHV0hFQ2t0Ykwram5va2V0WStMQlEwZlFsdXp6Q3Y4Q0VqMVZUWlRSCjlBVU1zd0lEWjZRaldUVVRNcVZQSE15UXNwNTB4VTBYUm1wSURNcXVOc1hjYk5WV05RQmQ2bzVHREF0Nk5qSGVWTStUYUZJUXJFb0kKQnlSVnA2TzZCK21maHA1REF1RjVjRkM3dnNiVzZxcjFvb0w3cW1xK3BQQVZrMFd5a05JQmREUnlNSW5acHNjTnAyQnlEUFlWUFpJdQpyN3FqTzRZak9kRElJQnJFblFDS3p1Zms0Q1ZHRFN2ODBJSGRQS2lJMHltbzQ0UUU5YjliaEF1dkk5QW5MRDNtbkZRT0QrelZ5NnlaCjdEY2U2QUNLOXVpc0hJK1Via1FyYW9RUGxCWTJUV1ZLVHNIOEs2b0tSUFhJV1F4RWJydXBScHJEeXNaM0FxYU51RHVBQXkwcFBUTWoKTzZHWnhHZ2M5MXF6UVJZQ0FYbkxTSUZlc05VZTFtalUxZUpWTWUwQVR3bDZXSUxLdG51NSszUEs5Z1RhODBDWnFwTlprNmtGRGZXQwpqTXM0ZHFDTUNUSkk1ZzNER1M4RXppMmFhWW5xbnNaaVlZMXB2QWlQaDZGMkE5Mnk2a25yM1BxVjBmdlpGYXNleUtLeGhKckdrSjZ1Cm1ldUdXUTJoZjJuK2c5UXUybGE2ZXZ5aW9zbi92R2tsekhUaE5FbVE5Nkx6STNxbXhhQ3dMOEFlNlE5ajRDQW5qZHJQK0dhQW0xY2UKT3FOdWMvZ2hwTW1LQ1UrRXB3Rytrcmw1ZUM4R3oyMTh4Sll4a2VkSEppN3NhSGtsNExnbTB0c0RMN0s1bzhMTUdEYW1qcU1yS3FxNwpHNFNBN2tnOWFCTDNTbkR6SlpvS1VZY29wLzZFMVZLWHpXb0U0bFhZd25oRU5mUnBUWkdta2YwZVA0ZlFGM2o2Z2JOb0VTOHczK01rCmxzMWlDb0RPd01sQ1FLZlJUb1Z6WUdnMnBybkNyMW9zRlJkcy83TW12YXpBSzh5UWl2RmVPRmNjeG0rbVUyaEJiSXRCTElEbFYzOGQKOFBBdzdzSWgyK0lWT0lteFFIcHdQcWpvU2JoQWFmZml3ZVdhRjJUbFpnZEtRUmcrOG5ndUxRUU9salBJNEYvTVYzcHVBUzN1UG84UQpSQ2kzRmNHTEFwT241ZllWZ2xES3RaaVJqY2NzV1pDYWhxVERSdGloMTNva0djOEVObE1Zb3ZXWXNpSXIrb2FFaGNjTnEvSFVMeXRRCmFLRHB1Uk9abHVxd2VpNkhhcGtuTEtWb000WnhPSXZucDRqcU1RUjhudmxBc3UxUmhlZ0d0U2N1bk9zeFdrNUxxRnF3aFFFTHFPYnUKMlk1aGpZUGRyUUp3eVVOVHpUY09WSUllbGFyUVp4S2dMc2N4WWdBZS9aWEhBeUcyU0kvOFlob1JNT3hpWjN0eGprYW1wZWdISkcvMAowRzBlSG1vcFBia3QyM0ZMeUNyaS9Qakd4b3dSTEF6aENob2hTV1JKTXNNb3Zkd3crS2JZTlY3NjFBSGVCSkZCUnZTOEp6cFg2RlpVCllNQkt3emxOQ00wNldJVUpwcWpCaFMzT083bGR0bGpLQnh4cDBHZElVR050V2kzWFY0dk5UYW90YVl3a3BEQ3pwdlBkbGszUmRqeG0KZ2FHd0NMeExCTWtraTZnbHM4WmhnL1BjczRRd3BDdDU2RGxNeE5pK2s2bG1yQUhzSmhDVHBaMzNaeG1yMDZaNFl6Yk9sSEtiNWU5QQovSzRzdUxScERvaG0zNGFFbExBRlIzMHp5a0xKWEg4TUJhL3FBaUNhU2tjdHFzZVlpejdZSWR0WWFkbGkyZ0VneE5URGtxeXpNNE5WCk5UamtqV1hBeXJJcUxDUWpoUWVHWXpHdkRXcmtHdHRsQkd1RHFORTVGbFdmMVVYR3p6QkE1S0lHQ0wwS1JneGhUSm53WVEzK1ZKQ0YKNTZGWkxDOERRbEFoaXV1WVlXWEJDYUV4S2F5RXZZbEdWdXhOYWRIbG1zdGdnNGViaFdrM2k2cThJTkJRQUFMRG5rQ0F1d1VFTjRXQgpTRUVEYlRFYUdDSElzSHVDNEhkVDdGaFJDT0Z6MndtcExpNUluVkUxU2JuT3E4cmNJZ1FjZ2w4bHk3bkFxd0x6a1NzeEdWSWYxamxtCkc5cXFmUTZNblN2TmF3VWlJaFU4b0k0RVFyYUtoNFBDMVErSFZORVZySmNWVFJDQVkrMnE1ZDZtWVk1WldSbkl1ZXFaS3d3VG5vM1AKUWNUVm1MZk1OSmR4dHMwNWVWenJxdkJyQUluY2trTEJEUlp0T0ZZM2srU1NmbGF2RlQ1elVnZDF2ZmxWUEVNQ2dYcmNITkJNcGY5MgowVTlFSXlVTGlsUFJKU2lhQ0lwRlpBeFBOZ1JRdEN4QzBjS3QzQmZtUWhuVHBqQkVwMnFjVE1vODlWcGRuMFRIenJRbURqc01zQ1E0Cnd5SzZQMmkyQS9nWWx4Y3RpTUk4dW5rZXdoMXBGV1dnbGFzMW1OUHcrMVlMV1NLU0tKb2R3OStOOHBOSVlSdDBEb3BsZElRdjVxVmgKYUhaUzNnZ0Frb3BscTA3czZObU0wV0hnc1J4TGlubFp3MW9vMExmREFqYzlhaHRTTGIxL09MR1pSNlFWZW9jMVZ4Q3owUTdDRWd5ZApWVkU2QkJ4VGRNYk90aWx1aHArWjc2anF1dk9yRnJxQUZVWFB6endCemxNL25RcmduMVc2QngvR0lvZnFuS0tmUWltekN4T0pKMkN1CjZzWGtIbHV5SjdLYURZQ2FOVXVCTXZsWjVUWFk4UmloRERNZ0pPVjEwWEJvcG5MQS9zVjBVS1duckdJWUY0aVc3NEdHWHJTZG5VRGYKV3U3cURkcW1teDhlNTNtMkF3L2dMV0F1ZUNJR2c2WnhBa1RmTVNqME5xNmFGbzN2eStNR3dURmhUNG5aY3VtcXM0MmYxUUUzTjErdgpWdHFLZFlnR2srclJMN2g1VUF1ZHUrMVV4QTYybzZBV0pqRXNkRkFHQVpCSEZCZk9FbTVTQUVEMGxnRlVzNi9EKzBlRG5OVWlBUndGCnpUaWdBWjQwS2lERjlFU2U4d2VyUkxIVUtjemJpeTJtakxsVVNsV0JwdFhDOVhDU0VaaUhTZXlFdW95WEdaRjUwNkVQVk4yK2dZZGcKNHZOcTV3TTVRV1czcXRieFZtdWIxVHlNb0J0R1NEZkMzR0ZialZnOWl5OE1vd0Q4MUdUeVZiVk1HalVPSHRxYWxFTlVwcU93dE1SYQphU21ySGxUQWNPMjZFMHVUeHRIVmFNSDUxU0JCZ0NDU3JlRm1SUzlyTm13OEVyTVhnQWhJSE1PL0VFbk1rNVpYd3djQ3hGM05EY0xwCmlDUkZLMDEwbWs5bVM1cVNOVjJwYXNjUXBVcERZNE84a0lqRmh5R2lvQXdDWE5nNEFwVU1HeW4xNEJsaW5udGZubHN5ci9GR1l4OVIKamZBSTRqTnpGbTVKc1N0b3BxVlZNakVFUGg4bVRFVzJzVVJ2VmJaZGI3UDFTdHVKSS9Rd0gydFdJNUpIaWpIcUNKTzNNR0ZVVUdOVQpOU2VYM2k2bzFhWGFoczdMQU9tQUNSMTZLd2dVQWFzRlVmdGw5QzJEQ0E4c0E4cG05blZtWEdMVUdCSm1kc3ZkU1JoaDUwSW1MNDB2ClhEU1BCM1pYWmxSZ3gvSTR2RmhVbGRWWHN4UmlvS3FDczdseUJRb01qUm9ZaDBRc01GVTRPRm9mcTI1bXdLanFKbWNlQVZJcW55SXEKREpaV2phMjV3Nkx1eVNGcUh2VkV6VkVEVXV2WVRGRTNzWTY0TzFBQlFiVGM2ZlFyY1JzMElKSzZZbzJnN3hmY0JMSnE0QUFXSVBnNQp3SEFVWWJET21DSFBLY29VaWlWbWJQWGdRb0ZLUjlHSitLQ3FhaGllc3dYY0VPeTlXREN4YXRCMDlDd2FKY3lNRTd6U0k0azd2cnIyCllHSlZJOERQNkV5RnlrWWhDTW5YR0dRYjY0QzRRYlJYc0hlazEwdlY2RTJQMEtINjNTbGhpQzNveEtnNTBvdW0rWUdEbFc5RTRITDIKQ0J4WU45aXA4R1VsbW8xTlZZRXUyUklQcWdpVExYNk5kczVTTzhySHdyMFh5eEFCdlVnVE9FS3kzelRUa3NvbnRJWDYwYzY1YUZCKwpDRXUvZzZXUnROUXJyTVB6NVlDcHlFMm5ORUFGckxtYUxtdmROTW9aa1ovY2xRbWpZdUtOZVFUWEI0WDdZL2xEaitPekJxdW1xZmVECkpqWmtoN1hEeDRMQ3BCaWFSZUVhYVU0V0U3V29wREV6Z1JvUHErTGg5RXBtZFYySC9rZDRTMUdQdDY3aWJFQksrdE5kd09ZWkJId3UKYzBVeWZSVlkzYlphMU1pcGJIVHpHUVd2Ly9KSUN1Y1BidWl2bjR6NkpOdnV4K1haUFQzUGJkWFR2OGVzdTFpcG9kU1dkRGZ5U0tyVQpjdTdDNEl0RHJzYVV1MmxUOTZLbjNNV2hvNWovbm5GMzVUa2k2NWh3RjdvMFpyRG4yNFZHS3ZkdDZYYWhRMVU5VXJCbDI0Mk1hVmxhCnNsMTh4cU42cmwxOFhqUkRiMHUxeTJ0Q2JwbDIwVzRwVzB1MEd4bXp2a3V6QzRTaXBmU2pReEYyVmxqUE5jbHVOdVRXa0dNWG1HbUsKd0paaUY0R1NNS2g0aHQzRkVGRzdCTHNqa2ZsMWwyVEpXejI5N3BMVWZMdkxycnNrUmZLMzVMckk1UDcvc2ZkdXU3NGt5WG5mRS9RNwo3QnNEdG9HaEtzK1psMUpiaG1XM1pFR3daQW1HUWRETmxrU0RQU05RbEFtL3ZkZDNpTXlzdFhvb0Nkd0NmS0VMY25yRnJxcC9IVE1qCkk3NzRCUmRSd2RhdHpuNi8wTHBNc3VVUlpOMXFoWS9CdXMzVDZNM1ZoYVJTN0ZkamRZTjF0cW02aU5qb2c3K2d1b2hhNURJT1U1ZTkKZWVZNlNGMG00MHQ1RTNXYmkwTTJVTGNWYWUwMlQ1ZUczdDQ0M1ZiTVdRaWFMbzZUNTJIcE5vdklYaWhkdE1RUnVVQWtYZWdmQzFONApCdWxpWlNhVXdzWFJaZnF6OUlQUlpjSFFPQkRkUGtQQ2RERjA0ZkVYQzVZNGQ0NGh6V2tRZERFSGkrQi9BWFE1VmZkNitMbXMzQXBVCk1UNnhxTzk2MFhPallzbjRYQlNKemNQTzVaOHV6TjNSZVhpM3ludWFuSXVPTUpwQkRjNGRsU0dpRzVzN0hLa0lhaTU2SFdDbURHZ3UKa3V3b3lMcVp1Y3VOTlFLWnV4SXpKUUhNeForY0JTNHdHblpaTldpNUsybCtNU3dYN2hOV0p6Y3JkM2JsSVFPVml5OFlxODRnNWVLOApxL2Jkb0Z5SXN4QlBDVTR1UzJMeHFRUW5sek1jSVc0M2NaalRYaDJiazh0Wno4UmJjbkl0SWlzdlRpNDFaUGpJemNtbGdpeEpDcW4xCkpBelVMODRiWHRXMU5nMU9MdTc3WTIzb293dzQ4OHNYSlhlNXkwNVFjdlVacXcwVksxQTRJeU80ZFdOeVpaeHIwdz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJYkdHZ0dyeHZ1dGFqd2UzRzVQSmRvZHphbUZ4Nkt4UmtYYnN4OG5oamNwZnB6a0hKQmJzaDZaT2dDQlg2L2FZRjEyYmtycXhmQ2tTdQpGMEFCeUYzVCtxbUxqd3NiUHFiQTR5S3FnTnJ0b09QaU5GcTUyYmpJdUROV0lqUXVYdjFIQjZYdUZXSTVDbG9QR0hlaHFxMmx6Y1hsCnkxT0Rpb3U1RWl1K2k0a0xQcW5iK3pBODI1WWNyQ0RpdGtObjJ5TlFzKzR2ZUxoZ0Z1QkhBNGZicFBpN1liaE5ZYTlBNGFLK0Y3OFMKSkZ6OHpWKzVTSHBJVUVBc0hCemNIazJPbkl1QXMxREdEY0h0YnRRVEROenV4R0FnY0JIdnhpdHpFM0JaUzl0SEFIQ2hSL0pycjFrdApLWTEzNDIrclMzbUNmZ3ZPR2ZWOWh0K0tmMWRlN050cXZFaXdiNnVyVkFKOWk3K0Jjcm5KdC9zNEJ0OHl3UUhaczhDM3Rhb1kvdWJlCkZnK09nYjFGRkxxV0h0UmIrRkR0NlMvcUxWZEVxd1QwTm5jOTkyRGVmbmJOSWh3cEVUQW01MW5leUZzVVJTQTBaZUl0YUJGTmZGc0MKYjV1N0M5NjhXOElobnJseHQ4M2xTa0c3eFQ2Wkl0dFRPSXVWaHRwRGluV0wzK25zNUM2SWJjdGFGMXlnVzZJUkxocHVOUjAzTUxmSQpVeVB5ZEZGdVVXZGVSdDJRVzFSc00zNXN4bTEwRnJzUnR3QjQ0cFV5MGJhNVdldjVrMEs1bTI4TGhIRTVkTnVXbmIwMDNCWi9zNFQ1Cll0dFc1NWNEYmN2dkUwWGRJdHZ1UHkrd2JaMUt3Z1hZOW1PaHBNSlh3MXFhS3dodXJDMXkyNkI3QnRhMnVZWWdxTGJ3UW5IWGI2Z3QKM0poZU45SVdJd21URjBiYTRwK2JPTGliYUl0VElVL09RRnNreWxBQUhEemJWaG5MdTJtMk1QR0xNczRXZitPdUI4MFd3d2ZrSGpmTQpGZzRYNGFCbTJVS01oekJMc0d5QlZXbUNxdS9KQVRsSERySWkyVUtiaU1CRWdHeEp2eXoxeGJHbGt1SGpKZ1hHdGpvWUVCUmJnR2N4Ck9OOFFXeUFWTDRSdGRVNDVDTFlnVWZKRHZnQzJoZjFFOHViWEVqejU4WG9FdmhaL3AxVmU5TnE4eEc4TGVHMVc4Qy9RdFpuUThCZTQKdHJoMXNMbTFoZW1ydkxHMWhjclEvS0xXSW1XV1ZmVEVpbUpRbUhWZVV2MTB3TUxYQzFtTEZFUStGRnNvTXJpcUMyQXRKQVZFZXI2QQp0Ui92S3g2MWdiV1k0L1NFUE5XakdyKythYlVvTFdqandHcmgrai9qc0dvZnljOWZxRnFnMXNZNHBGcmNuSXRUKzNDWXV5bTFtSmhRCml4bVVXa2dlaTlvUnFpYU43ZWhmaUZxZ1pqRXZiRVF0MGhLaXlJcFF1NFFWZWdGcWtUbDd4Z2JVb2l6bTBHbXpaWGozbElvVVpoTDkKbElJQ2JsUHlRZE11ZFVCOW9XbVJOVVloVDVCcFVkSFcxZ0hUeHQ4M2x6WnNnYVZkV3N1Yk9BditKOWJZRjVRV0ZaVm9wQnhiMFBtdgpHMG03Lzc2SXRHRUxJQzNxZlhEVXphUDFhM2JqYUJmTHhldW0wYUo4SGk1S3dHZ2ZMb0VGeTlpYXhhMHJDaG90NUIyRVdRU045cUZXCnJMeHB0UGc2S1JJSkd1M3ZtRmJDV2pSd3RBOHJac1VDM2dNT3lqOTd1V2kwOEtyd2Q2RGF1ajd6bTBVTHNpRkFRa2JSZnJ3c0hBTTMKaXBZcXgvSW0wV0tXZnRJRzBRSm5NeEcvREJBdFJnaUdRRzRRTGJ1bnozbEF0QS9vejRqTUI0a1dxMklNakRlSVZoU3RkRGkwai9seApnYUZGa0tYTUY0VVdJV05FbFFKQ0M1bk0wMG93YURIdVBDTy9FTFNZVVRycVNrMmdCVlNBWlVOUk1JTlFVU2t2L214dUd2ck5uMlZsCnM0cVhtV25IMzFQYy9aMXF3bUZLR3hzK2k1eXlRRUxXYkRhRmRpNzBMUHl3cmlKY2NwdllkMm5XRFo2RnlCQkJtSnM3RzdiQXprSWEKTW1mYTFGbmVFS0kzams0Y0FpTzhLOEdjaFJRQVkyQWdaL0h2N0E1eEVXZGhtK3k5STBsTGNSMTM4R2FSWWMvdFJadEZucDFNV2NObQp5ekxjMUt4Wk1HeGR1YnlyK0twMUlVR2F4Y2pHUllRUnNxMG9WWDl6WnVHd0kxNFhOc1R2eUtBelpUYit2aUd6MjJiR0xKcnpQa3ppCjZqMzY3TEtHUTR1QXNLbGNGMkpXRFRGRzM0alpWRjFNdFJHemtDSUloM1VJczZrNGk3c0pzOWhLdUZHTGVmQzlNT0J4QTJacFRSY2wKRmdiaFJvTXZtMWdSMk1hYkx3dnBBMHV4cnoyN2FQNmJMMHZMek9YTmw4WHhLRi9jZkZuQlFsYzVmRmxwWWNvbndDemg0c1VKREdhNApTM1plSWdDelJLZU9OTitBV1ZxRkhEVmdGdi9Cbk9ZR3pQSS9tRjY2Q2JNQ3k1WjVDTE84T2tSbk5tR1cvNVNNNU4wUXF6eGRQUmVFCldXYWYrR0lFWVphUGhwQzBtekNMQkpGU1VTYk00ajQ5ZFpaRG1OV1RaK0xySXN3eUw4WklZUkJteVRaRlZYa2daZ21pWitqeVJzemkKQjFEMnNnbXpPQi9seDRJd3kwTXkvSG9UWmlsc1lwUXhDTFBFNFdmM0xaYk1NcHY0OVNMTVNoTFZEMkNXQm5ZT0NjQXNMT0lTM1lSWgp3bG1adnd6Q0xHS1ZpdnNHWVphQlAyVVlMOElzTTJQNmtFeVlaWXhuK1Q2eGVtMDFRWFplZ0Zrd0Y2UkJGbDhXREVRQ3N3TXZpMFpWCmFwVjI0V1ZYaStwUzgyVzUxVGgwV1J4bU9lMjU0YklJMmZEQkJsdVdnU2tWVlpndGkrdWdFdW5GbGwzTFgwS2daVmNTejIrVFpaRWMKU1BYTmxVV2NVTzNEakpXZFJiaXhUWldGQWV2TkYxUjI1bWlhWnFZc0RLSUNHaW1yN0VWNUUyVlJvcTN5VVFObEdUNUQxbjREWlpuUApiMk84Z2JLODVQejBBNVRsbytOWEdFQlpjREFSbDd4NXNzdTFpTWJKSWxiRjRMOXBzdmk3OXBzbEN3dGxKVWJKNGdDUG9tc2t5UksyCitmRTkzU0RaeEt4N1Nwc2txemk1a3d3c0lNRHNKNHppUlpMbElFWVljNUJrZWRPclhoVnhHbkNqTUtyZktGa0cxSWNhR3hJbFN3T2kKYllHU3BjRUUybzJTVlE2bTVZMlNWWnBram8yU1ZmNWx6UmRLRnFkTWVWaWdaRmx1dy9yYWFQU0ljbE44SHpkTGxpME9vQ0VJbGl5RwpkbjVyWnNueW5yQlc3bUxKMG9qNHVWbXl6SXdqQWgwc1dSaVlSTGhac2pBR2daWXNXUm9RVndtV0xBMXNTM2F4WkNsNXBLYkdNRmwyCkJNSFhIekJaREI5VUhOdzBXY2tnODl3MFdXNkYyU3Rvc2lrN1ozSFRaR0ZrZWlGb3Nsc3NLWnBzSXJ4bnRCZE5Ga2I2RWtHVHBRRmgKdktESjBzRG1NQmRORnFNMktibUd5WXJvdjFMUVpObmFHd1BXelpPRmtXV3lBWlJOcElhV3VZR3lOTENLK3dMS3lvaGtoWUd5UEE0TApEZzJVaFlFczl4c295MDRJeUxZSFVGYkZFbmtGVUpaOVFQa09YMEJaMWNZQXpXcWdMTnJWY05JT29peVV6eFMyM2tSWnlxR05rU1pSCk5sV2pmVTJVNWQ5OHB5K2tMSTJRQ0FSU1ZxcnFyQXdST1Z6OE5XcVRicVNzWk5UNnJpblFZT09mcFZwQUVtVzNRdm9teXRLdGExbUQKbkFTZUlQSFRtUW1pTEVUMFQvb0VsSVV4c1JneWlMS3dlSXA2b2dkOFBYbkdqWlJOcDMyQmtiSThGbnRHQkZJV29uaXFMRjlJV1Z5agovS3hBeW5JK1ZvTFdoRGE0TitNVFVaWktKNWFYQlZFVys2bWdMWWl5dUJFaUc5eEVXVWpZVFhVM1VSWXZGaitLQU1xbTVvNlJMNkFzCnkyV29DQW1nTEx0d0tUVnJvQ3g5Y1FDU2JxQnNpb3ozQnNyS2NuQ3lYMTM0N2R4akxIMmtGTjg0V1VFUzhxYkpZbXlsTk5vMFdUMzQKbFY0NFdScmhpd1ZPRm9lbWdEbDRzakJ3VnJwNXNreVR6N0Z4c3Z3YnMxVHdaR1hvNjhXVHhZOGhqUnc0V1g0UEFralNqOExNWUJUWgpscXNodjRZU2xjREpNaStaK3ViRVloU2FiYjVnc2hqUG5GbWtyVWRyVGFGa093YVlaNzVJc3JBOVpyNU9iVUpNaVRpeVVPZk9GMFdXCm5lZytibHRBWkx0ckE3MEovc1NRY0NOa3UxdTZCa0VXMktoSFFXSEVpTkFxRnRQRHhZOGxqSFhWTUxIWGErdWJIaHQvWC9EWWJUSTcKZG5yT0RTb3NXS2tzMHJuSXNhTXBhcnkzS1FLRUJEZzIvcjY0c2R0a2JPekh1VlBuYUdyc2ZBU3R1cUN4UWNzTlp1eDhOTFFGTW5ZKwpiaGgxRVdPeGo4bXZqTTFNQzBpQ0Z6c2Z2Ym8zTG5hNEJVN1FZcGtiVTRpS3RGalFCTWpOdTJDeGhJaVd1Vm14QVJVTlZDd0MwQjhECndRMktoUW1mVW9CaThUZHlvY0dKSFNLMzNwVFlZYWxYUUdLSHU1Y0VJNVpCNnR4ZmlOaHVEeUVRc2ZqY0prdkU1Y0dONkJad0VXSUQKR3hXQVdEUzhRVFl6K0xBOUszbC80V0ZwS25uVFliRUxrWVNHd3dMNnd3em9nY09DaUZYR1JzTWltWW1Vczhpd2dHR3hIMWw5NWJkWQo3Qk5jV0NSbUtWY3hGblo0VFhoVFlkbk9sQldtZ3NKMnJYRUNDZHNOL2J1SnNEeVJ1bm13YkROOWFMQ2RZYS82Z3NHeXVBdHZvbG13CjNjVE9RTUh1UXh3UWJIZCtMeml3dzBtVFozL0Vvc0NDU29GUUJDQ3dHSlBTaFh5ZDBTYmpRc0JDeDhYVlNXeXp0RndLQUN4SlYwUzcKSHY0cmlSV29JVk1xQ1g5MjNpUFJYNmYxQ2pmOWRSbzBHL0JYcU4xWnZXVDJLMUMyS05PN3lLK0xvZDJ5d2E5WWJiTEJnd3VDNCs4TAorN3BOcHI2QzY5SkY5aHRXam5HZGNUTmZsNkV3Z1h6Rk9veWZheUJmaVRkb2FoYTFrYTh3U2xHc2lCVjFIUXdlalNoZDZacktMK0lyCmdVaEQ4VWI1RTVCYlVVdHE1Q3Uzb056MFFyNWlDdU9vSGNoWE9KUmw4MTVuMFJPL2NhOVlKcUhTUDJpdlBKdEhHNm5pRjM3Q05LSjEKMXczQVZjQ1ZCdTVWYm9JZ3VXclRsZHdwOXNhOTJsVW9HL2NLQTBlendMM1N3UExsQy9kS1A0c25idHdyOFFrWWVvTDMrc1hsK1BHSAovNks4VjRiUXdQQzVlYTgwSXNvWXZGZUYxVXJlZ0ZkR3kwaDN2NGl2RElwQWVCTEVWMkllZUhBUlgxZlZ1M0FEWDNIQlhKMEg4SFV2CkJBUDRpckFYKzN2ZXdGZTFQcFREUXVJckRUam51Z042WFk1QXZlTjVvbFFFOEZVUnRkNDI4SlUxZlNTMVg4QlhCcFllSjdNa0srb1MKa0FmeGxmRUxacXN2NU90MkRJMTg1ZnRub1lEcTR1bktJa04vSVY5dkkyUHV5Ukh3UUw0UzZkQ1VwdG5JVndyTklXSUo1R3NpYkRmbgpqWHcxdEtLOGtLOVVDRklQWU9Tcm1HQ3RiT1FySGlFWE9UZnlGZVVaV0VJRzhSVmxSVVFtcTVwMXVhbkd6WHVGSnNsUWFYck44RWt4CkNnVHVsYkdqcWk1L0cvZkthRWRYUTJQeEJ4NzM2UXpjS3cxTmxYbW45b1hHMmpmdWxjZEJoRE53ci95MVBGKzBWOGJvMkJ2WnRGY3UKQnl2ckwxalB4d2dUYVlNSDlvb3dqUHMwc3pvSXpDYXNFUVAxeXRLSlhGK2tWeFFORVJwcjBPdVk0cllGNTNXNFNPdkd2TUpXVkpGSQpWZnR3dldoQVhvZS9oaHZ5Q2wzYU10TVZRa2pSTW9Qd09yb2lPVGZnZFRSWDI1dnZPb2FLVUUxM1JZaUZyUElMN29wK0RSaFRBKzdhCmw3dGJtdTNhSFd1NzBhNWdEaUZvRkdUWHJnaGVjRjNKS0JydGhYVmw4eEJrcGtSMXhSSHdoZ1hVbGYyUXgzZ3hYYnVyTDZPTUZUMHMKc2lyWFdiT0dNOGQ2K3lhNnN2OXdMUnZveWtMcm5EZlBGYXdvT0hrM3poVXViOVk1TVZtR0xvRkZsMEdZYTNVVjRNMXl4ZHJSemV2cApZTmE1QWJBTStlTnZ1T0EzeUpVTnpVZmVIRmYyeXNsbFkxd0JJSVZ5NWFhNG92NlJMcmNocnExcG0yQzRRdG1DdGZHTmNJVU5ybndnClhLR0lhVUt2Y2pVSnhZeHBiYnQ2dkZVbEpBTGcycXJrc05jK3JDQzY4YTJvQjJmSnU1a1Y0WVFIdmhXMUpKZy9iM29yUUJQNG5ZQzMKdHJsNzBuTE1SeFVNeWJvWHV4VTJmRHFCYm0yZVhvTGNpdk1vSnNSRnJoWXljSVRhREc1RlFuRWNkUitJQUVYaTJZMXB3WlV1MXdvQQpYSXJGRGN2cHpXeWRiaVIvSTF1WFZ6ZUJiRjN1MHhQRVZtaDZpWGk3Z0swUWpMS0kwYnhXbHRDc3RYR3QwQ2NqQUhmUldpVmozcXpXCjVUQjJvRnJ4ZC94S2tGb1ptSWVySGFUV0NLRUhxQlYxUGVBZzNKeFcxT2h4N1dsTTZ6TEdMeWl0eS9IY0c5S0tuSURlakFnb3UySTcKeEtxaU9lWHhnclRTU0t5dzVWT0pHTTFjTjZTVjRXejRxamVrbFEydUVmb0xTQ3NtZHNNQnlXaE4wWjdnWXJTZUk1blJDZ01STk1GbwpwWG9mUVltYjBRb3ZKbDRJcGFuZ2dMTjN0aG10VHMydUY2T1ZuaGRiUDVqUnlnd2tacDFndEg1eDJDSlk5ZjBZcmNzdFcxNklWa1JkCktmUVBRaXVpcmxHOHdQVU1wdlJrWXV1V3N5eFN5Z3h0eFhBQmd3cEJUV2ZGYmt5ZzNIQldGaVhLcXlhYWRRVmZOUT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJaUtzS2wxMm9WcjIyOFAxWWNoaWJvV3ZUOU5xRWhmRW5yM09CV2VtR00xOGZZRll1ZHBoYURqQXJRM2l1NGR0NFZZYnhXQnRwTGl2UApDN1hTbTh0S0I1ZmtyYzg3OWlxZHZkb2NzeUptam04Ynkwb0xPN0hlV0ZidXdGb1lZMWw1MFpoTWpHWGRmOTlZVm9aeldhMXZMS3NxCmMyZmRXRmJtcjFoZGNHRlpHZnhtZzdnUnNyVkh5czdnc3RMenpRS3NucHBDZUU0NWJTd3JZdThzT2pLVmxmOWVoSURkVkZhZUVlNWMKVUZsVDlCRUlLaXZGK3hoRWJ5b3JqZnlvVFdXbGdVT0txYXdjekZBN2NGTlo2ZTNodklQS0NnT3JZNFBLQ2dQVjVqZVZsWXRLanYyaQpzbExac0JReTBxOWpkSmhLQ0cwcUt4ZVp1R3VCWmNWN0RkRitVRm1WV3l6MVJXVWxUd0pJZzZDeUtzR2QrcWF5SWpiUE1lV21zaExvCjBRUmQxVmdJeHQ0OFVGWkY5RFZlYlNvclZRd0lGQVdWbFlZaUNpc0NXTnlyS25Cekt0cWhpZUs5TnBXVlhMcWtZTjRRNGkzcEJ0MVUKVmdnZUVPb0xLQ3R6YmZNd1dhR0RZZERpWnJLQ3A4WDA5TjRLbmdPQkM0YXlJdTNCOStPR3NzTEk1cXlDc2dJR1JIY2dvS3hJenpCQwpkRU5aeVI3RGtqT2dyRERRWXdzb0t3MlFTdDlRVmdKNldNOXZLS3VJUFI5M002Q3MzSTIxTGhlVU5UVzdhUUZsUlYwcmx4VUJaVlh6CmJqQ0VENVNWYVN2V214akt5cE91OGdVSVpRVytqYUNBaThrS20zN0xUTlpFVWsydG04bEtBK0ltTjVNVlJtTEhnc25LclhqOVpyTHkKd0VtYytNMWtSZjVQbkFveldmRmtpYThLSnVzMjNFeldZelNURlFkblFDSm9xNG42NGp4ZlRGWmtUT2wwN2EyeUZqQ0JaTjEvWDBUVwpzQVdRTlJIU0JTbUNnYXo3WmJ5QnJNeDhvaTQ3Z0t3c0ZVWnlPb0NzVEdPWmdIKzZ1OE80NUo3cEMwWVNDNnVTQUxJeUdhVy9kNTBEClgvU3FtOTlVZ3Z5SUIwRkVBLzRWNjhlYngwcThWWk1QcWV4eENkWmJBRmtad3BrM2o1Vkl3Q2RMVWpQMGFhZ3FQbmlzL0ppY1BMNDYKVEZTNUJ1YXg0dStPQkY3d1dHR2dwM1B6V0dGa29ENTRyRFM0R1NsNXJEUThPazQ5MzBwV210UkVWbVpYS1hJS0lpdjBhaXlKZXhGWgo4Wmk3T2VzRVlyS0NmNmdmS0lHc2xHOFJHSElCV2FrVHlJS2c2NHZHK2xuWlhBTlo4ZGF4ODlPTHlBb3IrY0NieUVvTEhzbEdzdEpDClFkdU5aT1h4Mk9vOGtLeFV6TEVxTlpDc3NEQnVkU0ZaNldBeSsya2tLd2xXZytxaklJY2treUl2SnV1MmJTWXJKeVBxTW9QSnluczEKeXljb0s2eDZwUXhsaFlHTC9RMWw1U2JVek54UVZscjVoQUxLS3Z6WE1LWlZ1TTZzRWU4RlphV0k3akZycVd2bTdCb3FOcFNWakFLbAo4eThvS3dPMnJFOE9LQ3RySUtsVk0yNlZiejJ2KzRLeWN0R1NqbTFGTDdSQUFHN0RUV1E5Um9OMjBNSkMxQkR6V0wvNDJOL1ZqZis0CkUxV1lpWU5qVlZWNTJqUldlT3RjWVFXTUZaSGNYdDhzVmdTSldGNXJGR3VYdW5xVFdLZEsyRjhnMWcvYklERFZTRlhJSmhBc0RCQXIKVnMxbzBITnpXTEdPNi9jdUErckVEV0cxM3ZQRllFVXRJSW9FakdCRmV3MXhTRVJnUmJ5eXBmb0NzRUp5alFwNzgxY1Ira0xOYk5CTgpnWldGL09QR3I4STJpV1FWZlJWQnVtZHU5aXBVOVNpV3VkR3JVR2Vqd0NMUXEvaDdJY1J2OGlyK1JubnZUVjRsdlBialF3anlLclRuClVERHNVNFBJbHF1Umd4SmpNZ1J1c2JDclVGY1hmTzB1N1FLVmJ1RUdYZEJWS0crSWw5YUFXcGJHOTBDdWxzNlcyQmR2RlI1RXhuTEIKdUZYMlhudktwcTJDYWxlZitZYXRsaUk5d0dhdFFndmVpR1IxU1FRTUtDQjZrVlpoaEdRMFFLdUZoUHQ4T0tzRkViVlMzcGpWc3FRawpKV1VWbVZBeU5mMmlJTlhNRmdrWFk3V2FMUmVJVmNiQTF0aUVWU3pZdWE2OUFLdUlUcERuRXJHcXg5UUM0MVc3UlZZM1hSVTI2b3dNClY4VStTNWxKUlVvZjh3Y3V0R3F6eENuSXFzM3Q1d0tzMm1LcU9XQlZxRTRZU0RkWUZaSUl2S2ZHcXNJNUhPa0ZWVVVnbEFrdU0xV0gKRVRPQlZCMHM5SHNCVlpFQXo2S2xrcWM2alBZSm5DcDJBWUxncHFsQzZjTDhsMkdxbU0zQlh6WkxGVElzMWlsY0tGVmNIaWJwSUtuaQo4cGxCZFhnUzRVT0VXbStPS2dKdjBDMEdScFhaYXBFVG1hM3JLdFMvR2FyZFBQOUFxRWFKY0JCVXUrdi9iNEFxR3g2VXRmbXB1K2pUCitOVEMwdVQwb3FkV002OENuZ3JJSjlKalpxZTJzdkdSRzUxS2tFU3VtNXphM0ZZb3dLbk5Lc1dibXdwYkVRQ1ZxbHBTSlhyZTFOUlcKQkpxL3FhblZBdnVBcHNLTFl0YzBKODlRL2NTZWZoY3lsYUtEVVRZeEZhUU9mRGdHcHVJT1FFbDU4MUpoZTFiZnVGVGdGNmZFTjZTbApGcVkrMGd1V0dpaVZZS1VXbGwvblFLWGl6NHlydWtpcHVCdVBoRU1FcFJhOGVpS1pNTUZmV0psZlhwaFVWUDJ3N2t1RkZTVnIrZ2hJCkttcUt5SFc5R0ttc0FjaHRJMUpGdmxxYmtJbzhaUjh2UG1ydUtySU1QQ3IrQmpRazZLajQrNm5yQlVmTnlJTTg3YkJSUVJaU2grbkIKcHJDVkgvcE5SaVVnM1RhQVVTSGdRSTR2d0toNFdiTUFwNXVMMm9BMmVnNFdGWE1qbk1LZ29pSSs5SWlsdXFHb2l6WGo2VEJSS3lzdQpEeElWNDVaZW8wTkViY0xkYkNKcThwUnBJQ3BlaFpwZU9GUzhRWEEzZ29ZSzNTVFNGMEZEeGF6SXk3MWdxS0FrUVA4UUxGUUVFSldpCnhFU0RMSHNlbjBpb1VNKzBBMExGbWwwL1F2SUxXMitXL01hZ2ZyeFlnRTFzQ3FvRm94dUNDbDF2TFc4R0tzSVBwUjRFS3FhT2NnQ28KeUhDbS91YWZJdnE4NXNHZjRsVmwyd0RUVHo4bS9xcUt5QU0vTFhKZWduMmFQVE1IK3BSbHZYaUxMdklweEJWOWJPNHBGaEFrNFFYMgpGS1ZBVC9wRVBRVUF2bTdvS1JKbGoxd3FESjFjdWxBdGV6RlBKVlZnWTNaSlloRkRkS3RsS1VjZWxycktvZHpRVTBUVFJZczI5UlFHCm9lRU41L25pNTM1UEp4ckQ1dkQ2TktDblVBVkFjUlhNMDBBbkJmSVV1YXNwU3VrbW5tSWxEaGxrQUU5UjhneWhTUEJPU1J5bzY0VTcKeFNnRVg4RzAwMlpTWThCTzhmZVlMOVJwYzRZN1NLZklEaUx1SHFCVDBrTWc0NzA0cDYySk5oS0JZaUNTNEcwRXdCVEFEK2VuTnVWMApwcDBCcFEyTFpkNEJWUzBDVFlQVitJMDRoUTAzTWdpbnk1aDlBMDVCaHRTL2Jyd3BJdG1ZOVlKdWlpb1lCcmE4RFJKbnlDemRiTk01CjFkWXowS1lCOWd4bTZaaEtWOXhnVTRUWDRGK0VyVThKUVFOckduL2ZWTk93QmRSMGVIWU5YT25vV2tUZVRGTW9MSXZrVzdUTnBOQnEKRUUzMzN4ZlFOR3pCTXlXRGRkV05NeDFHS053MFUwaE1TVEl4ekJSQU13UjZBMlpLd0ZrZUw1YnBjQy9hUUprT2ExMENYSXBYdStUeApBcGtDQm1yOEtZVmUwelR2d0poQzZSYlV6cUNZSXI1V0ZDOWtMSUovbDBNeFpSd3h2UmltMDVDWlFKaE90OW9KZ3FrYjI5NzhVc1lzCld3MThLWE9jSWlkeHJsTU90Tjd3MHFEMkJMdDBUamVEQ1hUcDlNZjNJcGRPWmlPbDMzSlJFa3RjQWx5S3Y3UEN4WnRiU2x1YUcxc0sKMWl0RUMwRXRSWDBLZkxBYldycE94ekJXZGhFRk8xSXdTMW5sa1YvRVV1Q0JFQVVLWUNuZU9lbm16U3VOMXI4dlhPa01BR1BRU2xlcwpWZ0pXeXJLck50K3NVcE5vTjZrVTU3UDY0WlNpNklTVmVEZW1GR0ZCb3Fvejc1UEtFZ0pTdW85d0lVcG41QkJOS0oxT3NRU1BORDd0Cm0wOEsyYVBvcDhLVHpxUWJ0N0dqbzVwQWR0TkplNkJDdzRoTUMvR2JacE5TUFR6ckcwMDYzSlI2azBuSnhlamxnRW5IYytwUFE5bzcKbmxPQktyVGE0ODRlUVNYOU1LZ3U4NGFTZGtkN041TzBQMUU0NmJMYmJiaUlwTWNtSUdrM29HVURNVWRXZWMrTFIwckpTdjIyYWFRbwpLd21NRzUySzZ1cW5GNHUwcnFnTU5vcVUrdlFlSFpla0xJOFdUenRuaU94T0tvb0E2dGd6eUh1bWFsUW52bDhRMG1hOXdHYVFJcW5WCjJpYVFqcURnM2dCUzVBOGhJdy8rS0ZkRDNnaTFvU1FnbW9vV0tncVVBeFdEN2xoZVc3TlVUSnM5V3JONldyelFveFdMNndDWjRrYkQKb0pwb2swZXI1WHczZUJRTjczVFdUclpGcmlld281K2RreCsvbytQelg2bWpmeXQxbFAyWEtETEFTUG94eGtveVc1cTcwNUpxMTVQYgpNaTF0eDFjVUt3bkd5MmtJU1RDMFdYTzZ1enNodWs4Y2YycDFCYXl1RUwycmEwbW90VWh5ZVJqY2hxYldJOUs2Y3c3RDRJMzFYc1kvCnNhajBPYlFBZ1psQm84TnlDQlpxdk1TeWZvUUxVS3VxUlYrem5UMGZKR3lKQ2NmS2hDdEtrOHZUbUxJSTU3d2M1SXVyYkducVRDWlkKT3FRTVkvV1IrRTVqVklFT2lyd3UxT0xDSjlUeTBXMEZNZ3RiY1JjSUJ4NXErNnppelpiazZLQ1Q3V2x5ZjZ4WmdaelVYRUlKQyt2ZgptV2tsTkF1V2tzNmVoTWpSMnJwSXcrNWJnVFVwTGhnV3lqZ1JnWjRCNW0vdUtjMG1yMWoyc20zNzR0bGlQYzlBKzZOUmt3MWxJLy9lCnEvS3BiQTNIeW5rV01pVjEwUDJZaXh2YmhaUEppSDV5VWJ6RUh1T2RDMGpNSXl4cnFPSTNveXN4V2FQc3YwUGxPMUErc1ZCU201NXEKNjNCL0hTWmRZRUhva245WC9WMERza2pqVkVQdHhsWTUzYzIxWU9rdEd2NGdza3hMTDZjNXZBQnRrTkJTUzRWSHJqWk1qemlncVR1dgpyajBqSjhibFJkSjJEenRoUUJHYjJTRWJqZGZaamFvcjE0NlhvRTVEcGRCemdEQlVQaWFFcnRGMVFDZzFURC9FRjB5WGpPRVc4bkVyCndXQXhLRytzV2xRTlIwb1JqK1czT3YwcDBaTGliT0gxZVQ5a1B2QTNpMUo0ZkZiL3c0SUJIcFlVMkFCOHQ2eW14aFAzOGJzS3kvQlcKRkR3QlVOV25wTXpqa29wdzlRUDFORHNRNC9OU25XNm1wUzUyVkhaZktIdzJZM3F5aHBXQ0RWcEI5S05sOEJjNmtWVTB3S25PcktqWQpUUWNmbFVMRFNnaFRWaEcvRHNXd1gzN2M0MUtXcVBSZUx2Q0VWZG80RWsxTjExZUpPQzRJdXEwc1dZN2pGVS9Jb0hFODlZOUVySVF0CkJ6QXFaM1dsWm5OME1OMzNyYVVWekZGWSticnd2V0dPRGt2bXhaYmRybC9rTm8rakVHeUR6VzZPUTJFb2JpZTBQMzRoOFkxcmtqNm0KTWM4b3dnN09LSDVtM3djR0RPazdJRjZCcDFNNStBM0RWVUFMd09pZzZ5ekV1ckg3QW1vM3NLTTJtNGJBOGJIeU8xZ3FRTkV6V2RKdwpKUzU0a2p0U2t4Y0R6WVArbHRBTmY2ZTRPNCsxdG9tUVhlOG56RW5zNTZyNmZlU2YvVHdvZXVBM2hlZTdXMEhnUk9uM0xFdUQwMXpDCkxPa0NrMzhReTFzeW5BQ0hXQVF6c25URmJ4RllFSURuODV1UG4rUlhpUVp5S3o5Ky96anRRU2YySkwzZ090YkFnT09xV0xiWEpFb0kKVnVUOThKdHFQem1ZeVM2MHlNZWs1bXpGbnRuNERDaXgyR05zRllQNndheGxiNjVaQWl5WmxLelV1T1V1SEhqSmxLeGpzZDNRUUZjUQprTU00cithZ3JWOGpIcXhVUERaeXUvRzN6Z0h6Q2ZrbzNkSmZXa3BNMGFoZDVMcUdWaVRadXh1NzhVZ01FWEllaVY4c2dXM29XY215ClJKK1pYNFpieTdFbkMxWW1pQlFoVm9XM2xZaGlEajZqUzYzTzdpWHNGMENRYnRORC96alM0ay9xdlVQTUFQSVR6ZTNEYnhVNTZ1d0UKRVBYbncwTXhCRnNmbzZnZkwyWjcrUk5ZZWFCYWFEaU1DOHZIeUsvVEp6dUZmZDlXMThNa1FTSThrVFdUbnpuK0E0YkhiOEhIRGEyeQpKTDdzaGVITi9aTStISlE5dVBIVnhCMkFxNXZQbnFWaWliRXRYeVFFK1hwN2t1RWIxV0p3M2d6a1ErZ3Jza2RORlNMNTUzQXB1U3lCCmxYVXNzS1FzSi9OaitIQXowSmN6R2lGUVNYbnBWbVMxdENMRDcxY2JwUlNCZGVJbEl6QXhhMG1kcUdQdDdvS2QyRmYzOGxMVWh4ZFcKUWdwZ1lXMHNjQlYweG1DaDMxc0VkZk9lVFJoNnJaeVRlakdKRmZTeHRsNjl5STl3NXhOYjVGS0Jvekx6dFIxQ3FIeC9TelhkS3JvWgpraS9RUnJpT3pRMW9VWUgxdUxWRkk1bWNQYit5bTFSQmRRUkYyZWhSM2djVkpGOU02TXo0aEhxZ2FtNExrV0RhTStadzBocTdGR29mCkE1dXVxaWg4Z2ZOZmNyTVlxOFQ1TS8vMXN4M2RRc3dUeVBsdG1IREFxYWVaNDNsWnB0Qnc0U0lUVllHUGgxck5GZz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJSFp1eFpxNXNwVmhVMVVIV3hneVZXSGRiRWpUaVh1eWloZi9BODRXRlZLL3Vya25jTWI0aDF0dXcvV0ZSTW9lV0lzUGlDOWRkK3FlTwpyZEVqcXdWMmkzMDBLT0lNTGo4Ri9sa3RUeGRIMHVHYzZoNmROS25YakdFQmYydENRc3FTelRxenlXV0RKSys2OTlQNWt4eis2UGg5Ckx2ZDhtZXJYU1lrcUxXMnZBWVpmT3haT1BSS29VUkNNampVMVY0MU42cW5kbldENjBhc0hpa2YxYmxVMUYyR3hPQnVwY0R4a3Q4MGwKcWVmVGg1MVZKREFmdDA1YkpEaHgzWlRWSldlaHowSmliaTFwTXVvdEpJTE12dUpMUnNkVVlpaVE3R1MzbnlPMURDb1RaTWhyQzlXbQpwVW1UMGZDa0pyL1R6VllaYU9HZUxESkdKSHQvSkRYRkkraGUyWkJ1eW04RDhWZXNRR3YyUGVzSVBBVUFwamkwdzVtNDlQaVFOVVl5CkJ3VjlGVlhNMjZMc1UxWHEvbXlIVlJWblFjQjA2cU1JbHQ2ZmZybXJGb045M08vbFR3bFJKRG9TVkZlU0cwVGRwK2dzOFRrajFjUkEKR1FBcUkzaHUxVEFXSnY5SUhFcTVCM2FsN0QybHZpTzBoUUhrYWRZMFhxUktQQXZZY0VzV092emEwOTRmbENqNWNRa29heS9BTzhpcwpIWEVkSkY0THJoWjVYeW5oS21yRFU1S09UMEtBVy84bzJNZ3laYm9IZTRoRldESjhobWF3SHFzRDZEUGszSUtLMXo3NURGUnFWSGtwCm81cTdwMGdqa2J2VGJaZndqVFRyb3JSajk2QklLY0RrODFZamVNclVlUTdiZ2dhZU1mUWM2L1NpRlZGWGptS0E4M1BhaGw1U0hxRXQKZXRIRFp6dmJlWFZGcHdsNTY4dENXWWwzREdOcm9oWVRpOGNmd0tmT0JlOStYVUVxZ1JPaEo0bGdQRTZXZlhTYk9EdWRrdGhzaU5GagpVajd2Vjl2bDd5eHMwejJMZU92eTN4UjFQY1JJVGxucXhzWGdzVlU5Sk5KZWhlTnBlclJVRmRCU3U1Mi9QZHFCOUVWbm14TDJxU29CCjl3VmJwb0VpZmxVTVRFbzFlcmdoRjA2cUc5UmsrckttdXBnVUxoMVlJc0VoREVUYkhXTEI5MGdmaEtGTkJsWWgrb1lRbUJiditlRVYKNlFkempjYXNXSnB3dUs2VVpJcWpCRkFMREE4bGNOaVJzU1d5UnZaM0JjZ1JoZkpKcEdrMk1lMTJyTkxqdVA2Z3l3dE1TeXc2S0NrZQpmai9zSWliN3FSRDBONDU5MlpNNkFrTWw2dHNRTnFha0dwMXZOUElqUU0zN0NrYm5sRitnR0RGSkJWdk1qcWlQUExLVzdJcm9FZUh0Cm0wa3V5KzdVbGtmb2dHRWQxVDRmVy9JV3Z6MTBBaC81TUlyYXdZSm5wdkg4OFRCRDRWT1ZBSnoxRFA0RUN1ZWowZTFIOUQzd2JDdXAKQlJTUkluYlFteitWNWp0V0l0VXpJc2dDZ2c3Zk1sZ2Z0MWNYdTRvNnlhVEhwU1VUOGZreHZ0WUlmN0p1R29NY0FXbGRGczNzTEpzZwpNZlRxOGNOWG5EY0dEenMxa2NONm9lb1lpd2l2SGZwangza0ZOQTdOeFpScUFacHRhSGgwbjU4cUxTbm11WjNzb0NWV1pidkptcmZiCnFaUEJzUE5sZ1pSdjlmQW50eFh4WVhvK3cwZ1hWQUx5WmZ6czVNY2FJS05PdXFJNkhzSTh2Sk5xa1lYTUUzdXhkd1hBY2hERU1xWUgKek50WjMrRGlOcHlpdVd5dnhybmxZaGxJeGdjMmliMW9lazAvdmdINUFMbk1IZDJFc1l1T2dmRUluUkdnT0N2a2xLaldFb2FLQkJBTgoxWmNQSzdYb3J4M0pRT1B4ay83T1ZLM0FiUWs5TFE3UDhuYktEeEZPd051OUdETEtVZ0hqSXVCOEkwekttWVNYQ0NqVzRHYndhSGxCCkpnaGxLcGJ3azhVdHgza0sxUnBKV0lWZzRBVWdwZ0ZHTHRzWUY0UHRZRUVyRlJwMmp5K29wL0NPOFhEb1ZseTYwcENaZmhmQ3FMQ2cKSm9BWDJhd1M0Uzh5WUY2YXBnVnN4KzVVT2xmOGI1WG1FQmZMd0pGMmRHa2JubmRMRkc3QzI2WHFjMEJOakFmZkFqR3dSSXptbmtSUgo4NUtTWHNlY3ExQUtrQXFpYzFCV0tsY1h1V08vZkxORUkxaEtSZkRKTEQ3QlIrNTBSajBFdys0WUk2dFQrckFxbGd3ckF0eXdLSTZNClBpc1FLTUhBakFBSGo1WDJqblFxYzNWREVCNEtDMGRZV0hqSW4wekxCc3V2TWpXcE9NV2FsTFptSkozQmZyNWtET1VPVlVqalRyTUIKV29UODlvYzBFTDVRbkE3Z2FDTERHWjBxVWl6RE1sWWc5b3ZEMUNpN2U1Z3VLQ0xsY1RNK3kwZmR6bUhvdWU3OUdHWnZpdVp4RzRiRgpRYUJRWUsxWWgwc21SV0RTbncyUUtPcFRvRGp4MGxpeEVFNWlGSnJ2SlI3eHNKd1BjZWNlMlFVQ1J0bHFHR0UwaEZSYlJwd0kzYTE0CnFvUWVSOStCSkJJampSQkZzdjB0c1VPd2NPS0dHSWowSXZqS0pWakswQU5CazVIcGU3UGRzUjB3V056cElQa0ZoaCsvRzBJUlJjSkcKNGxuTHJDeTI3VkNxcUxEeGVKY0tLTWRJek12RXhSTXNBdnhQemRwVGl1WEhEaEVicWo1WnFSM0dEM2UwY0E3RlRCbDlWSjlYcDRBNgovSEgxb3NXckNjOXVmOCt3RHNic3B3cGxlU3dGdkRGZ1JKUGFvUjF6dkR4cys3QVl2YVNQd2J0U0krZUVjWVl2Q0hsRmVFYVBWUjg1CkcyekdwQk5DV3JnSEJLNDgwSWJ4WVQwZThnRlZXVEhXb1lzZEk5K1BPYlE1dVZVUis4NFc3VWdTR2U5U01rT0tWbkxVY1dXTG1aUEgKc0xYSFJMYk1ibmhqMkpMUHA4VysyTHhMT1MzblYzUU9VMW1yWXNUTkE1R0UyenZoNVJKSEdpVmovQ1NReldJT2ZpNlA4Ymc5YkNSSwp2bkowNHNXSGx0ek5PV01TOUxjb0MyV1UvRjRyazBOUTBVVjdBSkMxdEU1MUVVT204cmRvTzdZZjRUYU1mS0I3NHc3RlEwQU90M2hGCmJ4VW9ZUmlhVzQ3bFlPanRqOFlETmliUW80U3NudTJ5bThCUm1WRHU0ZWd0SjdyaS9CVi9jU2R5aWpHcUNtRmpkQ3pSWWhZV3FBZGgKVVIzWExLZTZFMVo1SWxQcW1FeXR0cnRvNzU4a0NZUXRzM3ZNemhCc2N4VktPU0xlVC9oYlU0K2NpNGlNU0ZIbU5VSStxOTNRTFlkOQprNk1iSnZnZk5YdllhM2hMWVdtUHR1bWhrSmQxNnBheDB3RVBOYm9zSE9Qd2U2M29GOWY1eFNyQkp4SlRLK0c5UmNpSEl4UUtDZVVmCmRIVld4TGx6RzdsS1U1Ri9waFRnem1RU0NKMEVvdTlFTnNqU2dKSWlrZ1ByTEYzakRRdW1ZRm5QZEhLUGVUVnNzNW9ubDNoekNJU0IKLzRFRkYyY2FOc2h5VnJCandRWkw3MDVQVTZtc1BhMnZoSlZkVzNCTkpIbHh4S3FrbUdVVjR5dHA1N3dqSEVLaG5xQ201aVNGT0FsOQpJNHdreUdaa1JIN2EwR1RROCs0ZkJrMmlqV3JNeXZTK3BscjJ4WUNoTVUrN2JnMCtlcElOcGFRWmdlYVJHcWVwUjRzNC9tS21LMEk0CmdYK3hHZnlBbkNHckUra1NLczN2WmltWkRhbTZoMWVFVXVVVEdzeUNTWUVMSTdvc0hLblo1NGJ1TWpsYStNVU5mczRoNDhYQVZCQUwKd1Z1MkhuaEx1U2pmblJtWXhidU04aHZYdE1EWUpoNFRmRlZVUFdRR1psSFh3ODFzR1J3R0VBc3FWb2RsYWlpeXRtdDh5MGhnbXJKOApMQi8xays5VndWNHZZTEN2ZTJuOThTcHp2WUFxcCtxVzB1d1pCd3R4VTRqcXkrbER6cDFwbTY2S1FBM1p5UTA5Ui9lY2tGaU5oTDlaCmJBMERwek51TWNiYSt6MXdZbWh0bm9XV3MvNE1CTkNnMUYwL2F4N3NTQ0VDdzkycktYR3FoQVdiZURGZC9RaThVVjJiSXFkbVNuYkkKc0hwU0lwcjEzVWpjVVVjSkIyT244ZzUrSExKWlpadGp1MUhqM3FDTkR1SWh3eFF6Q2o5cXhGMWg1YjFHSVZqMmR1SVFFeVR6eU5JaQpGTGpkZmxnNTFhb0JkeDZSQkx1Mll5NkxHYmorNkVscXoyYWlDTVRuakdBSE1CYmhOQVhGZ0tYa2ZCTVdPVkpGenMvWmJqV0RUcUQrCjREUGNGc0JyVXNEWWp2VnhGNW5sK1ozMTVUeHQ4Qks0YUE2TDdtMVJvUDVzbHQzWG9FWnQ0N1l3Y2g3NGdHT3RiSStjU1BqMW1RMDIKdVovVEFkUTJUNHBZZVU1S2pOdzZBQloyRzJDZVFpazZrdzhSU1Q0Um9obmRjeEZmN3Q2T2ZSZGhZYU1mNXU1WERrdEUwV2Mxb2FZdApkZXBoQmh3dUFLb0IwYXdJZVFuQzh4Q0VacDZHMFFpa1FoZzJhaEdjZ1dYNlpGbUx4WHcrRXpWcVUrNjNZT2xieFlOREp6am1MeGtLCmgyd0hFMUhhL1hsUkN4Zk9XMkliWHpjOVY5SVZWQSttSDBuMktqcFhkWlhBc1dwQWxJWmNGbWFveVBKbkRoVWhwdExsYU9LcStZbVQKZStncmJGSklJeUNWOVh1UGtCL01QZkEvWUZGb0ZHOUxqay9rTWJFWERHd0lFN2dkRmR1YW9CUXVwcm9WaG5MdHFBdzNyQWdOa1ZMYwpSRkNmVlBQQXdrWXg2RDEwOHB2ZGpaQnJjUWlNRFphVFV4bFVIdTBHeCtnTGtnTzlQY3dwaGRYYmVSRkh5MHdLWkdmSmx3RjlDR0pPCkVKUnBaVFlOb1cwT0dMQ3dtcnN2QTRvU0tRMDdxTFJFUEsvUzd0QlNvc0tmYit0OXFIUnViQ1NBNGJhanlEZU55SUhSeVVqM0NCSVcKWGlhVFRtN1RrclNka1JrQ2ZDSWNpV29TRGlWaDBRaGlrTzdaYmczWHhaS3loYk9BVW1adFlsVGJnNVp4K1VXWlJGckVUY0IvUE5Jcwo2Q285dGNlT1JCR0pFYy9sWlltZU10bEphTWoxT2Rkd214aXowTUtBeXdFK2twRzlhbWRmbWNubC9qR0FEQnNkeEMvcnN1cVRqVWZtCmNKaTNhQW9hRWI5OXVtdlNNS0dKTklFUTNHaGVvSEs4S0tHSVNSdGIwQlc1akNXVk8rdzR5QWVMRVFodTRBcUxVcjNZcGg1TkZEbHAKUEJ3ZEoxaThIeWNXR2pneVpKRE12TnR1UHpRaU9sTEVVbVAwSE9velNjRndBMW1DNnA1UnVFWTFla0orSHplYnkwSGVhTWFCNGRiawpwSzRLdUYzRmJidHA1SVd6VXIzSys2UGdqUllzOWFnaG8wNUxFZmF5OTFTdGRkRWJSUU5IVjhUVEtUWExGbzlTRTFCYi9PVGpma1QxCmlRNXZ6c3NoVlBsSXpQZDJzMzc4NGI4bzVKZUJTREtYMlk3VVFBaGFsYTN0RnBVeFBKbVM1VHJGb2NoNS9vNGxxb1JVa0o0K1hkRVYKSHg5cTRhYm9sZDRRc0YreXV5YlRzU3RURXRnUEIxM0JMUzJVZTlieU9STXgwT1dMblRod1VhMFAwNFdMN0laSU5iY2RJNjEyYnRvcgpSbG9WaXFMV2lKMEI4UU1VUjZBSUZ4bTNYS0xNSHBiK25GQ25SSDJzVGs1eWUxa2pnVkwyeG9XbytsTlRackgyUllaenpNNjVqQm9rCnIvM0JKK0Nna04wTU9yR3dPODM5NXRnNk5TWFJ3b3dERkN5c0w4K1A5WFRVdEpTeDkxUVpSSEVraXVKY050b2dmWGhLbktzUmhzWGwKTTF6bUZhcVRwTStHWWtZQytWRGhrSmZjZHE3OXNNME1SVVptdlFKY1pFWnJzY0lOWUJ0MTRrd3NQTTMxTW5nTU94WU1KWjRTVXRrTApDcXlKdXhOWittUWVGUTFLVnRSUDFJbWpONHlOTDk3ajdHbnhhTUZ0T0lJUnNUMVBoTXo5eGg2cllaT2p5a3dXeithZjVJQU95NGhWCkk4T2lReWZDSUNoamFvZzAwdEladVJyR1EzRFBHTkNoeWw4Nk5SWVN3eFBtb2hvM1EwS2cxYUpkbW52YWNLWkVhU0hiaDVYb05JTlcKOGl6UEttNktxMm9CU3JqeUNUMG1Ncnk2MnFwMXlyaEk5U3lhQ3RnN01ybHFqb2FZMVhkTldISDRoUUt6NnNZdWxBVW5JYXYwMEhqegpZMDhsMzFrYVFORklFNm8zTVc5U05hdUxqZ0VKa1NWTDB5MXdhckppSENwY3d1U1JuS1dxZkRqVWlhK0dLdGhZL0R5TTFMRkxBcGVPCmpxWENvbmx5Wkl0WE1Wdms4TkJnNVR6SkZ1aGVjRW1GV21zNE01SEVoV1VuRE1ld3M0ck1ZYlpJbGUxUmtPZXM1RkNoOW5GWUV2VEUKazRUb2pUbEsvQ1JYQlZnRThYTnJydnpuZjhpVHpScFYrSk93TnNzVWxRVkZxcEZCT2NwaG5kSlg3emxZU25RSWc0YURRd2JGaTltNgpUenFZbE14YU5LQzFDU1d6b2N2R2VnTWNVNnllS2ZtakNEQS8xZ3F3S0dIVUVJYVlES1Bybkc1MmlQVVYxekRxU0Y4NXJnOXEwaURFCklwWnBwR3YxMDQzeEJMS051blVZbGd4U0krRmtPSGlpQmlESGUwYytMQVRSc0xZbW9acmFzOTE3c2hlV0RyNWxydUpnSjViQWNZWHYKdFFrenQxU05ERS9yNmxrUnk2M2tYNlMra1F0M3R3Q2hINDRwaU9FUUNzU0dQdm1mL1JMb282UmNIT2ZUWTlLalhCejVhYXdldXZWdApKNVBmdTVvTkRvY3FPRkZUemJIVnNiQU02OXBUdUNCY0xpZ3FnVEd0NkxmbEpFSjIzTGplbXBEdVJkNDVIRjh2RFlmRHJoalNKYmFZClRhcGFDclc1MG9XR1BUekp4MDNnWWVUS25Ma0luZ0paV0k4VTNzenR5TEw5M21qUGhPWUFqSzA5VGxWd08rWWdPQT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJaS9nblNRQ0trWm41Vmk2UG1mYUliRWxpU0xDcUVtck5KdWtPYjRRRzJPNENVc2hCV1F6d1JNUExuaFMvNHh6RjE2N1BMUUNBMDZrMwpxdS9FQVVJd0krVEVEc2VwUWVXWW11eC9kcVpGbGFYRFNrRzRGK3dRejZjclB4UzA3NmFIbFVPakNhdXF4c1owTVFDaWlhd0x4Uyt3CnBUQmhEejc2anJ4Y3gydnFlc3hRNGRQMERaSW5oZWsvSzNLMEpPQlNNaUZxS0dDbG84NWMrWlIrZlRJa3g0YXl5ZUtwWGZNQUQ1S2kKV0dqTStRc2xKc2Z1TU1WWGJ6VGlqOThQWVV6bnBEQU12UlMzK2RYV3FVS2ZGUjdjMFBLWWxsNGRuaWYvQ0h1R3NvcHhSS2EzOHVOVwp6K3h4dVJ4MmJxN3VvUGZNNnFtMjYyUzY5SnA2NE15a0RSY3FaZDVDaDBHYmt6MjB5R2xCZVZ2dTkzYkdJTE1BVEF1N25lbERBN0FWCkxpaW9aYzJPZVNGOHBJcDZ4WlVnNWdvRlo1M3lZOGY3SGJKOXVnTGIxTFR6M0FhOTN4SEIyT1NHQmU4OUg2YzBrM1ZOdExCUkJYTGwKcXJsSnJnZEs3RHU4RjVNdW1SdFNrUE5mMVduaFVRaDFXMWcrYUxVc2RoUW9FWmt4cmwrU2RVMU1reTQvemFFNm9xSElVenhOTHExWgpSRld6OGh4ODZEQTBQMTZWd21ISDJkUGVrVDNuYUUxT2tFaUQ4UURqclY5VXc1M0graVZkWkRGSEVDNmZMckk0cERNalVvOEJqU015CnhwSlF6bEFWOThqWWxYSmRNYTBSM0NVRGk0Tmd5UDNzS0lBaUFzMTYxNWNiRWNMQ0pRRUNOTjErejU0aHViVFduRkk5eXFXaDlrYUkKVmVra01KSlFlamJkSy96SFdHMTNWYVFtcHMyeU93MGhXakpSVTZKRXRGVURDeVVNc2FNSzVORUJpYm1yN0s2dmlUUjhOcW9vSG9JUQp1TnU1Z2hJNmMxZzVqRUpVejVET2VQeHdtZFZSZEt6V1VFN0F5UEFjNWtoOEVEQU1COUVZU2VTUjZJaWpvSCsvNk9XSisxKzlQc3BSClBndFZNbUtMMU16d2hxRVU5ekZNRktJUVJXTTU1Q2NOamc3d0w1VnZJSk9rR0U5WWxFbWlTUExhREZtZG5wUjNVQjRkM2s1MmRmSDIKa3pLWjE1alJrYjVBK0JLSG1tUG9UZFJVM1F5WlNHd2VFSmVKa2dxdXcxZDh2V0NnNlNNY0ZnZWdydysxMEtzckhCdDdLcUtOK25wTwpwckFRcW9HMmdpdnJXSHBuWVhsbS9DYjhTTlpYWWQyTEgxaEtjc0hocHUrRS9LdjRIS01yOWNRN3kzS0NLVjM0aDB1c1BLUmFxUU5jCkFibGh4dUtiMVNYd0swWUVNMUN3RUlWcVRCRkRuSzR6cXlwSW9JV2hKcm9mTWRQVklqb3RyRXlNY2pQZWpLb1VBSSt0QWFvcVVLSWQKbXhzb1l5d2puQS95UTZZL1FOMUFubXdiNWxDb1NUdHVhM2Jnb2thUkRuZzdVTFRtbW81QWZwY3VRYlVrNzI5dlYyS1ZOc25CcUxjRgpWY214L04zVzFaVHR5T3hQU0tIQlVpWDNmbVdaV2lzdDB0RlZIV1NoMTVCc3JyT3V2bk84VnBWaHAzYXE2Z2FGSnBUWnorSkhvaytmCkxXSHNBRDdhYjR5bVI3bUxrL2hOektFbndnZ3pMS3MydVp5WlVoeFlxUDJFOTdwSFBCUnEwTGZ0SVVjWUNuTXpZOG1BQk55ZHpNeXcKOHdBLytpVmdFeG5OcGtsU3lnZThIR1pFK1huaEV3eEp3bDVzd1RxclZSeXFSbFRzTE1sQ1Z4dExuNUZETXJhSnBIRHJwby9IckRJcwphMGtHd1NrZEJvNzRWUm54dUVncGg1YVZtc3hvNE1HZzhEWDdFMUY5TGFLK3NZQ2h1cEFlRERJQjFJcEIvcG5zNVM5MGc4ZWd5SmtkCmJ3VTlFdzE0SzdvekRZOEVKVGtKRHdJK2xVbUlXSFVKQWVwMnQ0b2JrTUhhOU1JcVF5dTlRSkc2a0hrSENRYnkyRHRLVFFSL1RtTE4KcWFVNi9UbGVaWEYxRkxlSnB1SDBjQkd2MFdSSHQwdk53bkRaTExhaitKRktNYm9OTWZac2E3S3FocnFoVW4xcWZKTFV5OHAxM3lMLwpUTG51SEphYWNzSnhPcHdoVm82NXJMNmpMckFlbWdLdGZFNVE1bUdsZ1B1L0VDR0NSWW92Uk1Rb21zdFhUU0Yxbml4K0p5ODM2Wjg1CnFtQU9ubHFIWk9VOEtZekZpamltYUxFVEVQYmlSWDA0WlZ6MElVZzhxVXVCakl3WEhwWWQ3bnZXWmFWU1JrclhJa0xCc2VTcE1LMzIKM05idVNtdW9idEtRZGtIdjRoZFAvM3V1SmxoNUlWalIxSHZOQWtwMnFXbWlRQXZOQUI1ZW94bzlPNzBJdlRZVkN0Z214ZEl6dTRFdApXeUJBOUlVNkM5VVdwQmFsTnQzdVJQSUV3YVU1RWNGdWQ2ejZWb3JhMVVVT21nVW1ZN2dLVERmdEF4a1prcy9QZmtNOUNQYnhaWG5VClcyNkhodm1MU2c4bExiQVJucVdtajcxcmxndFpsMG9TR0JQa05TSS93bXc3TmYyUGFralY0M2tvQ293SUtNVjg2dFFjTld6VkJjZzgKLzZ5S0U0SjYxQlN2RkVOVmtCS09wcy9hc3ptSmw1MkZMaTBTZUJ0bzB0aTFtVmM0UWxpS1gyUmxGbjZIMjBpc2RzNXpXb21DTC9uWgo4UC9IM2gxRU5ta3FYRWlPQldwVVZidFp1d1VZSU1qdmVxbEtNa3ZsOFZSd2dsWmdoV3ozSnY0azJ6Z3dLZ2xXMHpnMUMwcVVwV1J5CkFDcEx1ak94OU1rWlMyVVNJQkFpMmpNME1iRFdvUWlyUXRycFVkeVEyL0F0aHdBc25mSSsrYVpJSnpMU2loOWdIQVlXNGxOcU1sME0KSzRtZHNBWFNwamdsckZRdnlwMVMzUDdxWHQybVJJM2pqU2wxNDg4SVNGT0hEY203YWk2SlJMY21jYkxhVWQremZKUytlbzY0WHArbQp6TU5DTVFjc1BOdmNGQ21PUFlXeHg5SWtNdmNLMU9jV1BKTXA4Z3E3b0tRUWZTQ3lwMmM4Z3RDU25YbEZVUm9CT1UzVEd6OVdKQXNVCkw2MzZIdkE5alNnOVoyQVB5U21GL0I5LzdoRGE3MWdpcWp1VnZFeVNKMEFyUXRvT0cvQlpQTUo2eUhKWGtVQmNRYmtMa2FwWnRaM3EKVXg5d1hPN0pkN2d3U2hSUmMrUXF1bXFvWGIxYzFPazRFYnY3cUNxWkRXVlNET2MvK2xtcTlBa2ZrZGdIMFJ1cE9nUk1rUUhEM0RXZgo1Ym5pcWN5UFp1VmNwSDl3VG9vcFdnazF1akxIZFgvUXNMSWJBR0ZjVmIrZ01xRWFnZWhoRGdvdDUvMUpEazhEQ0V6Nndha3B6MXJECnFXZ1hqN2s0cjhEclpHMGZia045RkdaalF3d21TRkg2bjF3cnJtRWY5YnoxNXU4OEtxb2lYZ0dTR1FaOHExZU4zSVFhWHNwSm84ME0KNDlKREdqWnEwM2w4MW53QlVrQU5DYXJPVzdGaGR3Vng0UXFzS1ZWZEpCMHRmclBETmMwZlRremxqYUJpVGc4ek95M1Bpc2NpL3BFKwp6QjdzQm9oRytYSjJGTkh0WWFSN2RZbTZwY1NuM3kzaVlTVlQxWDFTZmd0N3JxRGdicDRWcktyd0NvcEVkNEtSRmtaUitsQVVSWHVpCjBSVGpsNCs3QWxSM0lHVC9FR3BJYXBCS1VOU0VsWUFlWnJTd2FLNW40bmFjOHhCVWRxa3lQL0Urcm53RThwdGNKOEU2dTc5a0JqcHcKcnF5bTQ3ZHRsa2VMS0IrczlHaG9aU0Y3cVk3dm9yR1JmdEZLd1NFWjZjK2VaYldjSlVUajhXeWsxMjVhMVlONEdBZThhdndJOTBRcwpTdFdSdy9LaXBNb1MzamQxVlFzMVNuVThMSFpVdjVLbzVKUkFxY25pV21WMENtUWViK2lyMTU3RFlBeUlwZkhDUXNiS1lRcXZ4Mk92CjU0bFhuYUpkK1V2bFQ2TE1NaTIycFhyc0FOUVM1ZGZkMm4xa3B5TXV4TTRiak9qRFNnUUNZcVkyS05tWDNER1k4OXF6TDdJTGo4OXAKa3RlR1EwMlZGeks3UXpsSFNyWkVxVWxPbG9XaEtHbWs0dm1rdWdpZVpLNGN4WHlSU0F5ZlVOd2gxbjh0MVcwWHVrY3M0OUtFSzJFOQoxazk3NXNJMERKNnJLa0xiOE1UOERGZUUxaHBUdGNrZTdhcStWUk1FTGt5bXBtcTkrRmhKa0QzMWhDUk1hNHQ5bFhMYzBKRWk1ZUk1CmJBbkdvV1FXT1FORkEreDBmWUtBRzdpYlpKVDdGQWZIYXFpWmhCRnRxcDVpWm4zUHNxd3p5aUtIS0lrS09WZHpmeWRtNEZrdnpkcGgKT0tiYlQwT1lkM28ycGpxZDdueFhHVHpkRlJvbzI0Tmw2NzlDMThIeXVFZFYya3FINTRDc2ZWa1lmTmRsaCtTYVh1TVAxVzB5Q2NzMwpEUEkwbmtTdzhaQ2RNelR1Q1pWeUZabERFOFpqWHhFaHk4ZlNZWUZYMlJ1cnFPSmQweUlzbXlzQlZBbGRJVmdEZTZMMk9qaldNdjdOCldjbDZWbUQ0QlFrc0VNT3EwaHlyMUJmdG9vY3p1cUtub1VWaWlJU1l0dTNHRTlLalE5TXpqcmpvcWRmRnI0Z3NaUW9QQjRGanBkRzkKMFJweGIxemp6SUFWNVpmcjBmb2lVcDV5K3RmaitRbmI4UjFIRkxKVTdTazhCU3piMlVCVFNrN1hLNkRvU0o4cTZPanRhT0V3aEtPSApxSVIzZ0N1azZYVXRybFY1eEdua1VHS0hPRU9YRG9TSUdPalNydTNZQ3BwbFBzdFNoRzFCZE96czJhTjg3VEg3Y01wRlVHSTJLOGVyCjVYdFk1RHRPcFEzT2RnenZONlZaV3IzL2ZvNzBibHQ1dFp6WUlQT21RZ3VpUy9wMU0rZ3lLMTk1NzJCMzAxcGNkdFM5V1lLb2h3WlMKQjliTFBTNEdBeUhhM2IyZFJGd3JtV0tBbnh6ZWN3ZjFpV2dicmtBVTRTb1pwWWI0TVZFbWtJQ3lKR0E1YWNTM2xVUm1SU0pGWkFvbQo4c29TZ1BJMTVtaEZWbnBNM3l2SVlKT05WZjIyTTdxN05PYXZBRS9QSlEvOVovK2VFL0pMSVUzZVRLWlRZVEdHUzZvaTdIZ2dadUh2CnJDa0NFSzVESEQ2MndjMjZTVXBtc1ZsQmFIVVF4bThLak8xNlJsYkJzVVFwa2RXV3JJZFpUaExxSjVQRjg4ditBdmVrdm1aUjhVK0QKL00vbCt2L1lVV2w3WkJyR1Vya2dWUnRzWnV4aUs5WXM4ZGl0bjJRcis2TWdLdVNDMkNxOERVSklwSWlJY1RxSDFZNUdBbE9jMVZqUApPN3dDVzE0RnNteU9wVW1zNDJTWlVOOXFlcDBab3YxczY3SWtReUFuZzV1VlIrTWJGZEU1bWpURm5wemxZYVZPbWhaaUlkR0phWmVZCnhyRm0zTmkwSWh0ZEg1TjNsdk54TEdTbTd4Zmp4N2JvTXBOaU9pelg1bmI0VEI4RkR1VW56ZDNjeHhZTkg4TXcxYjBkWmhnRytPR0oKMGhtbjlyOHJ3NzhydlZnandIQXJWTCtQNElTUE1tNkRrQlh1cHdMNGNVdng1Nm1DMTVvR2d4K1QwMlFXV2ZxaDJuQnVFd3Nyb0lBWQpEQzJ1S3RMS24vWGJMdjQvbG53WHF4eHJrcENWV256bnczbEoxTEczWGZyVjVsWUhFUzdEcERRNVRUMGZXS2o4b3VheVNrWnQwenFxCks1TUtxa0VvV0tXSnVsclZ3OFA0bnVWNGIwOTdUMElsYUkxZkVQazFtMFRFYmRRZXFxZ0xXY3pOaXQrbllpUWRrRUVzejRjbnhWbWQKRXJlbTJxdGRrSkdNUkNXSUUyTklNemcrcy9uaEVQNUVLRklFZGtMbXcyVWpyeDMza1k0OHdGY3FCL2Rxa1hpTlFuWEFrSHBSZXo3cQpFM21lUUh2TWJSaitvcHMxc1pRb3hJREhsVjVjZ0laK1pnbUhhekpaRC9EWnovcnhPenB4LzVXai9iZHl0S1BDNElYUlJwUXNEU1d4CkdXRkVUSnZSR0VPMEVjMW14T05tYUpmbHpHc3d0RUdGWDEzbFd5eW1nTHRMdXZ4TjBFWjVKc3VlRE5DT3psT2JuNDNsR3Z2djNQanMKdkpRdzNmUnNNbU00ODBjZCt2SVljN096dVZ0U3BRcmpjamo0c0Y5Qi9FaGVLbGE3dWRuMUNiU21zZG1JRERGYkVOVHNadjNjQzVvZApmV2syTTV0RWUyYzRxYkdwMXFPOWlObkhhR0IyZGRQTXpjc0dtcEF3aVJ1WERiUlJMV0pqY3lxdVJTdTJEY3V1Vmc2OFdObE1ObVpWCkozT2dxS0dvQ2xKMmkvbjJBbVZEQml1ZXM0UGNBUkRjbUd5V0NGUVJmcU1ndncxWFl3Y2tHOFhTSEZHRGtVMHBReWx2UkhaekFkRW0KWktPM1dyWXdINTIvaXVvV2J6NDIyNitab1UwaE13NlM1NEZqTjh2M1hteHNBdUJhT1doc3hKL1VVczlrYkt6K2hrOXdZMktaQ3k3OQpjTEZaZEtiY0hCT0lmWVppN0lKaW85OU5zVHlNTXl2b3RNOVF0US9qYXBpaXlkdStpZGljeUxzdzJRUmlzeDdRNkhIaTY2SnM4SVhECmpxcTN6Y01tWlBqYmhtSHp6NlZuczRITnFDcFRBdGdvYkpHVlJZY2dDWHRVMFZCdUR2WndtR1Jqc0Q5V05FeCtiUXIyY20rL0Z3UWIKcXdWMlJBd0c5bExUd0NCZ3I2UWw0Z3VBVFVHTjlxRjdoU1B6dFF2NjlYSS80UmY4ZW5ablpZTjlEVUJFSjU3YjZPdXh4Tng2a2EvWApWRS9qRGI1bTFWbkpxaUZnZ0pjTkpVUkN2Sm5pYkROUkpTSVdKeDBKVStHckRiNkdSZG1ORzN5ZDZFS25FSld5MDBWMGlRbndOUzBTCmxNNmI3ZGE5REE3d05kYlBUMmlHa2IzQ29vY3B5QnQ3dmFyVGFvRzlSajhPYlJiWWF6YkVZT3p0NWw3TE9sWHZxZz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJaE0yVExQWWZodEE5cm5pOHVkZXdEaW5vemIxbW01RHVza2J2cDFqcHpiMWU1cmx2N0hVMEVRcnFOZW8xbXRkeUczcU4rV0o1WmMrQQpzOWRWbTNpOVpnalhMdUExakV5QkJ1OGFjWTJSODdlTnU4YlpOS24vZGlVekdqd3JiR1BXOVJvbWk0Wk9lUTBuSDI3U05kYTZUTzBFCjZIbzIxY2dHNWhwbHU0Wnlic2gxZDV1bXpiaHVTeDdiUmx5M2kzRzRoNjVtWmFZQjF3Qnk4S2VEYjkya3lIelJyWnZqY3NHMmJ0TjAKMFVCYnc3Qi9LYkt1U01kUTdoMWc2Mmo0R1Z4cjZoejFLVzJxTlhNVDFDa2FhdDBqYXhwTTZ6YjhTdDFJYXlTREZLTnlWZ1Fhc1AyRgpjSEpNVG5IZVBPc2FsVitCczY1UGFEQ2R0VWM1WkhNUng0WlpWME4zTnN5NlJnMVRzS3hoR0cxOGU2R3N6N0ZNc21iK2QraXJvdXFYCkpUS3NuRDhnYXl6cXBzVm1YRFFnbDZoaUlXT3M0YXlSZzMxanJITS9CVVNrV0dkT1pQTHhwS3Y1N0FsR0tGVmFic3o0NGl4ZkRHdlUKeDNEdEdBaHJRRkthZWRVa1dHT0daMlRoQmxpVGlQSW9qazU2U0l0aXQ4Qlh0K1k4NEUydnhpcUhTTm1BVitQWG1FOE5Kalc3K0tVMwp1WnBRa0J0d1hZTjRIZHpxNkNMN3dsWWo1YUtVcnFuVmRVVlkzTkJxSk9NWTY3eVoxWUJpODgwTFFEVk9xUm5uR0FicEdHOWdOWkRsClJZZGltU0QranBwVzVudGhVQkgrQmF1dWthY1BWald1bFpORm9LcVA0U0pWc3lLeXBrT3FidEhIUGtEVkxRcEpiazUxaTI1VndhbHUKVVVvU21Pcm1ibmczcFpvbEh6SXhFbzdWbjlJMlpsUmpBK1ZmTDBRMXpvaDNMUWpWSUV1d1NqMEExYTBxRm5uanFXSFRoMmcrTlF5YQpONHlueGlCRWljMU5weWFVaEJoZnc2a2htbVRvT2VEVUFCQ3BxdWxpVXlORnE3SGJhT3JhTEdJTk1qVSthR2JxYmpBMUZTT1BzcUtzCmRhZ09XbXdzZFlscDlxWlNJd2wvTTZsclpPY0RTVjJDVG40VHFVdG9Td0pJalFha0tsMDBqNW9kU1MwODJEanFqREtVcE4xRWNGR3IKdnMyaXprc3hvNXRFWFpqUWxEeWQza1JoWms5MU9nemVsbUp5NzRXaFJybThJdWltVU9kZFhCbHdhY0JzMHZyMllsQjM1NzNDOW5DRgptWVZ0OEpvWUdVMTNQVGdFYWk3UDJpRlE5eWpqRTMvNmNjTHVoWjkyYmVxaFR6OU9TRzM0Tk1iM21qK3hweDh4L1E1Nit2bVROM2dhCmZsVjZZNmN4QjZKbjhNWk9WelpsbFk1ZVZZOVZ1ZHViT1QwWmhHN2ZEblA2RVJicklLZVgweW92NHZSeUVqQ0kwMXkvSE53MC9od3oKdmVmeGJBMzRoazF6cTVJdjFqUzZSeHBIZlZqVGp0TnQxSFJYQ2ZraFRXL0REWnJleHVCTUw0Y21BaC9ORUdFYXg4QnpiQTYvN3EzcwpHRy9HOURFY3hQUzJCV0U2NzZyUEFFekhxM2p6cFZFTTBFZ2pGbDRhU3lXMkZ3cTY5TU5ZWktwdnVqU09rTnVZZ1pjV3BxRG1nNWZHCkI4MHV5aSs4OUVOSVl5a0hMLzA3SnQxcVQ0Y3ZEVkxCbU9aOTd4RUx6a2RuSGovdzBnWkhiTHAwRG1MMURaZkdLcHVwaW1CTEwvcXEKN1Z1d3BURVN6Zkx0alphR1o4Q0tkcE9sSC81a3Rab0JEaW5LeHovT3p6c0dXUnBlVVowenVBd2ZreUdHTHZiaTNHaHBPUVhTQW0yeQpOSW9sQVVFOFlHbmNxbVM1NHFPUGdHcnpGMVk2bTBpd3FkSm9TUDgwZ1NKSU1NQ3dKVVhSeFpTR1lLUzcxNEpHYllqcmpOVlE1Vld6ClJ1Y0dTcE0vMmkzR2xQNWJsV3liSncwRDVTQTNUaHJIS2sxdUZJVHp5TkYzcCs4bHlXMk9ZOTBzYWJpRzNiMHhpRlZoZWZqVXRiSUsKa05EWGtiKzlRTkxiR0J4cEtDSllUeHdZYWQ2aVZOOFVhV0pPeWpnUTZlS2lwTTJReGhZTStONElhUmo1VUlJZ0RTSENNK1lCU09QcwpyVTNaSlk2NEFBNU5RWTh1UzRqaURZK211bzArNVdGSDE5RG1CRG9hUlJOYy9nUVJHalFoeWlGdWNEVFdHTkVWazhiK0dCb1pzTkp0CnVLblJ4MmhvZEZ0V1h3UXorb3RUSFQ0M2hpQmo4aTVtdFByeWpINlkwZFRKc1d3dm1ORlVDRkltZnlHajhhRXlGckNSMGRoTUJPRlEKV0FGanpGalBSWXltTVYzY1o3M2NlVzFnZEdJQmFodHZZRFRlTDNJRXJoMjcrb0lZR00yL3AvbkV6OEd0RCtsUk56QWFNa2psRWdNWQpMVEZTK1VTTUp2U0JSYVZCaktha2tzSEZJRVlUNnNNYytrMk1wbFVNWVJPaitYaEdiNGNZVGVrSzgyNDNNcHBxbFZibVJrWlRxY0p3ClZDQ2p1VWt5WVB1dzR5Q3ZaN2c0a05GVXFqNnRibVEwVkRpaUZ0N0lhT3JhQlFNMk1qcVI3Y3NDQXlPajhVNktmSHNqb3lsc1pwQTAKa05Hd0VJS3dtZEZpSTVtVWZmb0ZKdlZYMnNob3FJK1VPUXhrTkhYQ2pEM2Z5R2lxeXhoaURXUTBHMll3dm1wa05BMTh6VzlrdEdScAovUkNqYWNDU2JST2pZV0hkNEFzWmpVc1MxRG1RMFJCcUsrWWR5R2pLa1pSN3ZaRFJiSzRiM3hGcnJSbTdVa2JReUdqRUpab3h0YUdXClIrQ01NZHdBUnE4bW9ldm1SYVBObnBvK0hsNzBhbEhHYkdBME54b0hGNDJqRU9oNTA2TFhqT2RxV0RRRGJvejhCaXdhbDBIUjF3c1cKaldLZ2lMS1RGWTFPN3VSckJpcWF2RjJWNGdRb0doRlJkVUUwSjNvVzRmdzJKcHJhbDZ3dzg2WkVvMCszMmo4YUVnMkRHSjFtUkdPMwphb3owUmtRallxY2laUk9pR1JTa3VpRUkwV3p5emJ6aFRZam1GZWRIU1EzbHBCNzNpTnFFYUZTOTlqcSt2UURSeTVXdG13L04xdHJPCmV4QjB5Szdmb2pkdk9EUnN4UlhOWkVQak1Bb0pDdzNOWHVDUGtKZEJoazRVSnlUakdOU0VKaWlvZ1liV1RPS09pQnNOTFE1Y2NUOVMKZGpzamIySHBXQUtMZ0pyQXNmMW1ROFBxQW1pem9XbEJzNWRnUTlNUVVPbk5ocWFWdFVuQmh1WVAxQmtrYUhqalRHaXQrZTNGaGtiQQptV0s4ellabWFZTUt1cU1BQkZYTi9HQnVPRFRMS2FoaURqZzBLaFNFN3dvNE5HK1FxaXd2T0hSUzhmdjZ0dUhRN0xmTkFIekFvVm5iCjhKZ09zK0hRckM3WldHbkFvV2xoSENqZzBMU29zK0lGaDZZU1ZTb2wwNkhaVTZpNXBsS3lEUEFpdXNzSU54NWE2bFNTMDQySDVuYmsKRXdVZUd0T1IybnZmZUdoNk5XcDFhRHowVWJFR0hocVdHcFRualllR1ZVNkg4ZEEwTURvWmVHaGExSHpxd2tNekZzZXFVTk9oMWRuRApNaUNoZXhCbTR1QjJBNks1T0tKQUpBalJpZEJmWS9ERmlXbHVoZjRpUk12S1Q5ZUVhQjVMOWFzaVJNT2diZzQzSVpvdFVpaGFDRUswCkZnRm1SbXNwWE1aaHFHOUN0SXFubW5SMVV5VTl4WE45SUtLNVlKa3UrdCtJYU1xMHhaTTNJcHBLYm4wV1JrVFRvZy9nWWtUVFN0RkYKTUtJbGxjOW1BVlF2d0V3MHVCalJFc2JyY0ZwTnd4OVpKdEVURWIwVjd5OUVOTDFDOW04T1JEUnVtUVNGZ1loV05PTVRJWm9SRGRYVwpHaEhOY0FhbnVHQkVLOERSamU4SVJuUTY3VXpNaUhaMFpCNUdOQ01xTS9BZCt6Yy9ybEorV2pDaTZRVXF1UjByVnN5dm54SFJXQXhJClNCSWNTVmJhcUFpVWlHaVMyWko3S1c5RU5BTy82dkpnUkRSZU13SW9naENkV3JUTHZRalJMSitpeWlZSTBXd05LSTJRQ2RIMDVIT3EKYjBKMDJvS0JJRVRMY3ZqUVgxY0FlMjJBMGZjSnVYL3dvWkVFWk5vdThOQ0o1SXRIb0NQNWQ4QUJrdmgyODZGcHBUVFlmR2djWDVMegpBRVRESWxuNkRZaUdWWEptODZGcFVGclFnR2habVBDNkFOSDRSYmFtQ2o0MEVwUE1Cd1FlR3BtOGdQZHRQRFR6a0N4VENqNzA2dFk3CkJ2WVpRMXdrd1RZZGVwVEl5TnJZbzhOd3NLRTdxc1Vka3Qxb2FCZ2ZzNXM1czhBZzFJN0IwSkJTVDBXd3RtdlBkcHJVV1pzSzNhUFUKTkxicTFSbXNtd25kM2Z0Nkk2Rkhjc0lxU00vb3E4MHA1Z1pDazZpODZqR3lOM2FUUHB3NDZHMjRhZERIYUJqMDlIeStJYytBT2FtQwo2MEpCaithdytONnFDSHV6U2REYmNJT2dqMUVjNk5HdFBnME1OS2dTQkx2ZEZPaU54QTRJTkVMUFRBa0ZBeG9HTmErN0VORFlMVGpPCkRCN04wTzRFQUhvK2Z0MXYvdk9JRmx1QmZ3YkVSWDBEalg4R3RVUjB5b3YrVFBTdkEzU01wMndXY0xDZlJ4SlM4U0kvdzdRc2V5TUUKQ0lZYURlSXh2UXhEbUcvczgzZ2luV3ZxODNCRHBBMTlob0Yxb1RmenVkc1AyY3huQkVCWm94SEk1eEVmN1VFK2I2UmFFSjhCWldOYQpPSURQNElSdUZuWmdpV2tzTWpJaGo5M0VBalh0K2NNZ1R1Rk5lKzVKZVYrem5oR1FaVm8vVU0rNllCbnVYS0Rxd1FMMGpIUzMrbkdaCjh6eThTbjFobnRuM2VkVkRlZTVhZVczR2N3L0k1bzE0NWluVkEzakdEV1lGalBITytMdll1MjFuSDJGK0F1N2NBNmtiYk9mcktKdnMKM0NNekdtRG5FU21rb0RqdmNlREdPZ09ZcURKZVU1MHg1cVViNFR5alYrTE5kSjdkWXZTOTFUS3lQWWpPa1BJMWs1b0Q2RXk4NWF5SAo1d3hEOTAwa3pubUdoT1RHT2MvZ1J3Zk5HWFVPVE00RnpCbkVhdmJqdkZET2k1SHNja2pPV0NvejcyV1E4Lzc3NWpnZm96SE9RTHBFCmYyRldiczFpWXQ4TmNWN0JTUXFHTTlhUyt1cUQ0WnlvYlhjenU4MXdobFVMbUlpNlVYMURrdWJPcTJONVpEanpidUVMYVJPQlZJRncKaG1Xd2ppQVl6dHlHSC9waE9HTUcxZlFRREdjMGVIZXBMdEw4V05Qd2hiajV6UitYUjY3SXhqZnpsSGgzQTkvTWd2NFp2T1ZkUkFLbgprUmNkL0daVzlFOURzTlZaTUVYWDdadmZ6R2ZKUWduem0vbEtOQU9pSlY1T1ZqTysrTTFzRXhsWG9GQTNQRDBPWlFZNGYvV0RmdnpoCnZ5akFtWEZCVXE1dWdET3RsRGdFd0ZuUndwSVBzSmxoUURXTHVCRE9ERmRSSHhRSVp6eFgvNElSenVBeDhnVzVDYzY0Y2hVWUJzSDUKckdhRDRLeHZlbW9DUFlGWmRueTFENlhNV283Nnk3WmpsdDNPU0h2RkxNVmgyUVJuR0tRYUNJSXppMGZWOGVFaU9PTXFwVTRMZ2pOZQpPMEdiQXVFTTEwNkNzcHZoZkR6WVlEano5UXlwaGdnUFpGSU1wYkEydy9teW11SE03cjBNL1FmREdSYkp3MjZHTTNFUFZCd0Z3emtSCnErMEtldlVTaDZDV3c4dk5jTVliYXpXR0djN1U1YW42MVF4blBGK1ZJOThNWjFTbmtlZ1ZDR2VVYWdtWDdnTHI1UVkvTDRBejFHWEIKbDZmSER6ZWFRMGZ3bS9HZWNIMXk4NXNaNE9Gd0hmeG1CbmpFSmZCb1FVdHpEZWlwbHFMVnJYMnA0K1N4V0NRZC9HYitaSlp3WitPYgpHYWlNWnZRcU9Yc2VSUUUzdm5uTllHd2ZlalBLYVlSWE5yeDVEcTEyTjd1WjVUVTg4d3Zkakc0d0FrR2IzQXpwSkN2MUE5dzhvdkx2CjVqYVA3dUV4c00wamlwZUQyanppNDdtb3pWallMQ09hR1JRWkp0c0dzaG5rUE03N043RjV0Q0JJR05nOGhzdWlqV3NHSW94Qm1rTnIKUnRzWURzMUJhNmFzZ2lYL2hqVjNCeDlmckdaUXVoZzNDMVJ6TDI2OVkxQXpzVjVENWRDYjA4eE9SNDl3enJ3c0hJYXZZRkNhMlgxKwpLSm04MDNJOUtvQ2p0SnBpUDBNWVdCZUpxNmlQR0FrYjBjd3U3MVdmTlFOUVNOVHhXd3BBTTZocjlFQnZQak95ZHdRakI1NFpFQUhHCnhJTE9YS1A4OUlZelF6WTFTcWp4SVBNTHJ6RFF6TVF3ckZCM1dqUU12UTlMNHdQTXpGWmZ1Und1TS9qQlZCZmRXR1pVNEdxTllDcHoKYTk0cW9NeFlGN0hFOEdZeXc4Z0ZTRENaSVYxcTVpaHoxUXh4azBpSkY1SVowd1RCOUVGa2J0V0Z0ZGR1cW1pOWVjeGNpaEhnWUc3TApYamNFanhuS0VzN1NONDRaVjhCZkN4cHptNmU3TnljVDFGV0pvSDNCbU52d2x4WXM1aGF6VjZDWWNUN0ZqTWFkQk1lc3dnS1RJREZqCktocVhxTE5OZDd1NE1jeHczclg4TjRVWnB5WkloQ0hNRURuTnRGNE01aFdMdEdBd3J4YTk5NHhnWHQycDQ0dkFEQzJ4Q21rTllGN2QKUHg3OFpVRWJ3ekMzVGxqTm5BeGZYczRGYlBZeURQRmJHNzNNUkFmWEJvRmVqbVRFSmk5RDVFWHF4dzFlbmlNNktwaTd2QUt3R2RqbApGWUh3aTdxTVJJdmVtNzRqOHNFY0NEVXoxeitVdzkvVVpWcEZFN2NNTHBHRGE4V2R2Y2RxanNsTlhZWjFzZUEvcU10a1JQbTlrQ0JpCk4wRzVvY3ZYNFF4ZGhrWG9wb0F1cCtSQ3R4ZDBXU3VmdFE1MG1mbG15azREdWl5UWdaVmxoMDhQUDVEQ3pvQXVNdz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJemNzNUxhRExYejNJQ09wOVAranljck9wRjNONUxRRVdObklaVmZPc2p3bmlNcHlIWkFUejFob2hIY09VVWZDV1lWQnBzbkhMMkkwSgpxcHUyek1MWWVsakxLNERKZ1ZCV1FQRmlMK3Q5aGtpVEphK3hHYmlQNUFZSGFmbEtuRjJrWlM0TnFJc0kwaklYWkV6aEIybVpZVTZYCmtHNWNNa09kQWs4YnRNd3pVNWU4Y2FLYVJ0dDkyck1ieXF2KzhUQ282R2RFaVVkMjZjbkZXV1k5aCtxdHpGbm1aUS9XVjVtenZDMDMKWnhrN2ltd1NuR1Y4a2FUN2I4NHl2S3FxcXBXTHN3eXIydmtHWnhrV1NuazNhSmtPT0IvUkRWcW1sNWExR1JPRW9EU296RTJZWlc3QQpSM1pqbG5saWpDUUhaam50bmlTQldXWXBDQWZkQzdOTVkzVVhCdEhVWU5Gc0pjd3lCejBXbzl5WVpUcVkyVUw5b2hWRlVzbjJ4aXpECm9qS0ZHN1BNSmJEbURHT1dFd3ZoaW5TOU9nbU1IU3hkdURITFhCUlREeDJjWmF4a1dRTVNtR1VsZFV0OVk1WVpOeUdwSXpETFhDS3AKVzRReHkyU2pWY3MrVDhROWgxWTZNTXRrbzAxdnhrZWJnd0oyWTVhcEptRjB6SmhsR29xaHlnemdjVWVobmkvTU1qS1d2di9HTEVNTwpvczVOZ1ZuR05ycGhOMmFaYU1VZVF6eFRuY3ZoOGhWc3B1VVl6QTFaSmsyTGxUVjd1OVlzdEEvS010YU5lbmx1eWpMankyUTlCbVVaClBxWWNqYUFzSTNhZ09OaE5XUmJYYUk1RFdhYjJUNStnS2N1MFVFcC9VNWFscE96elVKYkZ1Q0pNeEpSbDdxa3lxNHV5ekJCNXdMWVgKcGU3SnE1MmdMSE1oS1hqNFJWbVdmSExOUTFubTZVZlJLaW5MQ0dBSmYzRkJsbUgwTHdxeXpEQ1hUc3lRWlZvWUVMb2h5N0NLOVdmSQpNamZUdlRCa21jZE9iaXV4SWN0YzNZak5Fb3pMc295R00yVDVHRzdJOG1VMVpCay9vQ2hMd0pNWkxsSUMvNElzWTZrbzcyNXZsNzIwCkNzYnlNVnlJNVcwTXdqTFhjOHJWbTdDODM5Y1hZWm1wWnhJR2dyRE1rbmVxQm9Ld3pPUmh0TTdZZWtsYWwzMUJmZlhJSG5LMUpNSXkKYzREeFp4VFBTS251cDJHdVkzT1dMUURMM0lZcjNodXdMRUh5VElldzdDREhQSVJsUnFybUo4QXlzWjFQam5ZRnlYa2hJaDhDc015dgpMekw2VnplYmFzY2pBTXV3OUdKMWdFaW9yY3FkZWdHV0dSc2ljU0FBeTdSb0hoTmdtUVlKRFM3Q01tOC9NOWFCV0dhcW00SzFRQ3dqCkVLQkt6aHV4VExTVnV6T1FhZ3VWV1ZlM1FCT1dNZGFwdmRsTldLYXVJMGZ6Qkk0Q2NIMlVYVGRobVRHVGt2SWJzUXdybWVBYnNVd0wKSDFJd2xtbWhRUEZtTFBONFZPVUVZNW1SRTVWWHI3aE1GL2E4R010MGFMTjdTVGc0WnU3T2hpd3prZEJHOUdhSW9XZGJBN0xNR1c3MQplaURMdkdmc0xYOVRsbUhWaXhhVVpWaWtsQXJLTXJlaEJPcW1MTlBLUnhXVVpSSDJSaitVWmNvSTFjdmpLbm1qTkpMMTFFRlpobktICnc4cW1MRE03UTRmMnBpd3phczJLKzZBc3M0Q1hDc1NnSjhPZFlJdXpGMldaSzZkMEdWZW9NUUxLdVEwM1l2a1lUYUpDYnh6eGNneFkKL3VMVmY5ZUZBMi8zMHc1R0RXdUd3cHVidm0yOE1wNXdtWHJFb2l0M2R4dDd3Wlc3bWF1YnJkd2RNTjVvNVdsVXc0dXNQQjBaM0lCawoxNmtmc3ZLTWQrNEdLMDlHNHU2OXhNVGRWT1hwOHBJWFZCbEZZMVErbWFuTU42VGxRQ3BETmNKSTlVMVVEdER0QmlwRHU4VWhMVURGCjJmV3ZOMDhadG1uR3Nwb3VkUEYzQXFiTVBOSXFiNVp5ZHVYTVppbkR3SFphZ1ZLbXB0NGg4UTFESTUwYXF4aWhsTkVSWTZ4MG5lQ0sKdGU4RlVrYmhBc0d3d1ZFT1BOTEdLSmRvOUh4VGxDR2NZb2c0SU1yVUI5ZDVHTXJBSjYwWFFMbTBjQ0xOVHk3UmxEZnd5VXd3T1FHNgo2Y2xJUXlzN1pYZ3lLZ0thR2NzTTZ4WFRPbTUwTW15azhRVTV1WVRRTDhESnlFa2htdkxpSnFNVUp2cm1jbnBGcWtFWlI3OUJLRG9SCklmdUNKdGRnT1FZem1WRzlOUTR5R1hvUFpvbHZZaklTT1BTNkE1Z01DVXEzSTA5SlViZDI3b1ZMN2srUUVFMUx4bTZNakFjc0dRZG0KYU81bUpUZEwxallxdVVYUHpTQWxOODlTTDFJeVd0eEZZb0drWkdoWW1Pb3pKeG41NTZGLzM3TXBWMW1QdmlHc2ZvZEpUTUZJUmxmMQpyckQ0cnBqQXNpK0hiQXNmeXpBRmEvT1JzUmY3b2R4NFpBUmNsRUUwSFJtT0FSbnNBVWVHdms0bExSY2JHZGZhWFF0QlNDb0ZLazBCCmRRYXFJRDFnZ1BrR0k2dGlySDdiWEdScUJwem1ZZWF6QzF6eGdpTDNhQWtTVE9SZC9oNUk1RzRveG91SXpBWXFQSmFCeUtkWTJUemsKNGdMWUZ3NjVCaDR5YU1nZzlETFZHRERrVmc3b05WaklCSzF3eGFLSy9CWk56NEtFM0VLdGVvT1FZU3pFR1p1RFRQWUsxeS9HSU9PMwoySmJpeGlCWEYyTnNDaks4TkhXR2RDS1N1ZXVscGRkbUlDT0x3Ynh3SUpCQnRlRjNGZ1RrRW0zWGJ3QnkyYzM3ekQ5R2hZWkltc1lmCmsyWm1odkdtSDI4WVVjQ1B5NGlwejZrNUdBaHF2ZEhIZk8wc0RlTzhBclljNWRzQlB0Njg3NXQ3akpveTFSdTZLS2RrVDB1aUhtY2sKcEl4c3Zwb0xPVTRWekdQa1padTNVaSsvTGhibkRUek9YYXIrelR1R2dSQjk0WTV6Tk1hOGFjY2YwenlyQ3cvc3VKb01HYXpqeGdGVgpOWDRiZGN6V0NtSEVYSWo1Z1VXdVFUckdPNTJOSzk2ZzQ0Wkt0NmVlc2tJTW00T2ZrREhIaUM0OTVpTnZ5dkZpTWpSOTI1RGo2azdlCm0zRThWQUw2Umh4L1RKakxnamNoanBPbjVTQWNXMmY2NGh0UExxTnN4TFFsR1o0b3lNeVNUWUo5bmJTTGRURmdJbWxKVVUyNDhmUkMKTHVER0VTZC9vNDJsK3pwazR4R3RDZ3cyenRIaTlzVTE3cXF5T0ZoakN4TTMxWmhKQUpVcEhxZ3hreDVSbFlqRnI2R2ptMmlNdHlJWgo5TENCeGlnSlcrWTZrR2VNOTlrTEVYMGxIUHUwcHRrMDQyS3ZLV0RHT1h5QVlCbXpSdDBqYnFDTVVlL1dWVzFKVHdtTEdyNlNtMk9NCmdqT2psQytNc1NYZFFURnVEQktrQXpIbXFvcGxxamZFV0FxU1VRN0VHQkhVajNWYVB4Ump5am1mc0lUc2lWclIySTQxQkxEVTZuSXgKUHJpdi92ZjM5TzR4K281WVNKdGlYS2NiVGdiRU9GaGxtMkhjY2tocEw0UXhBZ2pGMUVsNjFjQmRVTTBUQUdNU09Lb05JWlVIOGtKTgpndXRtWUFpcUd2VGlscDNKdTlqRkxiUUZSaGNqM1VxOVo1Q0xrYU9VQnZ3Q0Y3ZG1mSTlqNTFDTDBzTXhrUmo5aHlPeEY5amltU0s1CkxCc1JzWFVjYVBGS0NpQzhtTVV3c21vaWtNVXIrbmNFc1JnZ1YyK3dYZlNWUEtjR3JoakZXQXJlZVN2OEE5TnhONng0VHJkRURsYngKcHZNR2doaG9DcnFYTjZsNFBQWm93Z2loTUhOeHdTbmVoaHRUSE1aTktSNmV3VGQ5R0NKZXJuNXZTREhFdHdyazJEaVQ0YVJDRko4LwpMMEp4R0RlZ21IRGxWUStmZUFSVzVNWVRRNHdzTXBEcHhLTTVZeDEwWWhpbWVickhLNDFlMzhFbUhxRk9DaER4YU01aDNtUmljS2NDCmFZelhld2IyUDdqRStHdzJlamV3eExQWm1RNHNNUTNsd2hJemZwM2VVT0laSkNjemlhZmJnUVdTT0ZxSDMwQmljQmlWblRhUEdKcE0KSmhxTkk0YjZrdmZ3cGhGdllGYkFpSWxjWkRyTUxPTHBEL1dGSWthcFVqSWhORXJuMU1RMFNNUXdaRWZQTjRpWVJzeEV3U0VHMDRrSwpFbU9JVjdFRGVGT0kxOTBXRVI0TFVjOGpIUWd4Uys3eUcwRU1QQmREWEVFZ1J2cGVsUm9HRUVlejlSZC9lQVpITmZERHk4dXFUUjltCmxTQnh4QWMrYk5iMFJnL2poRlkvNE9GVkpObDljWWZ4dWJGWUxyRERLMHBoZ2pwOEhXWXpoMmNrWkFNNVBKMmEybnpoR0FaZXdHR28KWFVVME5tOFk1TEErTG83d3FCNUxiOXh3RC9adkdKRzFacXc3WU1QVW5NLzZaZzBqZ2ZTNDZUQkNIZmc3R1VnOEpjQncyZlFGR2taaQpLa3FuUlVOOGxIVFltR0drdUpwNzFXMHRUM2VnZTBPR2V4U1dCV040RzI3RThER2FNTndOTjlwOFd5eHBrMm0yR3pBTUtEN3h1VjZRCm9LQXAwSXYwVktxTDhsNXdZWkNqVk5SdXRqQldQQUpXR2kyTXRWQk9iN0F3VmxPcDlNTVZ4cG9yOE1QOHlxckZCQytxY0xNb1kwT0YKZ1JscTdTQ0ZSOUN0RDFFWWdpWWlKUU1vekxXWlM5dFkxa3h5YVpsdm5EQzlzZFFPVFZncjJYNWd3bHpyanZ4bUNXTlZ2SXdtNXEyRwpRUVg5UmdsWEt6TmZKR0VBcE9LOCtYMUVIbXh6aEQ4N09qOStSeWZxLzM4WTRTOTQ0RDl5NVBydDcvMmozLy8xZldBYzZBOS94Zi81CnMvL3JMMy9SRC96MGg1Ly85SC81NWYvVnNkdno3ZS85czEvKzdDOS9ZNi8vNFM5Ky9kTi8rc3RmL2Z6TDcvLzYydjZQL2NZLy9hdGYKL3ArLytPVnYvdlNmL2VGdi9yM3B4MUczOE1kKzRILzdpNDlUK3A5KytZdC84Mi8vK28veWttUFRQL3k3UC8yNFFmL2pYLzNoOS8vUgpiWC82NVYvLzlYL3l4cmpJajd2eUY3LzgrLytNYS96eEQzKzVyeEZWM1IvRDR0OTZqZi83WC96NVgvL2IzM3lXLy9DSC8rYnYvNlB5CnAvL3c5My91L2ZCM3c5OS8ray8rOFB0Lyt2R1MvZlhIZS9hNzM4bjhEMzc1TngvWGRmM0RELy9rMytGZnB2N2xIL3pWZi9qMy96YU8KODkvK2sxLys1cHYvK0piK3V4K2ViMy8vNC8vKzVkLzg4QjkrZUlHaG4yLy84OGQvL044ZnByLzVWci85NDIvL3gvLzVmUHR6YlBuUApmdmpkQktPUS8rL1gvUWZxZ3JDUS93a1cvRUZ4N2g4emYrenkwMzJjbjM3NC9jZXgvOWNmNEZ3endmVkFmNWIxSDFoK1Fwby9vTndGCkRPaGp5ZmV4OHNKL2ZJekRuUmJHNXY3bG4rR1lrTkpYMUgweGlKVSt4aG1lSmNaM2xHb2ZNNHhWMkt1SkFNTEhEOVpQeHE1MmNELy8KOERZREZmSHhDKytqb3BqbFkreDVuNEdOT3NDUU9QNjlyWU5QNzZNZTQrc01qdms2MjNQVTY3cSszSU9mZi9qWFB3Q3N0T0JrZ2lySwpmQmpVUFFWVEsybmhINjcyUkluRXgvSVhtWEFVSUMwVWNYN2MxL1I4KzhmN0VjS1BUY1FuUDM0Rk9EdTAyd3dqWk1YUUlLcHIyY2RNCjlEWW1wVDk0YVpmNUVTWGlkZFNQNldqaEpYaWRRUmgvL3UwVCs4dVBLNDUvWVAwVEpybE1rRTRjdTl4bW53V1dySk5GRkwyblQwYnIKaC9ZWjIweVFWLy8yT21yRGVxR1ViNjh6Q09QUHYzMWlQdVAvK2dMLzhSZTR6cHBOU3B1TXBIeXN1NEd3UVRGQnhidjdNUTRRQnRpZgpnZGc2NFFFWUxEUTJBSE9ITFA2blcwdEJSV3J2VTRBb2dVdWkrMlF2NDMxaGwvbmNoT3VvNTNaZFozRGYydWpmOE41MmtxdjI2YWpICitEcURZNzdPOWh6MXVxNHY5K0R2T0RiOGpxY3lRTHo3ZEY5eDJ0QnJ2SDRmRjRnby9ldE1MK045VlpmNTNJSHJxT2RlWFdkdzMxZjgKQkZ6bjk3YkladVpQcit4bGZKM0JNVjluZTQ1NlhkZVhlL0JkWGxsMjlYcmE1L0VMZ3c0cTFsN2pWMlBZcXJ6SHI4dDRqMStYK1l4Zgo1NmpYK0hYTzREVis5U3hGOW50YmxuWitQdW94M21kd21jL1pucU5lMS9YMUh2eXNvUkp6Q0pRK24yNE81cENuZkRvSXBvdktMK1FlCjNJL3hOYmdmODdtTWM5VHJnczhadkc0T1pyeUJRT0pyVy9MKzE2ZWpIdVByOFJ6ejlTajNVZStIL3VVZStPWmN6KzJlcTY5bmZPYnEKOHpaY2MvVmx2T2ZxeTN6bTZuUFVhNjQrWi9DYXE4OHp2cmZkYjhOOTFHTzh6K0F5bjdPOTNzZHpYVi92d1pjMzUzVnpybWQ4T1RMNwpiYmdkbVdOOE9UTEhmQzdqSFBXNjRITUdyNXR6bnZHOTdYNGI3cU1lNCt2eEhQUDFLTS83ZUQzMEwvZmc1ODhlU0dNZmkvYlpBd256Cnk2dG9UTEYvY2tzdzIwaVA4VFpia1BjK0trdVgxbnYrRCtNbkQrVGVkbnNWOTFHUDhYVUd4M3lkN1RucWRWMWY3c0hmZlpvOFUrL3IKdmg1SDRmcjk0MUpjWjNvWjc2dTZ6T2NPWEVjOTkrbzZnL3UrSGtmaDNuYTdGUGRSai9GMUJzZDhuZTA1Nm5WZFgrN0JkNWtteit6Nwp1clhIVjdoTzRYZ1YxOGxleHZ2Q0x2TzVDZGRSeisyNnp1Qyt0Y2RYdUxmZFhzVjkxR044bmNFeFgyZDdqbnBkMTVkNzhIZC9aYzluCmdFOW5sQzlEUVpoZm4zZEhxWW85Z21Qc2dvRjhHZ3B3cVBxTTkxREFaaGFwdmovRU1INGFDdTV0OStkOUgvVVlYdz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJWjNETTE5bWVvMTdYOWVVZWZPZkZ5T3ZXbnUvMk9vWHpoVjhuZXhudkM3dk01eVpjUnoyMzZ6cUQrOWFlNy9iZWRuL2g5MUdQOFhVRwp4M3lkN1RucWRWMWY3c0gzWEl5ODd1djVhSy9mUDUvM2RhYVg4YjZxeTN6dXdIWFVjNit1TTdqdjYvbG83MjMzNTMwZjlSaGZaM0RNCjE5bWVvMTdYOWVVZWVHb0hXUWdKbkU5K3p4QSsvNVB6WklYNzI4MDZ4cGRMZHN6SFF6bEh2WHlaY3dZdnYrZnJpVGtjTXI2NDl6eHMKK2JUMjRRbG8yMnMxY1l5dmxjY3hIMGY4SFBWeTJjY1h6LzUxT2pqRmY2NVE2ei84L1orL0FxMy9LZkhYOG5lTHYvcU9mUXJDbmxncgozckEvWW94OWYvcHl0Qk9LL1RzSEMvSHVkcTNlcVh3cForN0lqUFdGMmZNQmlKVmVaNmYyeVlnMWVXMW43ckFaNi9kUnY3MlAycFQrCmY1K0JqWHZ1V0dOODJuWkk2ZjArNmpHK3p1Q1lyN005UjcydTY4czkrRzV6Qi83OTA2MmxOMUUrblFMOURyN1kxOGxleHZ2Q0x2TzUKQ2RkUnorMjZ6dUMrdGMwNjIvZTJVMEsxOTFHUDhYVUd4M3lkN1RucWRWMWY3c0YzbVR0WXRmSHB2dkswODZkWGxoZFlQcjJ5bC9HKwpxc3Q4N3NCMTFIT3Zyak80N3l0K292YithZHRwS2NQcnFNZjRPb05qdnM3MkhQVzZyaS8zNExzRnNsQVo1ZEd6ckJPT1lJdkFiZjZkClJ0dzhhZ3ptWlh3eW1oYTh3eEUyUHlvWWVSMlZnM2VhMzE1bkVNWWRqb0JvOGIxdGtScjJmZFJqdk0vZ01wK3pQVWU5cnV2clBiakMKRVUyZTczMXpPSmUyVHdmaHROblQrK2N1NDMxcWwvbGN4am5xZGNIbkRGNDNCelAvbk92VHRrVWxZZStqSHVQcjhSeno5U2ozVWUrSAovdVVlZkljVjkzNDVJbHQ1V1R6aHYxOUN1d2J2aDJYanA5ZmwzbmEvQXZkUmp6RjlmbGZIU1ZEdFJ4U25lRm5Pd2E1WDRmenNkY3Z1ClU3eHU3N1h0ZmhEM1VZOHhmWDVqZklyZjhjT1BYL3YwNFlmNWRZTjREYXQrTXZwNlA5M011RGV2bzM2S1Erb012c1FoOVNRL3hTSDEKME82akh1TjlCcGY1bk8wMW5KenIrbm9Qdm56NHI1dHpmYUxuSU9mUlhEOTNHZTlUdTh6bk1zNVJQOFVoZFFaZjRwQjZoejdGSWV2bgptM01aWDQvbm1LOUhlWWFUNjZGL3VRZmZZU0pQMGkxK3VxL1YzVHZmOTlYZDY5NzM5UmhmOS9XWXp4MDRSLzJVcU5ZWmZFbFV2MC9zClNsU1B2bGNYTVFYZzJDbmZacDlGbiszVEZIQ01yeW5nbU05Z2ZZNTZEZXZuREY1VHdOY1QrejdEd3ZhTXcrWDd0RG9JODh2anB5Zm8KNU1BeDJoUDh0RHFRMzdxRHpENnFQZHozR2RqNGFYVndiN3M5L3Z1b3gvZzZnMk8renZZYzlicXVML2ZnK3dTWjVSYS83dXR4NHEvZgpQKzcrZGFhWDhiNnF5M3p1d0hYVWM2K3VNN2p2NjNIaTcyMjN1MzhmOVJoZlozRE0xOW1lbzE3WDllVWVmSzhnc3ozajE2MDlmdngxCkNzZmp2MDcyTXQ0WGRwblBUYmlPZW03WGRRYjNyVDErL0wzdDl2anZveDdqNnd5TytUcmJjOVRydXI3Y2crOFRaTzRSaFpzajljOUQKUVpoZm56ZGpZUG5UVU1CNFdadWZod0ljQ28yRTNrZEZGTzc5R2NyMGFSZzQyKzBQK3o3ZU1iNSsrNWl2OHp6SHZLN295OVYvNXhEQgo2NmFlTC9ZNmhmTnRYeWQ3R2U4THU4em5KbHhIalp0MS9mNjVxZWRyUGR2dHIvbyszakcrZnZ1WXIvTTh4N3l1Nk12VmY4L2d3T3VPCm5nLzErdjN6U1Y5bmVobnZxN3JNNXc1Y1I0MDdkZjMrdWFQbkl6M2I3WS81UHQ0eHZuNzdtSy96UE1lOHJ1akwxZi84bngvOS9PZS8KLy8yZi9mckxuMzhicjhqbmYwYnNzK1lGYVMyazV2Z1BNR2wraFJXNE45U3pIL05Qbjh6dFk5SFRxOHpuR0w5dDNZZjRQYy90ZXdoVApYK2N5MUlKN256bmNRSnJseThrNGJIdjZyTGExNTJOaFFXTjZxRytYbWJXR05MTkYrK2NyOTYvOUJEK3pFdVVHTGdCNmxINmNlcDgrCkRkUzNmTHpxTkpQQUxDUDlRUDBrcWdwa0pMSkJQemhMbk1kZ1AwQnQybVAzanlGcCtkSlFSaVBqeHdyVXY4OWF2OWo5K2ZnUS9mc2YKYjVvdmVlMkRLdEl2bzI4TldSQnhGOWpTeDNjc04yL0ptbTM5UHFveGZMOHdubXJMUkxxbHpPUWk2cmJnZWZxazBKRlZScFRmK2FxZQpNSDQ0OXVmeTBSalo1Lzh4SHNlbWJYai9jd00rUFlBZjlXVHl3dzVpdEdmMGF2N1ZWcmFTcHJVcGxDL3JTUDQ1ZmVNeXJoWGI1dDYvCi9kWlI0OWZtbjlUMCtCK2VqNFdCZjI3dTE3R3JlNnFOV1FjQjdHcHVtMTREZzBGL2pQM0hpQzJmN0UzWnhVZkhaSmp4dDM0L1Rnek4KWVBTRTBXYWtGcDhZY0p5NnVLWWlVaHRSKzJmamFtRjhZc1BGK3h1YkxwOXY3M3QzRkJqS0JvVDJiLzcrZit6NXNORUt6ZXhuRUErSQp3RlZ2WGVOWlZML1BiTGd3LzdZSDlBQTNuUDJkZmJ4YU1kS2gyVW4zUzQxMXFXMTdhR0R2WEJqQjBXditKTmd5N01mWVArVnNNMG9DCjQ2RHhwZGFoczMzWURPTExkNzZFRHRQdkkxTm8yOXpuMVBmdTcvUDNoU1hVdHlYZmRiYUEvelhNVCt1WCtTZWJpYXlSK1dNMStzM0cKN2tmTTFoTzJvUkJkeGhUajQ5ZGY4MEFJTGdES1RmVVBZTjM5YW5NQ25rU0h3WXRnWTA3K0tqaDgyUWpJc0o3YzhnY0FhNHJ4TFkweAp2T25UWTNRaGM4REc5SGgwWUdvNURoQVBzK3RSeDdZamJnNGV4bTlld1hsMXh0TThtTEtUWGJ3NnJGV1Z1UTNQZXl6STJXYmdER3ljCmNjVWx4dU12eDQxSE9sV0FyWnUya3VlMlpCcTB6TlgzY3FvUzA4K3B0REEyRHpDRFk4RW40OHc5NzcxekdOdGEzMzd6NTMxZTZBUFMKdTZiMDUxRk14ZGJxTDRQUm1EQjZXbGdqNVczci9nUldxejR2ZHV2TVBmYlhKNlFXbmkxKzY5ci8rdms0cmUxcGdEQ2FQQVV2QUJwawpiQ3ZHRVJobjgzTm9WWGVMeHNldkNFSDhYNHhsZVdiODhsdCsvUWtnS2I1bkJYbWlYOFA4ekI3bXdXdWpjYVRmTU9ZUnhuai9VWFpaCnVzZlpqS1lETm83d3VQQ3AvT1lKK082d0VWb002dXlTK0t2Tk9SZWZCRWtZTXNhOGp6ZXNaaHViUHhaOGJUNHhTYTFHekVJbE52MVkKVmVUM3JQTDFCTTUzeFQ0akdoTFgzSjhWdTk3RWlOajhxYkFOaDQwbHZwK1AxN1Nlc1ROR1ZLNUlQWHBxZW53SVMyMnhxZWJjTDc5Lwo3dGo3Z24vOURXdjlqVnZ6TVhyOXE3amxvNGNaczhwUFlVWnJKcG5yRTNlSHpXTmtMSG4ra1Z1dXQyeXFiU2x2SlNlUlg4T2N3azFnCnRiMk1wTHZLT0hRZldQd2JiZ1lCS2ovK0VPWmNZdHVSWXR2Wi9HTk43OExYRTRneks1TEllSnpQZnBqczlWNUx2RkRObjJFQmd6S0YKZVNUOVhoRml6a2FOUlYrUDZ4OUV4ZmFZNjlyNlY1dkRKMmppSmRyWTloQ3A3K1hEMXNyK3RSYnpHOHg1blRQcnNTMGJHK2drMGd4agppZy9XbzZSL0MyT2dqeHUvVld1TGcyclErWHIrY1dHZ2J6emorckZmd3h4dUdLdWlaUHVZWDFaTTVuT0ZjV3lmc1V3UFhEQzNlQklzCkU3Y3hMbUZsVGE2d1BUR1FrTXo3NDIrZmxrZStpYTQyc1lDaDkvQ3J6ZWxKSHRXSEozNFlTN0ZuUmVLSmpSNDB3TGZ0OXF5bWNVSTYKYnRJUzRzTlllNDhmKzFqU2Yvdk5NN2g5dnZnNStORXh3SEM5ZHp3NUcvTzR2TFBmSGg5c0JZN0wxaGJ6ZTdncDJIL0ZvUE5IUEw0eAp0eU9adHNjNFJoaWY0NGUrVHYrOCtuUXFOUkd1dUs1b2R5TnppMitOMkM5TjhaT1VjZG02YjRHUDhKdUg5ZStoajBiekJYOThnZlpHCjBPOWlMMGNuOENRL2hmbXA0M29ZUHNLb05ZeHkrTDRjTmk0dmlZYjErZkt5ZUFOeUV6U0N3N2E5WjFJeFpVdzlKaEIrS3ZFQ2svUW0KTXhhQk5xNjhEMURpQVBtSjUwNmN3WSsvZlY3bkN5QSswUzl3S2VjTHVONXJyV0cvdnV6LzZtL2Y5blhjZi9IeGcvLzlmNkFmZ2llNAovQVRwcVB4cU0yTWtOc3Y1WEJoK2l1OStEQk5zNFJhL09CNnZ1OUVldWxhL2NzUjdhZE0wVXR5a3NjSklKaFYvS2NXeWNMSi9RYnlECm5uUUFYdW5KSHdJYlBNaEl1SitNTlc3eVhBcXY2SzE0dFBZaG1pVU93RmZGUm9TRS9aanJPWVB6SlgxNGFIRW5XdzczWWE0bkJxUDgKSkwrcDlrdDgxejErRHF2SlpRelBGWlRPMzN3NnNUdFYxL3FTUGFiU3VIek1qdzh0NzkvM2htbFB4YS9CN0ZrZU40ZldhdlFIZ0JtUwpMVUlzUThRLzdUKzBMS3M3cm1WanhNU2VHbGZQNXVNeWdtd1J1N01wbDNZSE52QW5tL3Z4bjNLT084QndudzVCTWY0bkk1c094WEZqCm1UTGtUc3Y0NGR2NldwTlgzOU5YbzZPMnZUdkRVSDRBK3ZweFVpc21BNzVNY2N6WVhjVE4rUGxZVVFONEdWZkZodHArVzNNY2dDdEMKais5eFRoOXJtSFZ0R0VjbHprbmZ3SGoyK2RkNFYrWlNGQVN0SWVIdzdjRkwrL2Y3dFh6OHJOSERjUHBPalRTbmphM0h3MmE4VUFkbwplOGdmNnM0dTQxbysxK0xRR1VoSkk4V1lXTUpabWVnc0VZZGx1RURHUEZNOGdPR0p0d3FPcnhONHdpdEF4OXhTWXJUSGZLd2JXLytrCjFVOURMWmxLeGMrZ3lBa0NRNzV1WDMrVnVDOVZuZjg4TmloSUFVNVRibkd1Y0VYOFF5M09kWnpIRFZ6TW5INWVYZE14anByaWpXOTEKeGxrOS9VbVhNUTdBVm1lK3J1bTVsQTJSNHoyeUl6ZEpieDZmWHMxOFhRR2ZzUTZicnhlK1o0M0UwKzNpUER3cUxEdXprTXIrcFlqcgpUaEQvWW5BaWNROUdFRnppVko4d3hWZUlTZDhmNFRDNU4zd3l4NFRHQ2Z1TlAzRlllcHlBaGRsUDNyRHVlU0E4eXc5ckhmc3I4ZzM0Clk4ZTBaem5VV0dIdlgrTXI2SzBYYjNzQzdsdzRlTXZWWTJJdXBlOERuQmhjckRySFdSTVp6Z3NqSnJWOUExTHlYUjJhQVgydTAyTWUKZXNpbEo4SVZjdnJIRUxIVkg1eEdZclNUV1BHdytabnBxSWlzeDB6UWNuRjhpQTN0NG8wamtSTkd0SnlJRXl0ZVlZeXFySnVudmVvNQorc05jZWpoTzRTdU84aWZka2VLNWwrTURYWGhqd3h4ZjEyQy90ZmhtS0xxaGtSQXgvNWFtVGRLQlp6eFpMRWgxZ0x5RDgwQnZhdHdICjRLbUhCeDNCWDlBQjkvRFFQbFk1K3dEaE8yQ0k3RzI3amdNZ2MyMDk4dHhtemlQMjBWSTlqdVp2KzUrRXV2ckYwZlNOUTZTMXg0N1MKdy9qRTlORkxPQkFmWmc3bEh2ODFKckt0eTlxdjR6ckdKKzc1TTN3QXRFR2Y4ZWFSOHZhVHpXY0VqVFV2eElNbEZnUGtVOGxJRXBzbgpDM1lvMEFIR2RpSStwb2djQjlnZmVzU1MwTW5RVXdpQXFSRkx3Z0hLZnFFMXJtUFRGZ3VYK002Nm1VSWVKMHJlKzNPNXBQMzdpbTE1CjZSNFVGRHpyVTExdy9JQ3pYeHkwTTZ3eFVueDhXbjd1NkNMZFl1RkNud3JHRGE5U0FGQm4yMVZsWm1PTUlLd3pDLzl3UFRNTzhPekoKVllNcWRpOWxSNEdxWGE3dUZKWEgyaWNlR0ZDdjhTTEVZTjNWZTZxRzEremR0eDlaKzdQMlVjTzUwT3BSTmhMci9FdDZCL0E3MitGYwpaZXpkQ1dDTGp6L3ZreW85bmxlTXk5MWtQRCtESEtmRmhoYTZWNGlBN3VQbUZ0UEZxaHdYZTFPSGFXN0xrVUxHbUxCdnB3Zm0wajIzCnNsdk5UemFIM3oyMVhJQVJoT1BoUmNLSHQ5bHRqRWtmdzBmeE5BSnozdDlvejluYlB2dkQ1UkFJSXpwaHJIQXh0VkNWdWZad01MencKak5LaTk1c0JldWJhazN0NHpwMkl1bmkyOWpyWStpL2M2VjZWaWUxWjVWa2U4Q00yakFPTVdJM0dSQTR1K2Q1MkpFM1BhTnkraC91NQo5MDg3YkFqZlh1NEZhKzduL2tBMW52VUh6YzlqS0NnUk1rRnZ6QkYrVjNPMjhjTTQrZ2x0K0t4Y1Z1YlpjZFo5Z0RialN5Z2Erc2oyCm0vdWo4YkJ4d216K05tSi9FcmM5dHpXUHlSQkE5cGdnbVdpRkVWVGZ4eTlNVEdQTmlRRVA2dUc0d0h4bVFyeTBQOWxjOGhPclZTeW0Kdy96c1lFN3ZXbTAxbjd3TzhXZ04yWEE2TVVhc0h1Tko2MmoyR1c2cElyMTlSMzA4cFA1T1lkcDZ1NmtSR1g5bThvWHBVb3VwOFR3aAp1K21rd2JjZUgwek16czNOVlR6NE55OVZBSUhmdHpEbVp4anovc0sxMUFTMTd3blhONVY5QjgrSE1HUFhlV0ljTTg3ejJkUHlUSEZHCllHK2YyNmxmZVhaQXg3RHREeU1BaWc1cFd2TEJlUXN3YzZUQlpQYmNTU1IrZklQaDhLQzliTjNSdlNlOGVZTEY0dz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJdUI5UElRSmJRK2hoMzM0Tnh1RHg1NUNkSkh0WDdEVVdRL0ZVYnNUbXNTTlFqZ3JVZHNVUGxpTjUwU1hBeHNqT1YxTmcrV001NnliVwphZ1MwN3pUL3J2dWlZbndIZUQ5dkI5ZnBPR3hiOTFRdzlhSVEwYi9kNjVUMi9vOUZMZmh0amZuc2JyeUhNRHdnR2JzanNYWU45VmkyCmN6alZhTnViOXIwcFdtVDVvRSs0T1FsQnB4OWoveDNWSVJCWXhxZEZzREdWRmNZMFkrVkdUeTBDY3ZQWmQzcHB2QVdyY2p2TlZCajgKN285SDlNRDhDbGVQcTBjWlc4cWZyd3NBOXUwNjdWaEpQUU91b3hhL1U3eDA3a1dHbzEwSTltK1p3RmxuSW80YVg4WU95ajlxa1BpYQpjcGlXeU8vWXFzeHhZZWQrbDdVWGxIWnliSXdCM3owVFphMGVnYVlXdDc5amp1WjhSVHdWR2J0ajJjQzlacDhBRUxKN2Vvc29QUkcrCjRmNVovRU1lYkd6WVl1SEVCdUF4T2JHM3ZIZmZBWXltbTFxc0x2TFB4MHFpM0Q3U2JCcEdQb3lNWHR1ZlV2UUFMZE42eExDSUs5Y0IKMEY0Z1hoZjY4RExXczU1VHhoOHQ5cmJNNm1PSkdQdlhLOXdWVThXSE1hMklBRVdTb1h5ODdqdHNUKzlZQjhCWHRGZHpVdzU4S1hJVgpkQUpacnhBeVdjOStnMklZNFA1N1JWb2o1MFVKNXZGSGJhekhHMjFuOTdiMnp6OG52M2FrV21VcXFTaVNmOGpnMm9yRHhzM3VFRDJzCmZkd1B6Mk03RFR2RmZQei94eTR0K3JLUGU2RWZhZXUrWXNBY3JVU0t1NjBZYjJPaHo3NkFFUkk1TDFaV2FrWFdPRmY0WXR2cDhPQlMKM0wzTXYxL1BBYzRhYWorWExOMmMzeXl0Zk5GUDU0bnh1WTRucmlEZEFTK0hrR0RjOFI5R0VuVFU0Mk1PYUpQaUFCeldYODRZT3Y3RQp1cWdVWi80VGN2ZzdZcHpxM2o4UyttN3I0VzFQeWp2dVMySkhLTnYydC9WZ3RvbmZ6d3BWb012ZEUyL1JJMmVTc3ExSXp1UWMzbWhlClhDTyt2b3g4Ui9LTDE3QXc3dFg4Y1lQUW4zUkZSS0dYV00xL21NdStMWFhya3ZLU1BNN20wTVNGbnN5OU9XUnNPdzFFYlVVSTVVYUoKUVkvaHRKOXNQbTRPeTNYQ2ZBSkp1WjF0VnppRFQ1UFVBai9uaSt0YXkzakwwbXc4amxLZWV3SEJsbFo4T3pJKzRkQWZ6YWw1SHUweQpaZ3dRSi9aUDg3ak5YNDBmRC9wZmVOc1dHWUZsalJyYlVLVklFL1FTUnJiVDFaYW9Zb3ZmWWxkVVhTM2NJQmtqYnNvK05TTU9zRU9CClIvbVd4M1pBaG54akdQc1ZzaXJkZWtkMG00Nmg4NFFqY3Q5KzRjbFU1SHVlS1UrSm81WTlTN2tkZ013blpzWEE0MDgyaC9CaDdLZzgKRy9SVW41anl6dG1kOVh5dU1TV3dCWEI0S3pVcFVZNmZ5aUUxWkVCRFc0WUdjbHdyYkpqM29pSWxUUjh3bnNCaGt5NkFYWU4zc21WRgpoamkzVzhNY0F0R0szNDJiK1AreDk3WHRiZHpHb3VmemZSNzlCL2EyYnB3bXBCZFlBTHNiSjIwc3lYYWR5ckZyNWNVOWJhcERrU3ViCk1VV3FKT1hFK2VQMzQ3a3pBd3dBdmk5bFN5SnA5c1VpaDhBQU13QUdnNW5Cd01WNFNHVTl5QTVyeGlRb0gzV0Vvc2N4VVpIRjJpSlYKTHJaVDJjY2xuWkR6ODFCRlpqc241L0VWbVpTM2VwbzVDTVJuQWgzS2hBV2ZsRDZPSTdNTzlDTUh6aFJIcUJvWDZDTFJsTWhIZWVVOApCUkpQeDF3L1k3c2NQdDBvZUxwUUxua0g5R0plT1pNVUFuM2tBRVhGV2dUQ1Iwc0V3eDRBcFdMaklybkI2amJhMUZ0SWhYZnR5Y1JtClhROHM1TmhVSmJKSm9aaFlvNVVGdXBnVGZDYlFtemhTTHpkRWlCak03TE1rRmtpaGFCWm9NZ2JxbkoxcjVOZndrWDNldWNaR1BIcUUKbEtNTEtiV01CZEliaTJGcVdRU1pmZi9hclRwN0doUE9VZWZZYVBWSWtVVnVZM3AxeVllRmVzcG9OK05vMHl6M3prempnaE9OalZOMAprOVk2cU9oVnk4eUoxQ0xqdVlSbGplRTRlcmRoUmpHc3h2dVRoWWtGZFNwQ3ZHcnVLQXVCNUZTV3lSWE95dzFBZXFMWmtoQWp3QVhCCnM5bnVnOEk5VGVFa2o0Y1Y3T0tqc0d5dW4yamVyNVQwbldXL0c0WWhXaE9EMFA3NGpJOWY4ekZKdUhnZnl4bkZiZ1NCd29mRk9zY2sKNFpPZ0diTUx4S2gwd05SSkRtZm9aTHpCSFVablprYkE0ZFBzVEVKZzdqY2dIMWlySXc5VllzKysrTTZKYngvV3RXMGZYN3VRdkZ1aQo4ZExXVjk1UmpmY1NiQndOdm5YcHdob1FhQmdyekh3NXhWazhmZk5oTjVXK01jRGx4WnppMEdDWVVTS2FuWXlBM2x0eGE1OE45ZlNHCnJRL1F3ZGRlR0J5V1E2cFVBUE0raWo1ZTYyY1NvSDhIRjZxd1J6aDhwOTQ1YS9FeFNNb0U0c29heGt1dk5GcWdOOEJ6aUQvQ0ZKK3IKM1B1d0RxMzNnN0NqUzZUV05PdGtuWnNJcWUyTGxmWVo3MWY4T3E5VFNtMVJhZk5jV1ozT0JVMUxlc1dROTBXT0twWTJ1TkFpZFpHVgpJdlp6K2ttUFQwejZRemdmb1FWS2NGYUoyWDJLUlROdnN5eHNGQWdDdGRjdGZFQzZqRHdvaVJ0dllSVm0yL3ZNS2tjaXNZNWNOMTBFCno2SWtzck56L0NXKzVaSDdaWkM0VzBTRnRSdTZrZVoxUVBIZU9WOUFLUXpIYzhFMFk2VUw5NXAvY0ZtdlNyRjVKSEZQUjduZFJuSnMKS3MxZVMxWWMyK29Wamx6N0tGaS9nMUVRVWQyRnJ1WFI1bUdyNTlZNzRCYUg1S0tzeDZIV2FsV2JKSThGdHpZQkFia2luRERVcmlnSgpDamQ5N0RZeDc3WkVjQUNoZmRuZHhNcDlzQ3Z1OTRhdlc4Z3NrTW9HYWdRTDlyOUk3YTltQkpkZnFxeHFnbGk5QjEvNWUwWDRzQXlqCjlYSDRXYlFoc2M2WlpKR3pqTjdIc1FqUXBzUENyQWozdzlDMHg5SFFIUEdReE1ZVjRaejFpRUJ5VUIvczNLbnZtSDErMHdzZDExdk4KeDAwaG1kM1NCR1U2Qy9YcHdVWGJBY054TzhSRzZUYWF0SmczdUc3R1NDOXlaS3FpS1ZNNEJCUXp5SmNjS0FHY2JjNmY0bkFxdTdPMAo4YzRLQkdxdmlBamg1N2V6c2hucmplRUpydDB4MEZnaDY0QStSTm80cDN0UzJIZDRDYWo4aFNrRU8rK1VzU1kzQjVRY2o4MHEvYXpiCkYyN2xLaVpCUzA5WFhyRDZIMkV0Y3U0QjN3L0RrcWxoWU02bkxSUW9obnRBandOWklNY2FHL3NNa0FVYWQydzJWZ2d4QXZLbldMRE8KM1BnS3diWmhaNG16UlRsWXlkaGJVN1lnV3hxTjlSc2V1UHJrc1NRd1Nnekd5dTV5ekNpWm1nRE8rWGpKZDUwQVNJRzh5dCtUc0VDVApGVzQ1cE9IeUVOcFU4b3g1YXhnQmgwWVpLK0FkMXN6ZnMvRDNFbEhZRytZWTM2MmhmWWxQMkh4RXArZkFEZk9yQ0pzVis4S01EY2x3Cm14VnJuc3JaTUxGNjdxZDk2alYxTkhaeCsvVE1tZHMvL1UwSXIzU2hHNGVCdEJueFhpdWRwZzdkY3Q1QWZBZzdaeDAxMVlwM2NPbWMKMGlZNmc1QmlrUHZCTFZnem9CeWxLcjZMc2VBK2xPU3JqdVF2dFVBT2hhRFhibGx0eTNtdzBpd29YWEFBWVFLODBtWVM3aE41c3B6YQphdGdZa3JENFJLZ3pXMmovbG8xVmtpVVB0MDBrTTZaUUc3c1pXS0RSR2ZkS2E1NFlicGJZTGpnN01LcjV6a0dFWGNnWnFFSVBOQjhPCjUxK1hZeFhSK0VBeVBGTW8xc2hGNms5TGlXZGhjT2NKcDdNcmUwdWdZSUtENHpHNmhrZE9UT2F0YyttSTNGOVE5QmM3SFRnVGZuNHIKUHB3RmtlWk1PaUlmVy9nY3VJZmdqT1ZrbGl0dWl3M2Z4bDdENUpiNGdpNnBraTFHSUpnSlh1L0w3ZHV3YnJkd0N6eW5KeE1kVWhOMQp3Ri9zby90S1J3NmNKaXdvS1pDVzBYcUpsdVRjRm5zRWpGV2dHRy9xVDR3eTk1ZmtWRmhoeXQ5OVU4YmZ2UEZlZUJHMGllaHlZdTZ0CmxzWjdzTEZiWHFnSDh3MGlVTXhGVnNnQWFEU1BMYXVwTSs3Wk9iQ0xJRGIybnR0NFdXTXRQUmFZdXhCb1krMmNiRThRZmd2aElEV1IKMitnY08rT1Vab09FczZXNjYxNWNuM0wvaDBWcWdieFRPS3NWQS8ya1Q5bXZobWFLY0QrVGRlcWk0YStHRmU3YUJGbytKTjhPTFRnQwpHODBrTEdRVGR6aEdJNHZNK2FxS2xteGtrZjQybnZSSENnQUxMN3pEdVE3QlJlYVhqTC9qelRKU0ZuYXJSSmdMUXpPUmpZVEFPZ2JQCkxEdXpaRUF3VnBhN2xlWGFuNW10OUpUQ3FpUmpITVRIRlhsSGtKcERWUW1hOHc2ZVNHOXRDOWZpNmJLZ0JXWUpLMTFTMm10ZUFHUjMKWVh6UlVlSWpodHdhNytzU1BibWVYenBueTZCSWVhTVEzak9HQnNQQzMrZDFZWWI0ckxTL3V3NHJLbk5BY3R3NEVyd0RJWTF1YUJYdQpEQXZBUEdmMWpQWTNMcGt3MEhzWHNhenhWNTU0enFUK1pJWjNZKzNxd2pjaEN6WWVKWkVabEk0amRzbzd1NTd5c1VQYTV6REFoOXo1CmJsYnVUNVpvbk0zODN1RUhCaDl5bHJ3dnBnVmpEVWZMeE4zNGxtamY4SHNFcmhoR1MrWWVaVy9lZXdlSzltdE9Xd1d4VG1icklLUlQKTGRqdWJkd2xMTHlSbndack9vVkcyT1lTdzZaNzZXK0lzZjBOamZ4K0VFTkFnRVM3VzhSRU8yUFE3dWN2ZithR2ZTSjhZMWpiZ0NQMgpLR2gvWTVFamdoQm9XSFJ6akw3RVFBYkRVd05uTHlNb25LVnFyQWVGUDVOd3BBbTZhaFJQWkZXd2xvM2VIczFya1I0VnQ4QndTWTdOCnhua3NqaEsyYVdHNkJwMTZpWjV3MlZUd0ZVYStaVGMvM3dNN1lMUS9SOHFjNzVGcGVvaVR5MFdUMEZmV25IK0JacjUzZGlVOGpUT1YKTWxIc2V0QStRUWMveG03SHhYdUt3bG5lWlhCWVVKK0RET0tjQWVqYmN6R3FtQmRDYzFxSmNNL1NuVzNucHBvd3prS2tmV0FnT2gxegpuNGNnRVp4cmdyWEFlRlR3eFhxdGZWOVRMbHV3R2t3M2QyMUJ0dnk1WkFOY1h4dSsvbGs0YXhRaVZTb2VBd3MwN2s2UXRpZEdSaEN1ClZYTllUaHFPYWJyaHRBU3NML2dlTG1rc3ZyN3pxbWtNOEdGWE1Qa1ZDT2p1VXdDTVVrTW92aGp1UGNrcDAwOWI0SkVEODdrY0pZYkgKQ2dzZ1k2QU51MFd2dGVadVNSMDg1TnJuUzZBWUFVYkxKMVh0cjFjQ01OTktNVEJMUGRCMWxwUis3emYzYzlCcFA0alRlQWE0S1l3RgppMEt4ZU9mcGl1NTQ3Zk1jT0w1STlwQWJObFZqTUlGa2ZZVGN6Tjd0SCs0UDI3V0tSWFBKbmRlS2d4RVNrekhReHlSUjNBSFhWKzVHCklyK2RiSUZ1TDhVK0dXWnFHdWFLOUs0RjkrcTdBL29iek01RU8rdkN2d1ZuT1NlWFlUOGhCa240MVdhY1RvdWhIMTZJMHJwbEJPR0kKeHZHaStFQzE1T1dldW1nMURCTGg1VXAzcmpoMGhHOWphT3NyY3ZVRmI4VSt6aWIxT29xMmxsTkdRRWNIVzFaN0JHSFQ0bHRWc3k2SAp1N0x1YUlHN1F5NTlVQXp0ZHVOTEhpK2Q4eW1DWFVHcGlzNWNwTUZidk1ydlpYaGU1ZXFxOE9mRGxIRVdMbjdIYnZ4Y20wM3k3c0JqCmdXem8xdmFoYVF1a3kyQ09yTUo0QlBSeXRwT0RCUmVWL3BLL3RwNC9Lc2pYd3BYM3BXRUlWUkRaN2hvZ0FqUFd3S1h6cGFYYVBtZHUKRVlTSUZPMVZsMmdRdFhYbVdtQWlHQ3NsUkhWck1OU1hJZWNRWDhKREJCbXJqMlM4WUREYjhJTktoQmdNVDI0T2NNQVlNSmM0UnR0bgp0VDI1eWllb2NOWTFCRW8rekxMMmhWajVXT0FOSGRnK0wwUDdxcUhqQ3lkd1VPNE1oZUZxR2VzTU1tSHRGY0ZlOGFFSXB5TUgxczYxCnJuMHdiR3E4ZVJFWngwRjRmUCtCUm9NSEVTM1VmbTVrZkZjaGpTTS82R2FGQmRKdEtHWk02b0JCZmZSbkU0UTZodzJnVFZ3Z0lzYWcKZXNXQnc5am1aNUF3UEk4eTNqZlFzczl5RC9iRm5JRWVRZUhQN1FET0ZmY3JrM3hvU3d0TzU2VHhxR2haVTNoUGJUamlBekQzaHp2cAo0eHNMNnd0MlN6SHpzWlFoSFFOTEtEeUsrbk9CaUNJc3M1eVhRdWJTZmlBQ3IvMFo1N0ZBWU1vSDdOeTc1MVRpVFZzaHA1WUtsNjl4Cjl0aHBPRCtoaE00SzFuVmRwRFpkNEdmYm1NZzFJK0RBRTJObERTTmdLV21zRk9leTNxeUVscVIvN00xRVVhY3cyVVR6dEpmT2VJSFoKQmdyMjFKS0gzRFltZkxTejl2ZTM4VUs5NWdPZU1vYXYzcy9NVTVGRzlza2s4U0hNS3RWZXNiZUdLWXhMVnB6anp0OXhSS2czbExCYgptNHI2ODRKTFVVVEpwN080Sll0QVJjbFVjdWxqb0lPeldtYzJya21GNkJzVEJSQUJtT05NUXNZcHhPb1RRSkd0eUFMWmRVdG5tOXdqClVJblhvS1JrQkR6bmpMMEtVS2U0ZE9sOHN0cWFnQzBDN1NOd3RZM0lPM0pndmlha3ZmVmJhVGEyaEF4ekNvZFordz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJNHhYTGI0VlRUZmw5eVlTeVh0U2prdktESzZ1OGM0UXNqWFVic1MrOGNRK2RJeGFvbkhLTWRtYjI5S25NTG0wM2E1VXJLbjNNQXp2MwpWZWFqeTdTMW8zUDlZUEpMM0kxMVJPQ042aXBKR0t2eGxramFqUmxCSmpsVlZPb1ViT1hDOGxWc25sUzV2MTZnYmRBQ0krRHJnZHE2ClhQamFBbnNGM1pIZG91Q0R1clkzV3kxUTVrSFdpM0FkUXBvZ2tyU3pJNm5jK3lhMERWRjNRTzltNDZ0MUNtV3FGd2dwWDFoVGhRMkEKY3R4MUlUeXFpSncrSnMwWmcwNjl3eUcxOFFrS1hWK1M5UnMrZkdDNmRpbjk2WmNka3pxSlVzQzVHenA0K0dHaG1EclJBY0RDRzFISQpZWHZncW1mZXI1bmtWaUFBa0ROQ09JbGlnZXdZMWRhV3p3aGtFYlpYd2JkYWd0NlRPWE1nQUhQRG94RHVmbUJacGtvSEFncldPL24rCmxvNlBkR1RwNStxNW5reG9xTVBWVTdyOTUyQWg4eUdwYXJhKzhMbWtjQnUyR3o0bDNPY3pyY3VWQjdEUWZlMTduM3FzMmtaWUlWRFoKNkRNcmQ0eFBjY1hDSDg5amZNa1Y4eGFiekNzY2ZCMHF6RmN0RmRlWFB1eFArS3RxbUF0TDhtQlRrSjhGY3U4TjV5UFRYbUNReW00cgpHNS8yUmR0SW1pTUhscEkzVUhkdXdJeG1pYzlJNXBSK0JFcldwRlVZVXVQRnNmYVplYlNKNW85MFNoQ1d6SVNiS0ltUG0wU3dZSjFQCjV0WUJnNWZKQkV2TjFLVzEwb1p2MHVNNVBhclBWKzIwdlczQWROSDFHTmJqY29laEVJWjc0QzZwWWJDTVR5eVhzLzFDWjlGSlVSVCsKVnA3TGoySkw4NDI0M01iMnVMS09Cem1lRi9pa21QTjFYZ1Jyemh2SmpuMmQrN0IzWktLMUZ1RmR2OEx2YU40d28xR1J5L2lvNkt6QgpKdkZpeU9XZnNzQ2N4U09wZ1ZRZmJ6RjZrVWNYNzQ4Y09KdytpUGQxdWtmSjdsRE52akVqdkJQTW5WMHNXc0hCTTdyaEwzRUdzNHd6CkpDUE03NGJLYjVKR2VGMUgrL1JRQmswbG5HK1M4MndZNFNNWXRKVjhqQ0FzVGZLSFc2S2t0ZXJhS2VPc05VYmF5NWRPNHRpNVpWTHIKV2JZTU5LeVpFWmpYcHQwT1Rlb3ZQbXViamF4dXI5eEtOa0dDdXNCTUNUZHh0UlVUUnc2Y3kzeGl2aGh0WTAwY1pRbGZVaGFLWjJ4MApxZHhFcDMxSzcxdW51OXVaNEFYcW9ueE1jTmRwdTh4cy9aQmhGMXZWWERRWEV4WUV2SlB1VFJBVXNzRDFNejhHOW9pRVY5cUQzOWdkCkdoQ1lDWC91WVhNTFhYL1A0elhBbC8yRkMyclEzdmxoQ3NwUzZHYTc0TGJZZGE5dHVnbEdtOHZDSDZCenpubVFSRVorVHErRWFSTzAKOFhZTmUwYktaSlNKa1NKV0NDK21idkNtUUZZK01NMkRadXNTQjR3QU1DUWJwY1hMQ0RJdlkwbFMySTZsTmlUYmtwRVduRlBDeXowWApRNWNwbTREYnpUaGVjVkNTZzI1MGRLY2NTa3QvK0JMYUpTRlJVVXBrWjU3S1VPVk8vVEdQRTNBb20rVEVpZFBDVGRtTVpLUGt5V1ZOCjE1bTJkeGpkZ05uOUtBUFJ6WjJsK0J5TEZrOEFpdXVuVms5Qm9HWlhxbklPL2N3RUJuZ0RYMmFpaEsycDA2S3pMSElyQ1plMUtzdDgKWWhSdGc0SXNnaXlLWUVoZHh0Y3M5eWx4dFUzMFo0RjhGZFVkWlN5Q1BOcm5OQWV5Wm5sa05jdGRMQkVCL2VHVGsySHdpYVZRSWJFTQpweFF4L3VvTUF2M0IxU2U4eU5tSGlTY0xsazFRTkZpR09JWXlLNkpvQk1xelpJRmFlMCtadit5RW1XRWttMmdwbTJMSWJPTXo1cnA0ClF3UjZ0ZHhaY3pPMEh2aTAwNFl2MldJYW5jemJocHdYRllBcVJMYTRTNXM1SHQ1OWVzU1FyeWZ4dVV1MFRlVmhnWm5rSUNCUldQRUUKd09DbUNxb09ndjJ4anpYdFBMSFhhQzFXbDNNRGdWNWtCTmJtd2tjZEI4TU1wUkZpTGtwM1ZzaEZISnRUOERrSXdkNmFXTGd6VXg0dQp3bW1iaHF4T09Zc1NyK21RRXNBSVFrTEozSXJZWFBpNFJEdzE1dHlCTkFuV3ZUeDBJSUJaelVDa0tXc1UybDNkeFpKZUxaTSt2UStWCjVXVkFKMnZYMmNJYmxKMUZHMU14OGRLa2JkcldsLzd5cnJaSE9nZFVYckYwbHhrcGxSTjNsT09yRVNpOVBvRm5sU01IOXJ1QmRyZHMKRWViRkk0c21BSEllRWxSKzJOMmFvNEJXdnF6aHNsbkJDNG50d1FBTWpwSW9KWnVNWEIwc3MvTTAwcjRveFlNRmh2eWxSSXRGZ0ZZWgp2Nk1uWEoram9aMVRZYksrRDdSQmFPWXpWNGVXQk0vQjNOMGZBeUEvRWVEeVhuTjk2YlUvc2gwNm9KOXFMdmxkNnFORVlpbElZSi8xCm5KTUtwdDR2UEk3QXUyOXlIMHVjcHo0bFJGUldScm82WjNqQkFmREdWUm5OaThLa1hrK3pkNWNRcVRmc2NUSXRHaFJtUU1oN2hYd3gKcktjSVRocVorbXhpSVNJRmdHeUlOWGJ6WXdSaEsrS1luRnpGeHFQY09wc3dvWnBQOTA5UG4xc0V5c2R6NHc1bHpRdDViR1ppZDIrTwpoaWpXVVh6TUFlYXY4MWRUcEJKY256SWhxZGh5ZzhBUURPR2pWQkJCTURlNnpSVGJ5dmtjeXlsdXFBTitaQVdibVhMalUxR0YzUUN6CjdSVXNYM25ud3J4OFJ2aStzcEtSWjFIb1lwSTVkcG1RY05pTFRBeVU5bFlpbnlZb056NWJGRTZ1akRQTm9VR2VsUlFPZE1mc3R6NWsKcitBc21GRjRoTC9YbnhmK0htWXdNQ05RcC83TW5qQ0M2QkVGdzJweFFVK3orRGxyVjBjaHJBRkVXVXVaMVFhS2NLNElrUmhGR3FsRAp1Wk10Q0N5OFI4YWR3UUNZaDRBL3pWWllCQWMzaFkvSExKVDN1cU9OS01rOU9OTWNXc1ZpRHpVcDd6TFVMdmtReldudkFHTDloZVFYCnN5c2NibmkxT1EzRVlxVm9LTGJSR0VlRThkZXFYVVNvUld2c25VWVZXOU9KZGN4d3VzeHFnU0Q0T1RiTU82SEkyR3ppWWJUOXdtd0sKYkh0M3I5RlljT0N2ZFBtSDZQcUVkOHk0b05JaTg5ZjF0RFZXSHpnRVJaWk02QXFZbmNjYnJsMk9Uc3cySVRsckFCMUxiWDJYQmNXcApGVzUrNUQ2aGl1R2NRcHlWMFZJbzJDZzNMMlYvbW5LR0JYNkxoakxyV0lxZEU2L0lRbUt1M0R1ajZVNlJ6OE9ZUlp4U0lSY1FaMHFtCnkwS2M1SVB5aEFlMityektxWTAvUVF3NVh3dmttdzUwcWNXbjB4UjhjNVd2cDlteTdzMkJ6TDg0RStYa3hRdGdmSW1kOWdhZUJibFAKMmVXenlaTVE0T1JIV1NKNElvSVU1cWJjL1JZc3FUZ1RRT0ZqMFFzWFFPNEl0eFlGUkJDeVdXWitkcHB3b1paVFY1QVAxRi93VnpuWApWNFZQNE9RT3VXVHQ5d2thL2MwcG1qdDhiNGZNZUE3b2N6VFFWUW9MVEhOT29oVTg0cVErZXdRdUJvYzJBbjRveFVzdTdZTXMzWXpnCmRhLzlGWEM2cE9qa2hyK09xMTAwZlRILzdRcWZTNXoxMkVJMWZBSlpUaElCc0pBQ2k2NHJIRGpCVldTY0Y0WHZpaGNxem1mcER2UUkKZEJsZzNMTnRYazRhbndEVk9TUlFKQ3UrVWxpd25FWWxKRXhPUGg4Z1dETUZWZ2tycEw5eEdsSVRJVEF0L0pwaEpiSUlJYVdaVjhJSwoxRmo1cWgyY0U1UURxcHdUUmxORU9TTlF2RzdKdEdoaDRRWS8rNDBMWVUrbGJwbjVJUlRldmg1eXJTQ0N6TThCdTMvVDVzWEpta1RtCkpRU0FDNTlvMWFXcktVU2M1MVZuRFBUWk5LbFRYRDljZGN5ZERST0JCVjlMejl6UkZZRDhva2h1azEwd2duQi96eWtnQUl1U1VMa1kKcWdJOXJueTFOZHlKS1pJb01RM2QySEJsQmFmbzVFd1NtQWZjTUsvOVUxdEZlT1lxOTJrWXB0T0wvMlB2YkUvVURwM2lHaVN5d2czeQpmQWJZOElrbUJ1YXpnRjZaazlFTmJBNnZHTXVPUzIrcTFlT3prOHZnM09MNm5yNm9xQXJKS3p6T0NRSmM2bms2S2ZLVllweEdqakswCjFQSUZaT1VQb0J4NWt6VmNBalE4RmFjc0pCUnJBWFNBOWhja25VaWtvdjd1c2JzMGhEaDk3bXE2VWVZUkpEeWJwZFBsaEErMnhUdVAKR2RlZjZEN1RsZHBKUUFLTFRLTThZa1hCYm5GMmVDQlFlL3UxTXczSXlENUhPZEVQcHVwek5wbzg5UmZldFQzTjF2bXM1RjB6bkFpVwp3TjVqVlBnekdDZlFEdFpqQXZJWmxqYWpDSUdjYnN5N2dVUkFPczRCWk0yZnZ0OUwzSnllT21PZU05aUg2ZWFhVWFYQk51dkNSeENvCnh1SnkvQkV0dkdsUzhCbVhuN0h6MFQ3elQzalN2NmpHbmg4NjR2cUQ2N0lqcWpUZUlzT3pKQ1FlZENlT21mU0haOW9tN2xnc2VVY3YKOWZmSTNEVVpCRXAyNFpJZVVGOTh5V0xtSFltUWZvVURCOGFCbkpwbnhwV1FNMmU0RHVsZ0tkWGp1UWZQU0VVc295ZllDbmF4VCtNNAo4bSt3VGVTTzREZlkwb0szWE9tQ3BNZHlSMUNpZFFmVUxLYkkyblhBQ0xSUDJzSzN0RXg0R3BMdllNeEtYc0hQc2sya1B1Qm4yYlJYCk1WS2ZVQlBBSVJrWDU2ZENvRTlBUkM2S21YaTV3ZFRlR0ozWUhCRHNjM2R4Vm5vQyt1eE1ibnJPVDNNUVV2aWxMbUNjRVBpVUFESFEKNXpOZ1BXRzZYMGZoNWFLeHZHejhjQkZmR1VhdzVyeHVRWkhtQ0V6MG9mbnNWeG1JbUphckh4SW9zQjBvRFJkZFFyNy9HWG5oN01LYgp5dnQwN3NEK1lqMWZMcE9adjdxUk5WekVrc3lpYS9uQlhJSFZNMVpPcFl0N3dLYUt4QVBkdmFDcDlxUG5aOXlwT0xjaHMrSGxKZW5CCkNiK2NGTXJ5YXhiemt5R3l1eVAzQVpzSWpFdldaNzlUTTdOYlIvNXB6cEE2bFk3Ri9EUW5CemI0WFZWbTNpU1grU3VuOHpsWkdNNlYKeFR1NHpDUEZrNU41VEhlQTN5Y2lOWXdQc2tiNHQyUHp3ajgyNHRPZ0U5Z1hkdStTRUpCbk5SK0ZreXpLZEJFZUg1aHFMVHhoTzFIYwpkOE1uMTZVem5lK0dUMnFnWE5aT0JHWWgzWWd4ODdyQkRZYUUxM1NUYUVtRG1UOHArL2VXTXA5NHlyOEdONFUxdkFMRjkwZHc1WXZ3CjhpZm5PblZKc3Z6empab2xxUmFDVTM4WTVWOEZkamxCcC9HeUNFeHNXaVA3QTZwKzV3NmNwUDZwME55L1laZjdqSytjMUIweGUxSkMKdXRMNVBRN1puL2o1QXhFbmx1ZUhnS1k3eGoyV0RjbUdaSm53VzZ4Q2VEZVQyN2c1RFVBUkxJWjgzUnZUc0hDZ1dlS3lEU0lDeFc4bwpKb2tJdC9qVGNJT04zL1FVM2lRVll2dW0raFUyR1JPaXgvaE5XN3h6WDdEN2g1MXRWSlNOLzduMk1CL1BRUWxOZUl2SWZEUWxSNTRnCnNQQldlbmNVbTJxZlpYWnExN2RpNitJNVF3VjcwQ2djNzhpQjB4RGVKMU8rM3loOVVBajNkd290TjZkdDFLamQ2WXgvMnhkVHhiR0cKRVI2ZVFMRGdrNzcwcjM5TUkyRzVhYUlRQXUySk1UNjd0YlladFRucG5uZXhrU0ovTkJQRmtYL2pMbWliZEgyQW4vZ0tGOWpDeFExTQovNXNHejdUZ21HS0tjN2VvMDR5QkUzaDV6eXA4dm1wbjVqOTM0RlJ6bUFiN25URzRVZm43SjRYbTRFWmp2SldlRFZCWU5IaDkrYlhBCndsc0JkY2hyUE5VQjFrdVR5TjFDRjRiT0hUZzhSb3ZCcHh6L0VSNEpwY2pnbzlrNGp1Z3grYUoyOTlQYXl4L0hQczE3WUY0ajhPVGIKZnUvNW9OTWJkWHF2Nm5VTHBuZm40eC8ydnIzQVgwUmlmenIrNGZHalRoZlE3TjN6SDJ0ZjdOMTcrZlRvMjM2N3hJK0huZGFvMCs4MQpCKzhXL1hDL2R2Zlg4MjRQZnFwRHR3YWQwOHRST2Z5MDl2bmV2UWVEUVhPaVJPdDFwOXNlbEQzOFhkYnVQZW1Od20vNHoramRSWW0vCjNRVk41TTZudFh2Zjl6b3RBQjhEM3Q2cjhhSnZtOTFMVy9hWFRudjBlbkhoWHZPY3lrSlhYT25QMTVtaTEyWG4xZXRSWlpLNCtJM1QKMUQvOXVXeU45dnVYdlRiMGI3Ly9hMVVDejJpK1FkSFJzREtWWTNWdW5OUUhUMDRlZEM5ZU4wOUVWUm83YlNpNWhDWXNjK09rVkorSAo3eW9QenJ1MXBtUEp2SXpvK0pYL0x1N3V1b2pKMGVYZzlMSmI5bHBsVlZiWXFoVzV3ZTNjOU1EQ0VhOHFQYWZOWWZsb1VQN25FcGhRCmZicE8xTHB4Q21WVjhucVg1ODlhbytiYnNycWNqS3ZjT0dHOS92R29NMm90Mlk0RGZVTXEvVjJudXdLQlkzVnVuRUpjY2xXcEc1VEQKeTI3MWZaeUxWNUZBYzdvdUZuUjk5bTRVYmNybGR4WEZTZWp6QXZhdjhiaDBla3NXWUx4Rlk5a2JuMldkWGxWYStoZmxvRG5xRHlvVApGQ3JjT0ZYSC9jdEJxM3c4YUY2ODdyU3FEOVlLWTNXTGkrZWdmMzdSSDNaR0s2eWQ2K2dIYWNsVnUzRHZzRHlyM2Q4ZEFkZVBvdDBSCmNBYVZhM0lFVkxzajRBeENka2ZBV3hHVFo0TW1hUHZkYi91ZDRaWWRBaXRiV2piekRGaFppdXpPZ0xzejRPNE11QT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJT3dQdXpvQzdNK0RIZGdaVVczY0dYSUdpVFRrRHdzRm92M3hiZG85Zk45djlYN2JBUTFhWDIzSkFXb1dTclQwaVZkYTBoNlAyWWZtMgowOFFPcmFDS3hwVnVmSVJQdTVkTFJQMEgwRVZ2UlVjZ1k4dTJhUWlQbTVmRFlhZloyMTg2Yk91b1lGZGVTZTNxWXJHOTFvZnZkbldwCjJQNzFGZ2pwbjUwTnk5SHkyYlI1UW1BVndiWXBxLzhaamRibXJmc3VhcFlZWGRqcWQvdURMMzU1dmZTSUUyK3E3N3JWclkydTlJM1AKdHNvQko4UEx3Vm16VlI2M21xdFFOVmJweG9rYlhwU3RaNWRMNXQxT1FveEppQnYzRFZhZWdEQ1lsOTNtNE9HdkYvMWUyYXMrVHRNVgpiNTdLVllrODZQZUdvK1lWaUF3Vk4rbWtBc2MxK0U5Vkp2MVdtU3UvM2NiUlU2eEN5NW9mby9VS3BIendjL1MxcUNMUCs1M2U2R2dWCkE5UDEyQ3JMWTdkaWo1eU9zWG5hMFpXT3F6dnZ3MjE0SDdaVUUxcVJyRTA1TGwzQm5iSXVNcUh5YnZGbWlVNFVqUVVXdmZrNGpNcUUKTExHcHhJU29kZFk5MzZUVkNVblhtcERxZTh5YjI5aGlWcFphNjc1bk5nZWQwZXZ6Y2xUZHNiMUplMmUzTTNyZTdDdzdpMjNlNXJtTApTS2k0aFc3T09YcDFxL2ltak9mVGN2Q3FSRTV1bmtxMHF2elk0aUc1dm43c0FvOCtqSTRsZG9GSDZ4OTQ5SEZkUGpubzk3djdnN0w4CnJiTHJiWDBEckZhWWpPdHVHYTVNeU5iR1Y3VTczV1oxZi9BbUhYa3EyM00zNjZ4enZaRlY2enBHSkEwckVuSWJRbEUwS3FlRkdEVGIKbmN2cVd4Y1h2ejBWdVQrNGVOM3Y5bDlWRnVicmMyelpYdWwyL1hlWTFsWVVyTGxNcTM2TGFTZlRkakx0S3ZyM3RnVHlubGIyaG15SwpFRnZoQnVPYUM3SFZMZ0t0SXNWdWFjbHZhdmp1OXVZSXFMeTdiR2FPZ01wUnlic2NBZE1FM202T2dOUHFpcyttYkV6VjVmbTY3MHlWCngyWlRkcVpOenRxd0pOd21FZ0tyM2JtNHBjc1dLMHl1dFE5b09hMDhOanNodHI1anMrNUdnZ2VWWFVFSHI1dTlYdGs5THJ0bGF4VWoKMjNURkd5ZnlSV1UzMFZXSm5LNTRhOXZSWVdkNDBXMjJ5dk95TjNyYXZOaThQZW04Q2FncXU1TTM0cXhVNC8vV3BqNktzWTlWcWFaUAoxZFZ4TG43emdyS3lzV3RqTnJIcW9uL2RON0hWY21sdWdDWitnRGVrbjFZUUgrc285cXJmejl6RVRDbVZyU2tiSXdlcXI1NTFsd09WCngrWWE1TUM2ckw2emxZSzd6anJkN2lyQmE5M2JVRHFXcUFlUmZSWWZwYXB1bDdXbGIvN0dTbVd4RnlqenpYOUwvYTlJNFVTdFd6RFEKOWlyUHhHYXJkWGwrdVR4S0lpWXZxbkx6VnZYS3M3SmRQVW1UTFh2anRBeEtPbWhWSHFwMnV6UHF2RjFob0h5RjliM25kamJvbjFlWApoRlI0ZlQwNW8zNzFrMlQvRmdocGRuOXB2cXU4Z0VBMUdqVUhLNmxTdHZ3dDNHN3JsYzNLMTRWYXpXN3JhYi82bFkrb3dpMmRUSnE5Cnp2a0tFdnFhN3Foc2R2cTdlcjR0WVRPdFhkak11aDZGcWcvTnBwaEVkbUV6OHpidzJ3cWIyZmIzRmF0blU5c0Z6c3l5MDkxbTRFeHIKNndKblZwRG82NzQzN1FKbjFtaC9xcXlOYmtiZ3pBcVRhKzBEWjFwYkZ6aXpSVUpzRnppekM1elpCYzdzQW1ldUdEZ2pQcGJBbWRiVwpCYzZzSVByWGZSUGJCYzZzazlqYjZzQ1oxdFlGenF5d2V0WmREdHhtNE13bXVJZzJKL3huaGFGY2RWTGUwbEJ1Y0xMQ0ZRTFNkb094CnVYazlObWNzcnE4ZnQ5dUhyVWtaK2VESnlTR2xDanBaemI2MFpVR3MyNXN1NldieVJLL3I1ZHVQSVQzUFRyNVZrbTltSjk5MjhtMUQKNUZ2bHVicVRienY1OW5BQWdJOWJmU3VSQlR2cHRpSFNiYWU5N2FUYml0THRvMWJlZHRKdGs2VGJUbmZiU2JmSzBpMTJHNTJzNWh6ZgpNaUZYbWZnUDRvdmVLQy9oYmhHdHNJaXlqM2tSVlNaK3Q0aDJpeWkrNTUzVWRPVkFRRXYyaXhWaUFhTWFONjZPZlZ5dmJUM3YvRnAyCm4zZWI3MDVXdTQrNVpYSndVSjczbDZXTDJPV2t3WTdjVms2YWlrSmtsNUJtTVhtM21aQkdWSitUdTR3MHQ1aVJabHR6bnd3dk1QdEoKVmJyZUsvZkpEVlBXNmJYTHMwNXY2ZnZYOGFoZGxNM1I0UXJMTEtweDg1S2pKdTdMcENZMC9KdlU0UC8zNFRQOHZROC8xSzc5UHNxdApIRnRXVG1TekxxclV6UjRwYnpwZWRvVzMxamZsbHNBV1BwYTV5akI5cEJjR05zTnpzKzRYNEpjdGhNMTBzN1g2NXhmOUlXZ1R6eTZYCnlLL05rM1lyaVlZTmliTS80UEhhUExsUTJZajRab2w5S2hvTExIcno5cGJLaEN4eFhNV0VxTnZRdFNzVHNpUXNKeVlrWFd0Q3F1OHkKYjI1ams3bVNncmJ1TzJkejBCbTlQaTlIMWJXQlRkMUJ0eTRINVZYVWc0OXhLOTBjOThlVnB1dW1ET251RnVMTy83enpQeThpZEMzOAp6NnZsWE56NW4zZis1NTMvK2NPVHVmTS9VOUdkLzNubmYvN2dkTjNnMnhzNy8vT0hWemZSQXkyVCt5dXBuanVmODg3bi9MN0U3WHpPClZ6TTNyUEV3N1h6T2ErVUFhSGZPemk2SDVVRy9CNnBCci9yeW1hcDM0NVB1WGRudDluK3BTbWUzOCtyMUNINnZ0ekFKYVdVeUo2dmQKL1BaYldieGZEczVBS1Q1ZTdTR0FzVXJyNjZheXdubzEyc2JxM0RocGJuMXMyK1oxclNFRTZ6MUVXL3dzOGM0RXQrNG11RlcycjUwZApibzN0Y0x1WGlYZDJ1SjBkYm1lSHUrSng1LzZyUVZuMjdvTWFWdDRIaWp1dit2ZmZkdnJkY25SL1VMYnY5d2ZOM2pJLys4NUFkOU01CmJpcGJUTXN1ZkZuSlBCZlZ1UG10TEs5TVdQTzN6dm5sYU1tTGxySGc1L0szWnYwNTdKQlo0d2pWcmx1T25UbTB4NVFqcHdGdTNnSUEKeWJUdFZocXgxV2FhNFVYWkFvVjVzTHNTc2RiMmpCVW1vUnZRaDc5ZXdGbHJCYXZ2ZE1WYmlHUllsY3FWYmR2VEZYZkduSjB4WjJmTQoyUmx6ZHNhY25URm5aOHpaR1hNK0FIMW91ckhHSEdmWkladk96cGl6M21mWm5USG5mWXc1dDZGbmJWdGMwaHBacG83ZE9XbHpUVk5iCmViMTRPeE56YkxFUjZncWtiY29GMUExT3oxRTloOEl1UGNlNkViTG02VG1xRTdMbTZUbFdEYUZiKzcxenUxTnpkRHVqNTgzT01rdjQKNW0yZ04vTTJ6bTRMdlQ0NXQ5dEMxMDJwV2ZNdGRHc3lYRjFKK2Q5dG8rdXdqVzVkZHF0VjlZT1BjUXZkbkVDQWxhZnBwZ3puTHF2Vgo5bVcxK3JoU1B4Mi9icmI3djN6Yzd3NVZUbnExUzF5d0h0ckJUU1F1dUtVZFpiTXYrMWQrRHJSZCtUVmFLbnJqTTZ3NklVdTJoNWlRClgyK0JrUDdaMmJBYzRXd2FsTzJWVnN5bUNJTnR6Q1gvakVidG96b2pYSDJpYnNxZ1h1R3dzQzZEczlWbTdkMGhidnNPY1VJbGQ2ck8KMDE4NjdSV0N5RnpwbXpjenA5VXBlbDJ1RXBQbGk5Kzhjdkl4SHJXdkljWHlEZE5TRjdyeVZLeXU1OStHbXI4S0pkVVYvVjgzeS94aApkdWFQelRyeDdNd2YvZlZVay9OdE1YOVVKMlJuL3RpWlAzYm1qNTM1WTJmKytJakdaV2YrV0hQengwZDJzQjQxVjRqdjJrWVA5dG1nCjJSbzF1OS8yTzlWRDRHM2xpbVBNTGQxMHRHdWpjaWFaMCthd2ZEUW8vM05aOWxyVnRldUpXamR2MWFzY3o5dTdQSDhHWS94MmhhdlYKY1pVYnA2elhQeDUxUnEwbGh0WFl1SUNsdit0MFZ5QndyTTZOVTlpcWJNbmJsTE5FNi9vQzlOWjJiSGJKdERjby85SUtRbjZYcm1neAplYmVacm1pWHJXaUZnYnE5YkVVcmFGOW5nLzU1ZGVGQmhXK0JuTW92VkkzNjFUWGovaTJRc3NzalJadjhMby9VL0R4U3QySk5Xam1iCjByVllrcjY3SEp4ZWRrRkYyRmhUNHk0UHpBWmN2bHZoakxFaHB1QXIzRTdibVdFWFduZTJMZ3B0QllwMlVXZ3pxTHgxWTNuWUhFK1cKWEt6ZmdFaTA2cE54dmVQUXF0T3h0VkZvbzRwSzI2WTVNSkl0OTJCVURoWGFPVENtQ2J4ZEJ3WXV1V3QzWWR5U0tyczdBKzdPZ0d1MgpoSGFud0pYNnNUc0Y3azZCTjBqVFIzc0tyS3lmN2s2QmEwUEgxcDRDdHplUXJYSkdpYzA4QmxZbWIzY00zQjBEZDhmQTNURndLUzI3ClkrRHVHTGhWeDBDOWRjZkFGU2phSFFOblVIbnJ4OEFmKy8zMnEwR3p1bEJlMnpOZ1hXekxLWEFWU3JiMkhGajVPTEhMU2JFZVYwZHUKNW9yc0dnL1VwcnphdDlrWk5uWUpSaGNSc3N1d2NldFNZTTBsV3I5Q1pvM05rMmtmTEYvSVRWdTd1NkIvMTF2OWJuL3d4V20zMlhwegp2MlpCL1l0bXF6TjY5OFVLWnVQaDZGMjN1aG5jbGI3NW1HbWtkZHNFeEVwRWJjcWFlb1FUY2ZPVzFQdmswZG5aVG0vRGRycjlDc1JxCjhtSE5OWWdoWlRFOTJFYVJ0OEh2RzFaTzNPRGVTVDNvOStqeDl1cG5pOGw2Tno3eGZubTl3dVhKcm5zSXZsNWg3VVZVVGxhN2NTSXIKQitNTUx3ZG56Vlo1M0dxdW92V05WYnA1ZjhGcWduMDEyc2JxM0RocEt6NC92Q2s3MXpZZGZWZDlJWHA3ODhLWXlrbW55aTU4V2NtMApIdFc0ZWZtU1ZoYWZ6ZDg2NTVjcnVGaDkrUnNuaXJhazI4eG1kQzJxMW1HSDFJaWpWWnpDMTVRdDROREtoU08zOVcrZTdyZFRqRFpkCk1ickN5OHM3RGVJVzlwZksreVlQNk1OZkwvcTljb1ZqMW5URjlkWFN1YThybnlXbksrNjBwNTMyZEgzYTAwNTV1amJsNmRndDVjM1YKbm5aUjFodmpLZGhpTmVrS3BHMXhIUG02eUliS3F0Q2JKU1dqc2NDaU4zLzlyREloUytJTllrTFVXaE95SkxsUlRFaTYxb1JVMzJ2ZQozTVpXczZwWmRlMzN6dWFnTTNwOVhxN3dvc1FtN2FIZHp1aDVzN1Bzc0xaNUcraFdQOUd6d1Z0b2RUbTMyMExYVGFsWjh5MjBPaUZyCnZvVmVTZm5mYmFPN2JmVDI2Zm9ZZDlETk1WWHZIcFZjWTgxb1c1ZmFocjRudVM3VFlsc3pJRzE5SXR6ZFUzNmJtd0twYXBxZ3pWUG8KcnVUZTJveU5acE56TzUwM0FWWGwzQ0tiSU45RkxYSC9uZlhKUTZxU1RKK3Fpdzh1ZnVOa1A0YXF3K1d4WnBzbk9GWVhpWnNpTitqdQowdE1LQzNBZEJjZFdKMGJaNWt1QVc3d1JiM1pLa1hybGM4bTY1eFNwYjAxU2tXZGJmeWQ0bTQxeW01cU80K3F6YnYzZE1kc1pFWWhtCmZSeXY3MjRpaGZHNm55dzJSVHhzY0RTRHFQNGk5aWJxMy9GeXFuemxjRlBXMDFWa3hhWXNxYzNXd0hGa1hwNlZnMGVkd2RhWlVkWkIKaXEvTE9JK2FwOVg1c0FrMlQxbXI3UE1oMm45WXphZzVWdWYyRW9GZDlsb3ZOaytvYk4xa2EyUzFwUFp4VExmSHUrbDIrOU5OZkN6QwpiZitXQTFYb1BJU1h2YjhiTkh2RHMrcXZYYXpQN0w5aTlQM09hSEpiUmhNN1lOdW9jRi9sTUxFcHg3d050cHhRTk42RGJ2ZmFwOXZtCkhJZXV0ZzQzWmE1dWFCam9kZlRoS2s5WTNYbndSQ1FuRDN0dC81UVZnalJDVHI3dDk1NERDc3JIVkxmZy9mSlZweGYvc1BmdEJlRlEKOXFmamQrZW4vZTdlM1FmdGR1MVI4MjEvZ0VKa0w2azlnUCsvL0dYdkV2NmIxSjd0SlExVFpIQ3FiUWlWcTh6RzdEU0VTYkpjd1FlVApKVHJQNFVPZUc1bnAyc3ZtWG9qeGVma092bndESDM0RzBDODFrZFNlMXY3NVUxSnJZd3N2OXVwSlF5a2o4NkltUlNQWFN0Zk85K3JDCk5FU1JLQ2pjRUZxb1dsMnFoaTdTdklhbHBjaWhqYnJVV0RHdDFZdUdFYmt3dFlPOUFCT3lZZklrcXgzdFlkVk1GMUJWSm8wa1VRVjkKa0Zyamg3U1I1QWJRaTZ5Ukdwa1NKRFdGcUxXb0R6cHpNSWRVTjNKcHBJVkFWd2tpbE5JQmd2V1NScEZMRlpVcUdsS1pCTnVURFNscQpkVUNUNU5pK3lCdENxQlI3N3JsUUY2SUJ6UnJvdW1vVXd1aFE3R2d2YjZTSTBPS0JocElpU1VORFFnRmRjY3V0UFFBVktiYnB5MmdZCnJWd0dxb0JNWlFvWmt5NEsrSVRNWnY3QTM5eGtXV0FoRG9kT1plRHp3WjRIOFhBY0VVZ1ZORUxhQ0MycFdxb05BUklGTXdZQkVzZW0KZ000ZzB3aFBrZVRZVmlOTnRDRThSYU1vbElsQktjNjFGRkRqNE9aWlVVdEZJODBLb0QwRHBoaVRJNEQ3azBvcHNZc2VCTDB1OGxSWgpQREI1TGNzSWhHeVJCQ2pzTU1EVUVCb0hENHFtS1l3YThBZG1weWdNZ1ZRS0k0VFRRSnZNQXNaR2dnR3RQZWhYWWxJZFFDbndRbVRZClVvYWpxbXZSSElBQk5pS2xpYUdnUHlyblVraGZrU1JwRWZDRVNSZ2F3OGxMREk1S1FRZUV5RUtuY1ZvbE1Lc2l3Zz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJY0sya1F1cEFmUjE1WnFRT0RLcW5NcXd4Z2tBblBZeVpEZXNPYWhwZ2dXakl4Q1FXZ1lHeFF0NklBbm5qeWVVK3d1QkRZYTJ6bXN5aApycW1waGxFNVRDQnBZTVVBd3cwc0NLVjFEWlo1a2lvSkZUd0VhRWhTYkJmNkM0c2lqU0N3SGtWUlNJbWdYQUE2WFA0UzBjS2NTZU9PCnNBUTYyRHZiKzM2UGhGZDd6OVR1ZmxwNytlUGVuWk5ab2dxZ1Z4UldVSE9HdUVMbzFRU1c3Y2xWUkJiV3ZJclFpamtTaWEwN0o5T0MKNjg3SnlxTHJ6c21WaEJkVXU0cjR1bk15UTRCWjRNb2lqS3BOQ2pFQVRvdXhPeWRYRW1SUWJWcVVFYTdWaFJudzZ5cmk3TTdKRlFVYQpUSm9yaVRRN3ZhOGcxT3lLdW9wWWc1b3pCQnRDcnlEYVlQeFhGVzUzVHFiRkd3elhsSUM3YzNJVkVYZm5wQWZhNjkwSHZYNnZWa2hECk1zNHBlMUM5S05JQ1dDMThzRGJvZElRTW1BYnp4ZVFGU2orR3dKQVdLcVhWRDNJQVZwU1lCZUo2UjZST3dtUkpaNEZ3a1Jja05MamUKTklSNGxwRnU0SG93RFFtMWFCQnhlS1lnb2ZsSjJvNW1id0hUTEFpd2lHSVlwV2syQkdCb0JJZHVpaFVSTUhRNTFKNEZDK1NIL3N5Qwp4WFduMlJKZ2NXZW1XUk5OSGVMTDl3Q0JRK05vN0d4TWdnWFdURkZUTU9FTlNFT2xZZlZSZTFKSmU0aTQ5NkpzZG0zSU5WUUFsRTlFCitoeE9vK1dnVnh4M096YnpLcHhhSGc4NjdiK1ZGRHQ1NTBTN00xZFUrQVdjWllhakFjVkhmT2VPWDhERzJyMzlmcjg3VnZKaEQrM0wKank4N2JYdllCZnFtMFdHV3pjRW9JSUpqMThsZXdiUEFuOERzMlduR2FXci9jalRxOTA3NmIrRjRGeCttL25RSmZEdDBpMnpPQVVtTgpuNCtTMnFzOWtCaEpJbkNQeFRXWDB3YzgyZmhQSUdhay9iMXVkd3FaNUxnbjRaZk1LbWcxcTVUSkd2MkkyR3dsM1BrTGxOQXdNQUpWCk1QcDd4RzNTWnl3SnV3Wjg1dnE2UmhoQnA2WVdqdmE0MlFKT2g2NDRkaWZxT1NwVkNSQ2ZDcWg0am45Z1dtall6VUVvRzQyYmVjTEYKNFpQdFNPTElkQit3WUlZOWdCNG5FcVU0NFRDNDZSQmFwRlRZRHRFSC9GWExyT2FyZ3FKVHMzeHpUZFFKdGU5bVlvOGIxQi84eGtpdwpDWXY1d0JKd3RMZC9pbXZnK3g2ZTBOdTFWNE5tdTFQQ05Fby9wVUd0RnpqQUlycnRJbXI3ci9DY2wrQi9VS21CM2hnOE5CTUFtc3NGClNQTmNBRWRlbm84VjFETUxTdEJiOWxzVk1lNVh3UWpuZWNTSXc3Uy92L2VuNzJIR3czYWl3elNOMkhRZU04OTlxTS84RkhOK2JFeVkKbjNYSDBMSGhuQnByT3dzT3duU1kvc3R6cDdzWFRha3d6OXdNNFFrU0QyMDg1QkdSclhtRExDc1BNaFFDVFcrTTB6a09pWmdjNUhSbQpRZFRlSmdaNVBzYjlLaGlGRWRFZ3o1SGNxZ1pUUVlEdWVRTkNXdHlXa0Q0WWxPM09xSGJRSExUbkN1bHJNM3RKUStzVlJzeDlza3RGCkZuVHdrMVowQ3diUUYrTUVPUjYxVWxMNTdUZjRBcnFhKzBybkZ2cmdrYmtQaUJUYWtxaFoycjhrNnZpM3FJcEZZMlY3d0Q3V3N1dVEKKzhCWW92NDdxbGpvZ3hZQXhjL3Bid1pyRGRRTjZmNktBZ3ZEdndmRUMvaUFyQWcvMXFNNmRZc0hTeFp4MzZhK1VkL09YREhiNlhQMwp6VmhXMCtIQkllVVAxSnFodjhTak9uVW0vam11NTdBZDdFV05ISTAxaVYwQU1UcFhQUUxOcUFhYXZCRGJyQXNkTmtmbG8wN1piVStyClEzYUphV0VVTFRLVkNnWDZaZlFoeWJRQzdSYktKSEJDbmZXQjFoeXN3R3FyTGlQVm8yN3NmZ01EakpNaDR6OFpEUmdneTNScWNJRnIKYVpUMmQxVkJNMDV4bjB3YVdacEpQYjNlNFF5SDJMTWFUYkhNYmpPRy85Z1pDZHJkSHU2K3RodlVNUDVGclZyN1AyYStlcUVYN2p6dwpBKzVjdWhhMkFSRDRDcVIrbHVHR2srQjVSTTh1a2NQQkVuZUdoVGoyRitPQTAwYllXNGllYzA4ZS9sV1dQaFdvdlpJV3RhQ0xaaW1aCnlWSXl6Vkl5ZFVUbUpWR0dtbmtkTFJKSUthcWpqbUwrSytiVGFvcEFySnBKYkk1VEF2K0tMTVhtbFNNU1ZhcjRoMXc1eXFZcjdNK3MKWU9MeGdsK2twVUphSWhMYmQvZEhMcWFnanROblpTSWlmVVdrRThSa2k0aFpVSEdTS0cySjBwWW9OeUR1ejRJcFNFU2w3MGVTR1M5UQpKRlZKTW90SVN0MTBFMjY2Q1RmTjB2RDNRMDIzSko1dTJUZzhGOVBVSlBGc215Zy9TWVMwTkVoTFFtSjc3djU4d01tV3pGc3h5WndWCms4eGJNYk9Jc081S083bEV6YkhmL2ZsZ2t5dFp0bDZTT2VzbFdiWmVwa2hLN055U2RncEpPNk1TLytjRHpTdVR4WU9pSnVENUZDRmMKZm45VytZbitTK28rYWJjMG14TDM3NGViVXVPOXo4YmhNMWJGZU8remhiMG5xeXJOSEZtelBMZi9mcWk1Tk43M2FIYmJ6azh2aC9ITwpUMVlZNnozSm4vTTlKMzYwLzBNV1Yvdm5ROHdkTllmN2FnNzMxUnp1cStuKzQ5elJORk0welpyVS9mc0I1NDZhTS9QVm5KbXY1c3o4CkdiM1gxSHVyUTFxZTIzOC8yTnk1TnM1Lzd3OFUvdkNkTkFvcGhmQXVDUFNzQ0kxbmNGQzdCVVdPS0s5MnhTdkZxMkRDcXZPQWpYYVoKYzk1WGVKY1IvRWZ5UVUzVUhseFEwNnJtbFhvOFFyaElsWXoxKzFpeGQrcitIUDVtaS9YMUNha3NzbkZkTTdWellsS3BJTFYwUnRGQwpXZFcyR3RaSmpXTWVWcU9YMjR0U3FDd1MvaWR0V0p2aHRoNW9CLzJMZHYrWEdlWjlzaDBsT0lsV3NQSUx0dktUcVZPamQwaVRsL3A4CkNuTGtJWFhUVVBRaEF1WGt0VlZrN3RRWnVhaVZxKzVLTU9CZ0Q3M0xOcmFnOElpOE5kVERNQ3dBaXVtQU8wQk13TVd3ME0xRklJK0sKbXl0Y0dkOG5CbEI0QnpWU2VEUk1IRU9ZZXNZNnlUSTBRTTJjc25kUmFHZEpWcGhQUTV6ZS92NkRWdXZ5L0VWLzVHL2FRdVc0WnUzZQp0LzNSaTdMVkg3UmhuZHQ1U0hvNC9ERXBuS0ZOYW5BUnBVVmlwaGJBd1lNbmo1MWsrTzZzUHpqblhFTTBDMkdPdHZ1bjVjbURKOFVKCmRQZDQ5SzVibm9TbW83bUtMYjVzbzhpUlZ6ZzZ3eXBtZnliSlJ1WjY3cWNkam5haUMrTkJNQ0ZzbWF4aDZBY1BDR3oyb0docUJoaFcKcENHM2lPR0RtMkYrSHVaK1Bwa3dvUmtSMXBOMkhpbzMzQVhYUjhUNTVMSTQyTXVuMWc1RG1BaEd5bFFhUDVVOFRaTzhxV2F2V0RBagpyTlFYTkV1RHFQVWZKSm5walNBUFJDNjBYWWkwQVdDRUM0Yk16S3VVQWxraXlhTmF1Qk9zMnRKK2haWnlNZDZJMnhwMnEyek9LdnZUCm5FMXp4NXNmcHp5TWs2djJmRzlxYVllRjdCZi9oSENZRWlEeGZoZUV5a3pZdElTYWxHRlRjbTZKN0l1MndBVWdMMW1uWk8ra0RKb3AKbGlabFZ4Q1pESm1XZnBNYlpSVmY1M0xSbHMwVE9GYWpsRTdnbUNRU2JXS3V3S0ZLS21IUnhyVkl0SzNZMG42Rmx2Smt2SkdQVXJRdApkbTZQTGQ5d2JrTlBxY3JSaTJOU2dUNVJBNmMxc3AzclBEVUNmd2ovbjNhVTBuR09GNUZ3aStoOEwrV1ZMUHk2U3ZCNFFlVzZNNm9jCnpWZjMzRkFueTBhTE9EVFBMOTdJc3d6RC9OeWZCUDFCN3M4V2U4b2ZkYnJuRXdjZGY5aFpjTWFaOUw3aElVZWtHUHdLekVNbGcxekMKY0VadHlCeGozUnpvS0lEd0EwWERIdm1LTTBDaDRoa2R2RENFTXlseVVsT2xrZVR5WXhDS1BTV290bWprR0VJOURlRnFjN1U5NVZhSQptcmxDWk43QStBQ01PaFUyK01nMHNqU25HQ1ZvQUlPSDRRQU8vUkVKbXRBSzNBSmtnWkpRWVNjeFVEblZqVlFxT2JPcVRGRFlwN0tXClM0bEIxWVpzZGhnSW5hTXpYamVVelBTY1ZnM00zRUpyYkRWdEpLbWt3S0Nza1JsUjBPYWxSWnJrczZ2cUhCRkxNalRqRm9JMUsvVlgKYUJBR1Jsc1BZMUlBZjZHN2ozeU1VUkFoYzliY3ZhZk40UnY2NUUvOFQzcHc4aDZWN1FsRHdFRzNjM0hSd2JkbXg4Q0huU0d1RVZmNgo1VmZ3enlYOG42NUh3Y3d1ditBdk5PZGZOSWQwcCtvRU9nVlM4akhKNy9KdGgyNHh2NnU5L0JZQS8vVERYU2U3Ty8zekU4Wk5wR2h6CmZ2bDZmaGtFMlhMMmYzazBmMVNON0U3WW5UdlVsOFBtcVBrRkxDT1o3YjE4OG4vKzY3My84Ly8rZCs1Ly9oLytQdi9uLy8zZm0vbDkKVHNkM3YrOSsvNjkxbUorTGYxKzJ2dDd2UHlnV1lKdEdvWUJ5N0s4c3RnRG1oZGEzYzBLTmd1WlExQjVkL3ZiYnV4cEsxVWdidUhPQwo0dGZyQkNjdlA3TUt6R0Ewb1hzY2RYcHZXSmJlSjRRbkU2aXNNSk1vUFpmcEEyUFI5ZDlQYWhJVHNmY290UDkwU1gxNWNFRUMvaG45Cit3cEJVMm9GM2hlWlVpd2lZS1JIaE9vemdYSDFNMEl4cFY1RXdLQk8yT2o4Y1JVandJS1NjZWNFMUF5a2FsVkZBM1M5SzZzYW9NdGUKWGRuQWl5bFhWamZ3NXRCVkZZNkt2WjZwY3R3NWVVVFRoYzV6WkpPWnAzRHc5WGZRQVI0OHFUMjRIUFZyZG9GMWZpdkQ2V0hxelBYcwpkRmdPM3BidEU5RHNUMnloWWJRc3dsMmJ3dDIxQ2U2Q3hNZWJKbW1HQnhyNElIV1NtK2dEZXNRVWhrSlAvbDNwdHJYQUF4cmVUSU1QClVoWTJmQ0dDSlJnRENYL3p4RTd3WE9VVUZ5M3NMd3c0c0JwMWtxb0FPeUtZRkVyRkZXVURCaWVQY0VjQTZvQkY1V0N5SVhEZ2pzWmgKYVVhdUIwWU9IN1RLTEM3YkJZWWdMdUhiSVpEMVBGaHlRa1ZQc1VjZVFhZ0xCek40WlE4WXpyMVR6ekJPMGFoUTVYd2NaaEhqM0JVNgpianhyNUhRWkxlbzBMT1BDNVBsWXIzR3RtandtdDJpSUxNdlNDSGtNNFY0SG1PLzFHTXlObzBQdUI4cDNJUnBpN21rMHdwNGNYekdRCjdIREhBQjdoS1Y1UmRLNkMzNVVSWXh5TVlaWklCZUpYNlRSaWhFSjVvTEl4RnVxR3pvdVlYVWQ3ZEdjMXlTSzJBcVRJa25qY0l3QXoKMElNQy95SVFrZWdSZXk3NDVpUDIrVzVHL1BQRStKcUJZSWM4QmpEL3BqaEYvTU9wanZjT1kvN0ZNRGUxR3lMbmxlU21QM1FpVitQcgpCcGNiYkl4ai9KTjRqem1McGlwQU1ySlBCZXd4aERrWVlJR0ZNWXpJOU1nOUozd1hZaW5EWFkybERCTVVwSXdubXFWTUJQQlNacEpiCnlFTVpNY2V4TUFZUmpiaC81anBpZy9CM29UMy9RTVRBYmpRdWVOQnNtWTd4RkdPSVpacEhTM2dNNHZnWHdUei94bUJJWWtET1hBaGQKQ1B6elBRM3M4OVF3S05Cck1jZmZIZSttdUlTc2d3MjRNT09zaTBGRW5nWU1PdWFVaGlhVUdaTitDamE5UkdZeDUxSWdKVWtqeHFVTgpvMENGQ29oamdHTmJBSG11eFNBa3plTmwybjNiZ1dXK2g0RmxuZ29HQlRvdDN2aTdZOWtVZDVCbE9VZ0VxY2UyakJoRWxPV1JxQ1RhCmMwQXBUQjZ6TEd2SU5FdGpqbVdnSjJUeFZtZ2FPV2hyT3VDTkFZNWpBZVE1Rm9PUU1vK1hTZWVtQThOOC93TERQQTBNQ2xSYXRQRjMKeDdBcDN0QnVLK3psNkloaE1ZZ0lFeUpzM1haNUN0QWM4N0h0UWVENlVXTWN3NWJUV0FoaTEraEN1MGNjQVJ6SEFzaHpMQVloYVI0dgowKzdianBZbDl6Q3d6RlBocTNrNkhkN291MlBaRkhmc3J1RFVGc3BIa1dleGhsZUF5cGc3UFlCVW9xSUJla0VlRkR6K0h1bDNESXJVCnUxRExLV2tCcndOdzI1RnlSd2NrazhXNm5aQjg3SEtJNGEvSzNJWkRqVE1nS0hZTWlmUzZVSXNwOTNnRGdObyttT1lPc1l6Vms0aGwKRFBLa3NlYmppWGZhVWNReTFxRWlsckdpNVd1eEt1YnhNaUJpbVFjRmxrVWdTeG9qOXNSejR4SExYQThqbGpFUnZwYW5uUEZHQUdiWgpKSGZHZExlSVpRenlwTEd5NDRsbmpTamlHU3RPRWM5WXUvTFZuUHJsOGJydkVjY1lFaGdXSUpZdVJ1b3A1NFlqZm5IM0lvWXhDYjZhCnA1c1JSd0JtMkNSdnhwUzFlRms2VUZnK2JwOE5DOHlwUHhIRFdFdUtHTWFxbEsvR3lwWkh6SUNJWlI0VWVCYUJMRzJNMkZQUGpjZnIKMG5VeFhwaU9qTEF3bVhhL01BUEFMOHdKL3NUYVdlQ1o5Qnl5bExFMnc2U3p3aE1ZNXRXaXdEQ3ZPekhJYTFlTTFnTUN3d0xJTXl3RwpFVjBlTVZQdUd3OE00eDRHZmpFTkRKR0JmWVExK3U2WU5jbVhXQjBMdkdJSUU4VnFERlBObWs3Z0ZhdERnVldzTVRHRU5Tckd5ZDhECm56ekVzeW1DRUQyTWsrbmxWZ09QdUdlQlI5eDNobmhxSGM3b3UrUFJKRDlpL1N2d2lDRk1UKzdGdnFXWGxadkFJdz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJcHdBRkZyR0t4QkJXb1JnbGZ3OHM4aERQb2doQzVEQk9KdGMxR2pqRS9Rb2N5b1BnSm9pbjFhR012anNPVFhJalZyZ0NoeGpDNUxDaQo0bGVjMDJVQ2gxamhDU3hpbmNqWGNUcVR4K20rQnhaNWlHZFJCTEZtV1llVDZlVldvNVhtZWhaNHhIMzNkWmhheGhtK094NU44bVB4CmxkN3I4bFVtMXVMOXdWeVdwa2IvL0lSM21OTmt0c3ZTbDBHUUxXZi9yZUN5aERGOVg0K2w4NnJ3Ly80M2ZIL2ZILzlyL0gvOGFmZWYKM1gvOGYyYk1vZi82TUxOdi9vOVgvYy9IN2hja24rQXN4OFlFdExKcnczbnpKcHdiRm5vRjk0YXRPT25nbUlDdTRPTEFtbE5PRGt2cgpWZHdjczNoblUzemh2c00rcTl3eDIvbGUwUUVDRldlNFFDYWcxWjBnVUhHR0d3U2dWM1NFVU0wcFY4Z0V0TG96aE9pYWRJY1E4Q29PCmtWbThtOC8wV1k2U0NlZ0tyaEtvT2NOWlF0QXJ1RXVvM3FURFpCeFkzV1ZDUkUwNVRRaDZGYmZKTE00dDRQSU1kOG9FZEFXSENxYWQKbkhhcEVQUktUaFdxT2VWV21ZQldkNndRWVZPdUZZSmV4Ymt5aTN2ek9UM0Q2VElPck94MndWU0owNDRYU3FCNEpkY0wxcHgydmt4QQpxN3RmTUZ2bmxBTUdTVjNkQlRPRGEvTVpQTU0xTXc2czdKekJtT2NwOXd4ZStMeUNnd2FyVGJsb3hvRlZuVFJJelpTYkJvR3JPMnBtCmNHcytZMmM0Y01hQmxWMDRHQVl6NmNTaHBMcXJ1M0V3SEdmS2tUTU9yT2pLUVZxbW5Ea0lYTjJkTTROWEM3U0thVGZQT0xDeW93ZXEKVGJ0NkNMaTZzNGVxVGJwN3hvRlZIVDVFemFUTGg0QXJPMzFtY0d2Um5qYmxESXFBSzdpRElwMDRtSFVpbFhnVmwxQ2srZ2J6emppdwpzbHNvS01QQnloUHB3bUlGMTlBTWJpMWc3TFRMS0FLdTREU0sxTjR4eGw3RmNSUXB0Mk9NdllyektPaTdZNHk5aWdOcEJyY3E2THBqCmpMMktheW5TYzhjNHU3cDdLYWl6WTJ5OWdvc3BVbkRIMkhvVk45TU1YbFZRYnNjRndSVWNVSkZpTzhiV3F6aWhJdlYxakxOWGNVUkYKR3UyNEtMaUNNMm9HdjVacnN6Rm5WM2RUUlpwc3pOWXJ1YW9pZlRWbTY1WGNWVUdGamJtNnVzdHFtay9MMWRlWW82czdzNExxR2pOMApkWWRXMEZCalpxN3UxQW82YTh6SjFSMWIwL3hacnEvR25GemQ1ZVYxMVppUnE3dTlna29hTTNKbDExZFFVbU0rcnU3K211Yk9jZ1UxCjV1UHFqckdnbk1hTVhOMDVGblRRbUpHck84aUNWaHB6Y25VbjJUUi81bk55WGphRWE0KzZ6eGRkTWx6MVJ1eGYrK2ZsWk5Mb2lVaisKNjhvY25hcEdsdEtEUEtrb0tLK3hCTW1hNGhzeTlHQ09wcEFDQnVIN01pckZyTWdpblEvQ1YwRVNROG14NkpxMHZXRUJ3RFNsZ0E3YgpKdG4vVWlta0RRNHVHaExJcUZFcG0zOWFnc0lqcmRhUW9HSVN3d2diWG9FaFFKRlBBNmhXUXBlNENYZUVlWkptaWcxSmJEVjg1b0xZCndLa1VIT0JvTCtSVzhEM01YVHI1R1NCZnJVNlk4M1FHQkJXdVJORTFkTThtQVdlM3JLQkUwYTVIb2RSRUg1ZmtnOFkzblVBYWFWcFgKb3FheVJxWXhzWUxHNTVTS2JVNnA5YnpaSzdzbkI5MytzTFJQWnp5YlRxMjE2dXNaUHErV29hRkNrd1pNRWpUVTEyeEszc0lGeE5rbgpzd3I2SDM2bGQ0SHdkaEtGNHRsa3pwU2d3LzdDTmFnRUphbXEyUXdjdGhVYmpHdkxnblpTQzU4UlgyR1RPMGN0UlMxR0hlVzg2VG1sCm5NdWhsS0JEQS81Vm1NMEQ0RG4yRC84UUdReXZoOEwxbk5xMEdmbU82Qy8rSk8yYlM3YUMvYkdlRXgzMW5EcnZmNGtxNUpZZithSmsKZDVXZnZoQTJSL3hZcWpsS1lacE12b29nWmhhVWlaak1jemNmNDM0VmpBS0UzOVRURjFtWVlzUWJtOHQ2ak45MVluaGdxMk5UM2ZJcAovUEZERWczVTJBakcvNFl5b2FJZFNvdlBqV2ZVYnRRZE41anYrM0FGWmlDV3lUaWZNSnRLVmt5bUl5eG1GeXp5eVNHYWozRy9Fa2FUCnpzd1lhWk0ydm5mNkVkbkk2VjRveUc1VUpGMDhxNUpvQytYZktFUXlsNFdvMmQvb2VRRCtCTCtrTUZZVXp6ZU9hbUZTa2l6WHNCRlYKU2twU1o0VGN3TGx2YWtaL1p2UTZGSnBDdGFDUEV1OTlpa0s5WCtJVWpmZG8zVC9xZlJKRWl0dmR6YTZmd0EzWXJ2c1gzMThzZU9OcQoxUlNZUGp0TVN0bW8wRVpEMlg5c1RsSlNwTkx3VWZxUDlhaEVQYzBibVgxcHFxNHdDMm5kUDNSbHY5ckliUHhJc2M3MEtUeVVSWlhwCi9TeEVTYzlWOGFjajN5MzdCWXJpQTFqd1pReUhRMzJ3eDgwZDdVWDlRSVhYVmNRdXhvVHlYbzhNcUtjRllUbDNuOXhUVnZReFBKeUUKeVM3OWswcVdCZlNjbFhRZkhaUHNBMHlXak1TeGlDcFNQY1Nacy9MaG1zV3UrQjc0RXZoMkV1WFh0QThxTVRNU3o2T0VSeWg4c3QzdwpuVHVLaGpheFRCVCtqUzFQSFhhRnlUN2dqbnlZbDdaeS80d0Q3dEpDNWcwOTc2MnRtVVZudjdZMUYrdjBlMXV6aTg1NmNTdFNPMkttCm5jZmNqSGdjR0I4R0l4cWp3R3RtTlE4cEQzbVlhQk1qRVk5UjFKSDNWeTd3RGF1SkhWNFdsblZqNzJKaCtubDZoR05tWWRqaHAvSWQKTDhTOFh4WHorQnRaWVltZDcwMnN2WWwxNmFkdTNjL2R5WFVXcjBBM1hBZlJhbzJXY0QwYTdqa01GKy94REpreU14Z3U3TXVvY3dwTApkSjR1ZjQ0c1lONnZpbm1jNGUrbHpOSENxbU82YnpmQU9LK0ZuYjBTbjRiTEdYcm04MzA3bnVGLzNYdENNa0tvL09qUFhIWDBlL2hrCmZ6NExJMG12cFBwcVlteWtKOGY4Ykg2QzdTSlRwZ2FuRkdDSTJ1WmNjODlCd3h3dFZpaXV6N1NHWXFHUmFIeWMrWHpQcGpjeEF0OHQKQnJtUUpvSk9YRm1DRDdQaTYyRDhtUjZ0TnlZTGtDTVBRYU1wZWxnaUNLcmVNSWt4elM2KzMwdHZkS09keDAwVFkrM2RCenh0UUd2SQpETnFPOGtaU2FHdVhLMHhtRFc1VVpBckFPSTVzTXQreFZ1cVQvYWhQZHZWZ3J6NU5UejBtMkZyL3hobFNqOWxWanpuWnN1L1V3VWxECjB4RVluNWZUalRSVHRYL1lsK3B5KzNxeXg0bFpPaWtLenZESEEwZVorOHFFQnNKbmZmV0ZBeDZ5TE1adFJNMkhidG1YNzdpL01TbHYKbmFvR0RNNDB4Zk9BY3BlYWxDNFNKWXBpcVJ6a3lFTmtabTNqUjlQVjdMMHRYNHg2Zis0aE5JejJjbkRtaHpYNkhnM0hIQURqUEpwcQpoY1FmbGduTjFzZStNdzZCV1U2RmIzVGlhNHdPSCs4enhFQW44dHczTnh3WlZuU200MW5mZmVsNlFFUmZHbHFsQlVQcWFCZEdneWk5CktwNkZJYU12RFMyVkVpamxKNzlvN2txb1BvRTV0SHBRNGRrK0JZUnJZWkhsNlZZYloxOGNINC9KWTdkclhxY2d4cWZYWUwyUllhdEkKYzVjWXhvSmd1U3JjQzBIWVpLa21QMWxPUGdtMGhoV0NYTGtXMEtJbkRaUXdvVWpodkxRZUNRTzRvZGFlQndGKysvYTdRNElkeUlVSQo3VENndGVmNzRzdHdiejJTU1lwYTh3d3U5MTQrUGZyK3lXSHRpOXJkVm1mUTZwWW42Y21uTmJwU0FTTUY1ZUhIc1ZzVk1Cb2duM0pCCkxsMlRxWlRlT2trYUtlaGdxZkV3OHUwb1NiSXV3NmZvRTNLNXFBUjFWMHpVSlZFR3B6Q1k2RnBPYVZBVjBvYS9KWGgrZ0xKSjZ2aU0KVDVvRE9sRW9HMUZvTlA2VTRQclJRdHBMN0JhR3dqTXp4anFEb0M4aUo0dGFnWkVHTmZ5YlpXZzhNN1E3VFJKeU1FVWFTcG9DSDduUApaVVF1TEZ3WVVCMlZBbGxwaWdRblltNW90T3FZYWR3R1JzTnZtVWtwRHNvb2RKalZjZUVMbmJ0K1d4ak9BMGtaMmdRSVQ1UFM5TWs0CjZNRHVuZkJMU205aWozZm9ZS3FMQzNRN2dWbU8wNXFDM1N0REk1MUFucEFocVNoMHZzMlM1YmhzRGxxdloyUVd2bWJwQWxzWExuRmsKc1JJdWJWSU9lMnRCenZGQ3A0YjBMbkxpWVF3aUJUckQ1cXFFOVg0TG9hMXdFVW9sR09xY0ZETExNQ0YxQnZNWW94QndvdU1Mc0RDeApDcE1VQ0xDMkg5eHpNUGNkckMvWVJrbEd3VWNCNnhMVDFtTzRHS0tCNnRBTGxFcEt1dldTcFFtNkFWeVRXS2xBeHoyY1ptQm1rcElGClBjMEtQUFJubU9HUXVvUEpEclY5UHpkTGhNMXZXR1JBSHI1aGF5alNHeVBQSkFHQUl4YlBCSGU2TkhXbkxtVVYwZldzR1prYTdiWFYKWnphRWFiV0J4T2lycVJGQzRPcGpSTkdWazZOa3d4aFhIU2VvTlQxU0ZLdTIrbGpaWU5lSjBYTEpLVmNkcnhuY1FseTR1M3hQWTBDRApjK2NrRE05N0RBdVFBRUlON1FDWjhqR3ZlTUFRTlR3OUZOcGZja20xZFV6WmtFaE92V25TcEVDTkc1UlI1WURqS09mQnp1aE15aFI4Ckg4VjFaQzZiNHArdVcyem9oaUJIUDdJYlJqcWxiVWZyUkVZZzBBcFRlaVZDWU41TWVybElGclIxNEFOYm9JL2lsWEk4aVNsN0FOTVkKZlFSSGtqU2g0MVZTb09NZmtHRXdCbXhZV2ttVjJ5clN3SGFwOE9WcEc3Mk9FeW1qd0FDODFRQVRML3ArUUdrYmNNOWxpR3lBcGx2ZwplQmlhaXJCdlpUaFkwRzBwaFV1aGd0RStDdHJBeFVXdlp4VGFuaGxncWtsQmk4THUwaGd1ckFHUHpYVUNyQ1NER0V4VmpWT1VJdGNUCmlzQjNYSm5pM01IZUl6ck9oeDlTRzJ0OERuc3Q5TThFQ0xRTzJBci8rRGllZHh2S0dEcDZHbVZjdmhVWVdMcGtadDhBQjRnUXVhSGEKN2hZVExIK0Z2OEFjVURtdUlFbzVVdVM0VXVHb29QSFpYL2lScWtsWTlpUllwRlBXOEoyQnRDQ25BTXdscmRHM2x5clkrdUd2SUFFQwo2cDBRTXZWRHdkOVJFMGh5bUJRZWdyUGFHQnlTSkpYb2RFZ3hISWVDWnV4N1dERGNDblVRTkpuaDZjbytOYUFLVkZkTlErblVwclFGClFXUjFVNHBNZ1Vya3A3T3gvTnFHaTlrNjJHQkNsemFBOVlJZWhiT0JYSmdCUW9CaTdKTGgyZ0FnSENHaExNaFlZWWVDUm9RUm1ScTAKZytseFhCenBnbjVQR0NpQm1WQXIrQlFWdmNBM3NmM01WcVZBM1VhdEMwNW5hUUVEcXpCRVc5dUR2ZFJiL2N6NjhlcytKWlNvSFRRSApNK3huMTI0NkV3MGJZb2ltWG1kTFNJUjlqaTJSYkUxSVJIVHNsNWtOS2hObzFrM0l0WVlkS3hCaXp4VVlJb1lQU0tLYlBLRkllb1RrClpBUWpPenJPZXcvQ2JjWG1yQUZJUnRFRzBCYU12aXpjZDl4UFFCZUhobE9xZ1FDWDdNeDJqaUZIRWNUWVorc2NTRkNXNVFJUEJOYlkKVTJocGJkdnVxbVhhOE1adXJJYStBR0tOaDZCbEg1WldqZ1kxaG1FMlk0bnhHNEM1U0FraEEraVZHMUkzUFFoNmhBSWtvRUhEczBWagptK0x2MXBKbnlWQytkUWRDUzQ3N2FxOGVLYjZPQ3BLSk5obURCNUdVcUVTYWFFc3JMTWN6elYrQjNhNGJEdUM2Q1VzT3pqM0dmMDJwCjBkWWVmNGZXOFFhS3IyeG56dGcwT3JBWm9BcVprdHhPckNxQlVUaHdFc1ZBMWdiTVYyM0R1U3lFaGlpbnQzKzUzalRFWTBJaFpkeFoKbHN1ZG8vRE44a0pFTlFNRTFNczhzV2tTWFRWNm1naDBuUmptSzZKTnJHSHZiSUo4VmJpM0EvN2NZWU50TmNzb0t0Q0RRbWU1M2pTRQpNWEd1UDlxR1F2YzlDSmNWWUkzbXNxUWd4bmd4QnZSVG1Kd0VyK2R1ZFpJR3lrK3AyQVl5WnpmVytQaE9EVlpiWm1pL2NkOFZMU1U4ClVEaEF6aTA0QkRUN1NkR3dUYmp2WkhMSU1EYzZGeENvL0dLSXBzZkFnTkFHUTdnWERnVjNjb0lNdEw1QTVVeEhoRUhWSWsxOEZWU3EKWVhRWUlYL2xGdm03NnhKWGRsMTJ1RDFGNkhWQ3M2a0RwSUthNE1yOGxYSHpkOWMwVjNZZEcrOTNhNEZoQVVwSVVKZzBLRUVaR3FkZwpiOWNKMlN3em5jaHQzZzIvYTU3dU53ZjFVZk4wT2tCbGxhQVVjdDJqaS9MY2ZpU1hPR21sS1lXQm9rbEkyMWdBL0VCT0c1TFg5SlY4CmU3Ujc4RmVzcFhQN0ZjYlJGbVdrSG1Bc0t2Y1ZWcVlUM1ZMUGcxQ1Y2R3R1TjB4RUtXM01DWWFpK043UXQ2aXo5QjMzQWhsOWhxckMKZlNYdkQrT2tMN2FMZFN1NS9jNmlKNzY1YnMxeFgrdnFFUnpDZFZ3N0FvTWJQd2RsMVQwc2k5bis5V1JBQnowUmxpK3BLUk5ONjNMQwp0NzFDcS90WGFIWEdTM01UOFI5ak0rSjhiM3JHak0ybmVLNU56VVEvUjYxVE1KNjhZL05haDdFZG0yalRrMUdOVGIzM0QxRmdacWN6CldLWThzNVBwSVNaK1prdHF3aUZtMWhDdjBPcitGVnFkTWNRdVhoVWxzQ0NsdXpCMHJRKzBiMG1SeVVXQ2xpazNCdz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJYkRoQ1VrelBDUjZKOHhuckxyeVh6S1duSWFIR21XMGpuVzZEYTUxUFlNVnYrUlRHYVVpb3NTQ3lBWFJ6MnB2b0w5bys4RFU5dWZXUAo2bjFYL2pwNk1DaWJKMmY5MXVWd1lvY2lEOXNMUEsvbHhnZzZwT1Y0b2NhZTJDZzBCclFiUlRjMmM1bnF0UGJ5d2V4TkRlWlRJVktRCkVWTjdXNFlPWEtYdDVoYStITGt2ZGZkdCtvdjdQS1JwWTBLVEtDNXptTHEvN0dVWVpLUGN2aG0rSExrdmRmZHQrb3Y3UEp5cjBxRHIKRmhRWWN1SFdDano2SlpRWUprOXV3Z2Q3WTVPRnNyZWRmTnZ2VVRnTTlMZGUzd3NKSCtNZjlyNjl3Rjl5Kzh2ejdpWDgrK3owNTdJMQoyck8zNUdyN2c4dmg2OXJUWnEvNXFoelVuZzNhcUE4dC9LMW1menhvZHJzZGtPUVhyenN0Vi9JN29QNWVMYTFkakJxMUYvM0xYdnZlCmRObFBhL1c5dStNVlJMS3d4bmhoWGJzZ0ZGRGhVYmM1NHZJd0daNzFnSjJ2eHdvZnZHNE9XdjFtdDFhdlBTOTdyVTZYaXhPS3RpMDYKVnFFNSttUUluM3V2TGtzdTZ3WnBFajJod0Nya2hoM2UrN1RXc0t5R3NSbGo5QWNlc0tsZXc1REUzWWF2bUhrVDl5VDcveFQzRHp3MwpZeEJNN2NHVGsvMEJUTjV1U1JpT09xZXdlazRPanFHa09VR09uaEJoei9zWWFIVkRKTTBaYktBa25ocjRGZWNLL2MvK1B5Z015YUsrClhxbnBkS0psT0lpTnRadGNVN3VhMnNXUm9HYmhnRVlORmZTL0NvMSt5SUdadGFwd3VrMHVLNENCV0t1eGZwZmRxL0YvY1h6dTFWejQKWk5pQ2hQK1VvQko0TTlUTVdjaEVrVjNFam81dnkxKzRjRTJNZjAybnZ1Si9jWm1wRkZRU1dqdTQzalI4a1JnRUFkcGhXa2pMZzJRdQpEejdzaUJiMkYzN21lYmgzNzIrOS9pODkrZ0liNWQwSDNYZkRZZlBrOGZHbnRYdmZ3b2pCcG5QdkFleWtiMHN1Y3UrZ2YzNkJVLzlSCnB3dUVZaVVZOUU2dlpndFlxRlhlNzdraW40UHVldStIenJBRCt4MGluTVp3UEdxMjNxeUFZYjg1N0xUaTZvUCttN0o2ZlVrL2RKOE4KWEVYQXVPaXA3WHUybU9OQXJiSnQyL2tlN2gyV1o3WDd0YjNhM1FnUGJ1ZjNhOVJzN2Y1ZTdkN3o1bUEwZzdLRGZxOTkyUmxWSVdvQgpGaVIyQmU0dTVBWGlDcHg0eGxQbTArbUpXL3VuUUR1VS9ZRSttU1RQUWVYQ3hGVmE2NElTTnZ4VXUxamxKV2d4emxIZkcrU250eHA5ClB5d2Z2aTE3ejlydGlteStUZ2JoTnB2a2lzTFg0Y1JXME9QeE9oUDJhVDZqOGVZdWFPTndqcVpuKy9KQ0NWQlJWL0doZkNpdUxHRFEKZnJmc3RUOFVod2paeXFzcGtCZnF6eVZrNzk3RFg4dldKZmFCZnFDNlU4S3VoKzdDZFpaejc5K0hpWEcwMjUwVGU4L096b2JsNkZNYQpnRG4xWFlVbjNlNGxuV3I2ZzBiekF0VHNlM1lud3B6eWpuMTN4NURXam9DVnRZZG5aN0JSUWVudk9xT3JDTjY3enk1SFF6Z2ZSZDdlCnc4N3dvdHQ4Wjc5K2VvM0MyUzhiQzdka1Blblo3bFFTS3B1MlpqNzBKdlRCSisrMVNta3llS1JGbmtlYTJHMEk0YTJaUlZ0RVUrWGQKcEphYWRMMTNsSjNtdkRhYTgwcTIvQThpaGNTSFVBV3RSbkE0NkYvVWpsODMyLzFmRmlzUVdOQ1dhelE3c3pXSE1WeGVWMWpJVEUvSgpSYk16Y0tTaEJiZDJGMS9OK1hTT2hQZG5GUEYrVEIyMnV0Um9sbkNyN2ViZ2paUHpGdkM2UC9pTkFHaGZjN0QrUmJNMU5neVh3L0w1CjhkRis5M0l3VnZjMEFNZ2UzQm9PV3ZIMzAyNlB4ZzJPRU01Z1RFTml0YTNhdzE4dm1pQmQ5OHV6L3FDcy9WQU9oaTRxN3BZM216RUgKNVMySTVrS2E5UmJOTzJWL3ArenZsUDJiMlhtdks1eDFkd3JZblFMYzVaTHIyMnBtaW5seVI5VU8rb01lN1BnZlNzeFBJSzJvbXpVRQpCclk2WlFZRGVpNkg4NmI1N2tDek5RZWFxM0ppWjZ4ZVJiRGs2NjNEN2xiam1xeEdWSEtTTk1NZ092Z2dkVUxPSmY2QUZnYUY5MnNtCi82NloyOG51VlBFRGs0djJOU3BuanlGemJRMlB5dWJvTmZLNTJsNkdVWmc2NTczc3hjSzk3Q09TUXNWT0N1MmswRWJwQkdzZ1NYSzgKZUx1VEpFNlM3SGN2eTlxMzVicDc0RzlabEdBb2R5b1QrOGFPeUdSV3VHajdFTWVkYVludnlZVElrWW1BYmdIREtpaWcyK2pwWUc3eAova0xvTm5paUU1TmhtSDlEWmpJM05pKzdTQXFLNWNicjY4TG1mNlRJTzJSTldraGg1ckpHYkJOcmpESDRTZ2ZNRzYwemtidnBrZ0pMCk1sUUJOWnU1c2dJL3plVUpKcGZjRnBaa3VhRzUwVkNTbmg1eEJqKzZBWUVzUVhzZnpxSE1vTVZ2TGtzd3ljclc4Q1RQMGpRWEZJK2EKRkJuZkV3SVd1WUEwV0Y1UzY3bThVQitHRVIvY0dIWXJLeTQxT3FWd2VwM1NnNzU0d2NzSzZrWnFweFJtRkRBcUl6R05VYjl5TG1PdgpaNFpkcjJNdnVnRjJEUTZLclpnajJxUjVnZk1nVFZKTWp1MFdtOTJYTUJtQXdGdHNxQ1NtbU1ocDN1d3dHemc3cE41TmoyWFRRNms4Cnh3MUs0bzBBT3pjMDc5Z0dVeVBpMU5EV1JETm5hbVNiT0RXdWRXYnNEc3M3QTNvNGNCNjhIdlRobVBuWHpxdlhYZmovYUtQT25ldmkKbFY5OFJXMmhUY1ZXb3RKWWVLNWRaUnI1MVdOSzdqMStkdnJ6QzVnTlg5VGkyM08xWlhUY2l5OWFZUTVkMTQ4UWcvV2c5NnBidmlnQgpHOHljNy9yNGs0L3pPdXdBS2NUWmlmS0g1UVhNNWVHejNsaTRGemxaZStXUXpFR0ZCMU9Oc1lJRSthRTU2UGhFWjJORTNmdSsxMm4xCjIyWDE2SmtYMVdNUTU5NFNUNlFTQmEzZzFmYWVaSkpiWTRReDJ6eHZsckJ1Wm1YR1BGYjMxdmM4bDVyM2hqYTkyOUYxdFV6b3VKMFYKQlQwWHJXQ1dpWlFDYnREbWdQcU9zOHhNZnBnLzBUQXIxZXJUN0VZT25WYWF1R0E0dkVMNm9lSXZ4bEJXc3pNclhodm4zYzQ1Y2dCegpKbUtxYmc1Q1BSdFNmQ3duRy9pNU43cW91QzUyd24rKzhKZWJLUHpkTW5NUjBYTlhYbXBrYm5ZaXZwcUlsMlNBM3F3VDcvV2RhUlprClRKc1RUckIvQ210cFJqNm9KRm1ZRVlwU0pHMVEyS1c4cG5qNGozMld1TGZrdG1lUzZCdU4vdDZBTVU3ejZ4L2lqODAyTW40RGEzUE0KSXJ2SW50dGF6dXRqckZ6UCs0alo4dnVJTjNIOWsyOHE2cW1iaXRsNzNGVFVremNWeGNSTlJiSFJOeFZ2VFF4L0ErSjJsM3RwSjRJcgo1MTdDQ1RNejh4Sm1YTUkzTStuZnBKYnFOR3VrR0JZa2lpSTFqVlFrK2lOSXZBUlN0S0JRcUtTUU5rU2pLQXkrdTBCME4zSmpNQlA2CjdlVDUrTmhrMjdOTDdQcmpidjhYVE5TNVdkNjNuWXpicVpuT3h1OW44WHRybVRHcWFrcm03YXFRNzZNZnlwMCtlQldaK1UrQU5pKzcKbzU4aWFYbmNPYi9vZW1rNUo3SEorenQyWnhFbzNOUjREdUo0RlBXYThvbys3TFZEVnRHbHFVbWZON3ZsYUZSUzM1K2ZWdTd0M1gvRworYlYvK3BTb2Z2bmIzaVI4NzNscm1pMTNmM3pkR1pYMnQrb0xCd1lUOWc3R09ETkgyTjBYai9kckw4cTJRNTBZZXNHVlNoc3RWTzdWCk15NzdqN0tMeTk0V056SXo1REFJdGl3dTkzaFFsajFYVEl1c3NJbnRHMExxMUlhNmU5S3c5TUc3SmhmTzg5UlFPR0ZtQ21HaWhMcSsKTUY2L2NJV2xLZXdWUUh6WjBtWHpGMk9GbnpaZmxiMVIwNVVIcFFvb1JPcXl2TWhJcTVLWnBMc0lpY21VdTVSZ2NncUZGWHc3UWRqNApWMEw1bFNqUzJ1T3YwcUsyLzVYU3pMWUVxaHFiZDE1SlJmaFRTYzhGSjBsU1VBTE5BcEZSNG9ZRVgrQWpoekpGeVZtMEVoajArQ3VaCkE5clUrQ0hMb0doS0kydnl6TjZIVEJKaHg1cWpuVlBmelRGOEFIcjhWWkZFK0xBdThKYWl4aE44bHhtUTVOcG9kNmx5S3RwWEVOMGUKSC9aUHdMK0FNQWtJcFZBVW9vOVBEdUk0WkNLMVVlcUFXNlgwbzRGcFdSaUtFQlFwTU4waDFOaEI3UEQrVjdwZ2hFcUwxUFVuRlhtdQpIQ1lYN1Y2aytCK2FSc1JNeGlTUmRTbnhMdVdSeGljSmM4dXdsS0pYWVdLbk5yQTU1OFNsR1haeENwMmdrWUJoak5CcHZLSWg3RjJOCjFFNmNJa2x6TzZtMXU0ZWFNZWVBV1lrdWVNS29CQWt0c0h1RzhXWEtDR3NlQjBRWlRRZDhTUU1qS0J2U3h0SGlBM0J1cGtqTFFZdFAKMDBEa0F0QmxpaGVOU2ZGVk1KeXFHcTlJVUU5VElOamVLdEdaMURSN3BNMFBZaEZSdHhUeTN6RC9nUTVhSG1tcThCVTVaQnFsd1lXRgpsaGh0eHhYRElCUnlRTnE4cnhHeUJIbW1lVlZrc05TMDdRRE0zU3kzekJjOGhwNDZPNUV0bWxSNTRvVHdpSEtsNko1TWtlVFVLNlV6ClRjRVpPQUV6MnkxL3FjaE1kY3ZnWWhVNlk2bTFXZ0lWd0tMc1ZNVmVTY21yS2M5MFNyelIwaWp2Nm9qQ1F2Z2FRdWlJU0xFamVjN2MKTG1DeDJOV3RGVFdmcEtwUU5neWZIaXJ4bWJZVGZNbE42Q3dNcHV1WkFieUs2Rk8yWXlTdVlieGdmZEdhVTFvU1JzRTNSUktlOWFuRgo2R1k5VFhvVUYraXU1RW1xSlhVQ3hJUndpNm13VTFWS2l2bFZRVHBpN0l5OWdtSm5mWUtMRWdZRWU4WURxUXF0cVE5QWVVRkJOYmlhCkVrdXd2UU1WaVIrV2ErN2FENittak1XdlVMeWFRUHdnT2lJcWt3bE5BNEZ2cU52NDlNSWtGTGhFRDZZblBEWHc3ZlBIWHlIRmhSY2EKQW1hYVphQXFwSDNmSnM4a3pZZlVyZnZBUHRzL0ZocVMrVmNrWWNmTjZPR2NCRjlXNWFVUTlnRXJYakhDQ0tkUHZBMmtOQTVTOEM0bgpjMXg3c0hxVkpQbU43OXJhWFRyajZXRndwMEQrZWZLQW16aHBjNXI3bmxNZ2xSVXRPc0NaMnRrTGROQzhNTG05YVc4c2xBU0d3a25qCmVhOXg2eE9wb3RXWmVkR1kyb0VERHRIR0RkeTJLd3prV0VGeTIyOVZMTWpUZUt2Q1o2dGhxOEloelQzU1RKTWlBc2lVSUtUQS9aeHcKd1Fhc0NKbVhhVzVBcE9ZOEFJQTB4NjdTaWpYYXM5RmtWcnJsOUZJcTNYNnhHbFFpQkYwbzhoTGNYMnBRTWhvZEZPTEFBRzFvbWJEUQpUSVV1eUVLakNwRmJ3WjNDN2tYVHA4Z0xDaWZqWE5uS2loWHNQYUR4VzRUQlpTeG95OGtsb3pVeXRmTS9Vd21oelZVQ24ybUhNSUlHCm0zZWVsUGRhdDNmRU93OXRQRjc0cGZaeFpMeGdLQkppQXJBVEh4Q25TM2ZFQkRmbXVPMjRPU1hHaHd1bktPM2JYck9BTWJJM3NnQzMKWlMxTWZhdFhBVlB3eFdkY21idzd1Z1VKU3ltd3RrQU9XTVl5WDFXV1dXR1l3VzVrN0NMTUJDMUxBMnViMkNpNXI0S1hWZUtDOFFncgo4VFVscVdzWGszWE1ramI5ZWMycXg1R3VQY3NoTzE0NDREaCs4MjVwVlN3VGFqd2J0RjUzMmtzcnVXS3VIcXUvKzYrcDBjdnpjejRlCkxtclhGcHVKNG5HLzJ5NTd0UmNVVzdVRVQxd1drYzAzMmtZSTBEaHJDMUtWUlRuMkp5cTVvbllYdS90NDBIeEgyYUNldjFycDBHUDMKV3BTZ2ZoK1QyaTVtZkMrVGtHallmbWk5dzduQkJuZWlnaVZtZkhCaUdlZVJ4Tm5wdDM5VEtDdWdVbnRmRjlZekxYa041eGVhaU9ILwpUbTlESlVLalJOSXMySTFPTTlwVU5DajZ0S0hDSGtwQnBpQ1liTWRTN3Mva0I0czBvN1dJU25ubVJTY0lYMUltdEVCaGdCK1NMQ05rCkVyWmIyczJVLzMrOFcrTy9LTmhZQXNHbXIwaVFvM0FwZFB3aHliVGI5TGxia3g4Y1lra1NrODQxOEMrdmJBbVNsOGhVV1c3Rklld2UKMm02NFJVNXFqdkgvSDl0OGREcStuOEd4alFTcmhIT1hzUW9QakxVOU4yVGNwY2tQRG1ObWQwalNKVEtXT3JDOUdPcWJRQTBzb3c4NgpkY2ZYM1AvZnpZdEUwYmFQZ2h2KzVWTk1yblJoSDAwUUtjMHpVSjR6NDg0MTNKZkpENndBSkhTWXdma3J3MEhyU3VsdzZOaEdlb25DCmdZVi9lVzM5V0o3V0R2cmQvZ0RPNmYzTEM3L0szR1FHQ2EzNDZoc2ZaaUlOdTRnVk1wTjZGVnQ2UlE4a2ZwS3hHWUhXQmVrNWJxUE8KV0EyUUZpTlBGYXRDMFo2ZGVSMEt5RXhwL3dBZW1kUVpHK1lkVXJXZU9xVGFQdGlSTEhCRXBVTWg4REViMGhUakE0aEZJWkdjTkhRQwpGbU5tdlRHcHpQQm1tOVVSN1ROMGZNMXRyQStTNW1uR1MwbkNTdGMwcTJTR3pNTG1UV2FjRXNmWDVTSkYwL0VrTDhMOEVoYlo4MzF2Cm8ySlQxQ3J2NWh4MWhxUFlQRGNlTXpEdDdJalRYY3k0aVJROHYvRUxQRk4yTldyMUtxLytpQ1IrUWhEUi9CZjA2QT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJY2pUcTk5d3p0OU1QMzA2OU0zajNlZi9pKzR2eFdrZzFVT3kvUDIvMnl1N0pRYmMvTEczQlo2NWNjMVErNnBUZE5oZDgxT21ldzU4WAp4MGp5Y2RtRTdSb1pNeWpiblJHK1JJOFBhMDI4VEgvM3I4QTBlb0N1WFh2VWZOc2ZrREh2THBINjZlUmJlSmJHK2J4eTQza0lwSjJYCnZSSDByNGtQQi9MMzJoZjBMYklSMjBlSnJWVjd2MXVXN2FQeWJQUkRFNjFvdGNrWC9wTGFXUmRmZ2VyQkhMZ1lsTU55OExhc0llRVgKMklmaDRncXRidWVpMXVxamtmZlgyZ0E2MmUrNUdsbjAzR0JjWTlBY3dteXJ2eTFiSTVCRnA4MXVzOWR5dmJyN3o2ZkEwY3Z6Mm90eQoyTzllT3FPcHo2OExpRVROMDN4Y2ppNHZLSnpjNG4yT1hSL1YwUERzdWh5aVNJQ0R0YWZsOEhYdEJUWGUrWTBNcjFFenRrYWFqTldBClpYRnhPVnBTSndsVVRuWHVxTmw3ZGRsOFZkWmdMbDVlMlBMZTJRRExEWWgvVVY1Y2RvY3h0bWpRWHVCeWkwWnQvTmZ2K2hmUmIvZGUKUGozNnR0OHVaODZGKzdXN3Y1NTNlL0J6SGRnMTZKekNnbmZEZE8vQkFKUXVXK3VhVVh3QS9GRXAwSmk3N1VIcEdNY3VHLzRWL3huNQpkeWp2L3JFM1BIbmJIQXp2UjlNcEx2bzJNSkxnd3pubGVuNTJ1WjRNeDc1dEtIZDYvVjVaZ1RIZGZ1dE4yYTdDR1M3NWdTYm1WZWs2CjdmVGEwRkZSZ1RhWUhMQm1yZkJZVGw5YytnTU4vM3R5UWl6a1JDWDZPL2pBYTVXSnYzUlVOMm1oZi9HMjhsTEhvcmM4bzVFOFVHOUcKL2ZQYmxXVFhOdysvR0RaUm1VQVZCNVpZMWVsNDdldmlHQjhqV0p1dWJNTXFIWjc5c3NhNzhTMHZneUcrNGIzaG8xeUhjN014ckxMTwpvL1NYVHB0dUN5OGRYMWZ3ZGdWd05acGVseTVqeDFLaXVPVHRVaVhSNDdHTXFIZFY2SGwzMjZUa3VwRExLUG0xa2ppNWJVcjhpWHNlCkdhZjlFZWdCZUxCL051aTg2dlNxVURWZFp3MzJmQkoyeC8zTFFhdmN4MERZVzkvMFlXdTY3UzZjbDZObUczU2c5KzFIOFo3OStIM2IKbVRTcXpLNm84T2V6dHFLeHVYUmNkdi9hSEIzMlcwZjlWck9MWjZraC9UNXIzdm15UUVzNWVISVlsNHgvL2c3MUkyelY5VjZvUnRKSQpvcTYvZWZEazBXVzN5M1lhRjdRSnY5b0t5bHRubnNNdkZ5V0ZlajhlZE5vbnVHUWVOVnVsejNTQVEreGVjWHZlNy9SR3lNMnBXc2RBCldiRGQwRUJNRnZtKzEzRzkxWGt5cjNVeUJEMzhkZVJadTZBczlqUXVxblNPd24xbTQ0KzYvZjdBaHUxUldkemRza1ZsOS8zTVdGcjAKQlJzQUZwVWt3cUlPNkxsa0VjNW5GODFXWi9SdU1VY0paK2lvTkJpdk5yc284aXB1UFM5eXNhQm9RS3JtOC8rSFR2a0x6TFhEem5BVQpMSnRHNjNRQllzK3JISk1UVVVES3NwazFJbk1rRlZqR09oTDNFNXhieEdUMzlCck44YmtMNHFEc2RnOUFXcnVDNlZ5Y1dQQzQ4eHVQCnh2eHUvclUvNlB6VzcvMDFVb21RYlhyUk1Idkd5VG1sdnZPeWJuN0RoR21TUWQ3YWJTTGJkYk0zNnRTYTNVNXpPRjB1dG5IM0wwZmQKVHErc2pjcGZlUlY2TTNIQk5tc1VTSCsvQkd5amQ3V2o4bTNaSGQ4NUFLVThPYjdvajhocDk3VGZYbUs2NXphSGRLdGl1RXlnRWRFVApFaTJwMVRXYndPZFBQYzRrUTVQbGgyYXZNM3dOcEVRemtWeCtlU3JNVWt6WWgrZmRacS9FU1JJTHJZVnJtMnBRY0h3SG1ZZExvWXlXClQxWXNYejRCejNmOWk3anJWZGRmUUxCUEtsV013MlNBQXhGVndUR0xnVm1ocXpFUVYvWnEvTHZzbGdPcitqM3B0Y3Rmajh0V3Y5ZGUKcmM2anptQzRwQm1hR3ZPSGFWNDFUODc3RGE1SGM4V3g5ZlduaGpiTFJMV2h0ZTYyNlpGbHlnOEcvUXYwVnRyTFpOT21pUW5OS1JjMApwYktwZHFPeEdXL0FKVlI2NnE1UGd5NzBZREE2N1RjSDdacVlUcjhVT2MyOHZIand3b2tRa0FvcUZncXcvci9ydjdDTld1TDd3NDdmCmpDVDNvM29WTVNIWW5neTVyODVqL2UyMHRzc2xZcFZ4Z21tcFA2ZGZOTnZ0Q1lGOVRvOXFqSUdHSUdzblFDQ2MyWnZKRzB6N290TVkKRittdGZuZmdWZEVIVDJvUExrZDk3elFzSnpDU201RFY1TnFiWHIvMUJnUjM3WlVOeVZoUXREUHNnOWd0YTZkNFdjY2x5Um9yWE5RdQptaGV3RHd3NzU1ZmRabkJReW1qbkdRMmF2ZUZGRTA0SHJYZlFacWNOcGJtTFdpcVpSMldiUEY5YUZEUWlvZVhJZXp5bldFVlVyeUtkCmExblpBVy94c1RJd282U0lPcmkwYU5TQnBXVjlCNFJKODlTWDVEUEVRYlAzdGprTUdvNFlHNy9uN0ZESCtJVGF3M1puMUxTQ2JVSVQKSTQrdWxUZVJVL2MrL3V3dmVrMU04Y2w3WUJNZWJ5RnJNQmtmY3g0YXZLcG1YZUNMbk42MjBoSHFFYkRDcUJKMWZMTFNlRXVpOXZENQo4Y3BOMlZyTDIxclZ3ODZ6OC9pSHg0LzZ2VkhRQU5Qd0F3eExxelBtZG5jUDZ2N3dHUHZ3ckdkekdFNys5ckk1dVVpcHd2bEYxKzlTCkU2M2o0VFppVzFUdCthdXpjZm9BZHZqZDRYUkJsQ2QvS3djVHl4bCtlTmdER2U1RlFkVHd3L1BUc20wbDBMaW9ndDhvTk9lN2FTdlQKMUFHak4wWHJWSkhYL1YvKzJtbFBFSDRFN2JKMGZ1TElXUjdZUWZOdVFUREh1RWFPVWhaVjhRZW9pc2NjOEdVSTN3RUdweHk0NEpRWApjWERLVk5GblRucytqclhuWkY2cDc3eGVIMGU1VUJITDlucy8yUENXL1RpOFJVd1VmVFlSWTFNbC9BV1hUR1UyWWVFbGZMSVlxekhLCmxsM0txZkZpczFsbHkxVGpsY00zUHlDcHFQWDZJV0NwMXVsUlFCSnFHS3haTFJHYnJxMTd3Zmg1NytmK2FZUFFOYnRkM2hHR2swYXoKcVFvZ2lXQ0hmVFZCd3p5OGkwc04zM1F1VG9FamI4YWx4R1N4QVp3Y0I4TVNxUmtzTGduN1dSQlRkNmVWd01ueVRIWkUwWDIvcXlHagp2K21mUHVtZDlXdkI0TGFFMGZONmR0b1puVGN4OUd4Uzd0dTlJaTUrOGVyOFRlTVVOOHYrMlZuRG1yUGRQajYzK0Rrb3djUEo0clBZClBvNzhjbGpDYmtNYnMxK2dUaUd0L1ZpZTN2c0JSRi8vM3RNKzdPZmxUNHZaaWF3dmlUTmptdlpVQjRhamJxTnRtNkJKd2tPNlpMU3cKbWlzZjdMRlY2bHkweitIbmJxOXlweTdhbFpIYk1BVmZZOWJZWDF3TVhMRUZZd0psWE90dW5JV2NPZEpRTG5JUlpzWGNVckhQVGVUegpTblhPWVRFM3VuQ09xMVp5MU9jQXYwSXRMamtJeldkNmJpOXQyWGpHM29YSkJ4SzFYVHQ5VnpzY2RON1NZMzRMQmdHeDlPS2RZYnFoCjFnVFg1aGVLbWJZQVZUK3lIUzVBTmxGc2F0Q3BGQjFXRm91MGRqbnN2T3JOT096TUZMbW56cm0wQ0NNVjdKOTFKczMyczJYNDBFcXUKeWlKMWJEaW1xZTRPR3VmZXpqZXZSS3Zmd3lSdGVGcGRRQXVXOUZ2aXFiMzBaTSt4TTBzUDJvMytBSlgzNXJUZVBWbndESlFGenNKRwo4YXd6UzEyNEUvNml1VUN0dm1vc25IeXVqQXU5RXNuY1VrUDB0VEN1cGVYZUxxWngyTHJvdHQ3TmwwcTJUS3MzYVFpYkxET0NzMTUwClZwOURINHhUdDNteG5BK3UzSUsrMHc1Vzl0QU50a2p1WXFraEoveFlPbk9wdUpYVFlXRlVxUU5UZm9TWllseFA4R21sZVhzMGlIaFUKRjhkbDBZSU5la21aMXFCL3NhUUlLbXNkVUYrV0ZCdEVtVHFXTllvR2c5UG1ZTGhnSE1kVmpMQzlWQ2c4aWtoYVZqYmFZeXJvUmFFYgpGUXI3YmxRb0czVmoxaW82NjQwYTdlNWlrV2ZMWEF6TytyMUY4ZzZMRFM5UGVUMm1zOFpvQ0lSNk44dk1VUnlDS29ySzY4SWl2ZkpWCk0xaHQ1eFRDRXdnb3RNUEZzd2JMd1hiVFc0eXJLeEJiMDN2YVpvLy9zREY4M1lSRFFMbUFTMWlvSEtIUGlIUDRSeGJmaVlManBiSloKY3Y3WGk4YlkvUTNxMnF4U2cwbmRudXhIczBxK21uRUttRlhPN1lMQndUWnJmb1Z5MWltMnVHUzNzMEJrUUFGMC9qWEhEdEp6Q3ZZdgpXZ3VrQ2hVWUxoaHhLdEMrWEdnVFdDQjVvZnBpTlFOT2RNUGxld1NWT3J2c3RSYk1FbHZHV1NONHBpelpHS2hPczlkalcvdnMwelNWClduWTBhSjFINnRMZDd4dkhqWnE5NUFpbjNIYnRYM2VQZjN6Mi9GK2YxdDdLSlllemM5RG1JcFZ2bGc0SlpmQjQ1ZjA0YzdyRDE2ZGEKNSs4V0hOdWpnbjM3SnZmMDJmb0JGNDlPMTdFdFJnZEw2UFBPcjJYM2VUbkFwR1JUMXBqajV0dnlLWnlkT2hmZDhzRzRIZU1LaC9WTwo3MDEzT0lKRjVmMyszT3NudlRjMXZNVTIzbDF2Vko4dzIxN2d4VHMzYmphbjJ2UERSODZhaFZwRC8ySmtUVkRqSnNKditxZTFaL2FuCnlPYVZaZU5HdUxoVU1NdmYyKzh3cFErT0Q1NDh5ZlZoaVRNQ2YxUi9lZmJIZjM3MjVZOWZmWEsvK2ZMemI5SS9QcXZ2ZnoxNGZQNzYKaTFlOTMzM3o2SGVmMy8za29OTnNEUDlndnYvclEvUDdMNzcrL3ZHZm42cS9mSEgwcjArZWZqMjRiR1dQSHNxbitSMmgxTytUWkhqNAo4K0dyejVNL2ZIMy8zNDAvZmYzbDV4ZkRyNGQvay9mMjdueDkvK2gzQXk3MHpXai8xVi8vZnZUMWw2bzhQdWg4OWVmV1lhUHh5YXVwCnBvN2EvNEQyc3NOSGQ3N0kvdnZ4NlBEbm4vYlZmOWMvZjNEZVB4ckM0STVlZi9abjgvdkxSNGZxRHovdS85ejk1TWU5TzRkbnlUZW4KTTVIOUlTdk9zaC8rL3M5L1BmanVvUEhEL0ViamNsLzg5UFdYYng3OTlQVVh3OGI1WjRlZjM3bDhkUGR4KzJ6dkRqSHIwZitjUExzOApQUHZweDJ5LyszWDM1UmRuKzY5SEI2K3oveFpqN1BpZlB4NjJ4TkYvdnY3eUw1LzhhUEZBbDRjSC8zNzE3ejU4K3VOL0RwKzBuL3h1CnY1Ny8vSWNIeC9YZjkyd2ZYamJibDN0M2lwL3ZmdFo2Mk5KL3YzdndXcDNjLy9MQm5mU1BuKzEvKy9uL2ZQYjF3U2ZmUHpvb0wvLzAKNXgrKytmM3IrNjFXOHcxKzZuejI4T3pvdFcxWkpQZWEyYUR6aC8vNW92UHZiOXI3M1R0LythUSsrT3hmbHcrT2p2LzRIK3ovcDEvZgovK1oxdW5mSDNQL2hwNjhmOUZxZm5ILzIxZFA3OTdMemYzM1Z5Yko3dzdQMHdhRDFSSHoyNWd2aE1iWU92eG4rQUd6TFBpbXpIOU9rCi9VWG40RjRUeGxjOC9lcHUvZk55djVzOVA3Y1UvT1BvenRjSFQ3NzgvWThQUHkvMEVNYmx5VC9ONy8rY0hmVC8vZG1YUDdULytZVTgKL2YxUGhQYlB2VHRBMEovTm4zNlBRL0pQODZQNWV3LzU5T2Y5TjUrYXVwdWFQN1NQRXZIVDc1OGUzbXQrK2NkSHYvdnN2d2ZZaXNFZgovazFZcU1qZW5lVDAvejVSOVBtelB6LzYwbjM2OHNlSGY3UEZEejUvK0Q4V21meUhmQUpUOTJYeTJaLy8vUEJ6ZWZpWFYxODVQRDkrCjllWDk5cy9mL3B0RzBuY1k4RDNiMTY0VktMVC9qZS9BVDZFRDR1NVhMN0JRcVFpbWY3ZC9lRUtzUGl5SGYxSG12ODNQclFmZkhmNzgKMmVIWnZiLzk1Mkd6K2NrZjlzM3A5Mzh2bnQ5NStmMkRad2Y3encvUGpqdi8rZm8vUDMzeGF1L08vMi92eTdmVFozSUZuNEIzQ1BzTwp0dG1Yc0dNZ1FCSUlFQ0FKTzRFRXdrNzM5UHh4bjMycXlydnhDbngzenAwejV6djlhNEtOVktXU1ZKSktLdVdDdmRhUUltWS9QT3NYCmg3aTdrd3Qyc3k5azRmZDltUC81RGZzVDMzL1dCWm4vZHVHQWdLbFJKTktjYlRsODBiZjlYelg3VW5OVnlZSnpWcVZvd3hDYTRuMncKK3FlZHIrRk9kOFo3YWtLcGNIU2NTYlJPcG16cjZYUytuSnFJc2p3Nk1BdlJQUmdaVUc5QWNsN3lKNE8xMko5WkY4UW9tUzVnNUdjbQpnRmdnT1VxU0JjQWRMbzg3dDQwTnhHc2xwQ3gvWVptRm9EZ252VHllRVpYQVhQaDBlcXFWc3NNbkhIRk0wcjFMakVqSGQ5V1h4Wkt0ClB1RzBESkxVUUlUa0NKK2JzVG5wc08yYytXVzR1U3E2YXo2UzQxUWdBTDB0MURCdnhSbmswQXdRcWo4Ym1KclptVjhzaThkSVl0cHUKWmlOOTRsMjhCcS9sZFVjQTIxUXFlajJUbU5TU3hGYnphdDVnemJicU16ZlFNS2xZSVZmcnJhUkdpOTdrdlZmcVJiNkIwQlFKakNnRgo2NWVjYzNvbG5hL3JCRmtJOVFsM3FqVHlHcXpjdk1Dc3B0OWtzUkNLNU1MZWx3NVNPRDY4M1BFZ3BBWC9hT2ZLL1o1bWY3bjFwclBOCnRwWmRDd0JSZGJNQWRrWFA5cGtnYmQ3SVI3YjV2YlNEbjMzWmNtRjNiVWxwUzJmaDIxNE9nOUZ1bDVRV0xIYTZaUT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJUm9VREJOV3ZWb0RNTEI2N1VNR1BDcFBnOGozYnRrNlB3dmRzMldicll4MzlYWHVUU0tOeEd3SEF3ajNmUG1ISjNNcXgreUdUSmR6QgowKzM5cG12TXB3bllTM2pLR3NjbTU2TGpjZi9PN1RTaXA0Q1RIYWZJQWd4di9uYnhQRHB5SnlySGI2Q3hwK1pRRGordlA3SnZvNmM4Ci9UU1dIV1dTNWJ3UHZETHFBQzFRcytYdy9ubVlmVHUzZ3R4VDlETFFZK0NMdjh4aEdyZFJxOFdYVDM4dm1YN0pieUxONzRVRG53emEKMllETmFDbENuczdqcFh3dEF6K2xzS2NTL29qTkovRTA3bkhrMCt4M0tlNFhCaXYzSnZvVy9wbURxakNQZm9qK0RML1ZpQVo4K2tqOQpta0dRaDkvbEtHRFpwSmVNaEx5RkpqSHM3NHJ3bFNSNkdmNVpNRmpaNGVYZ1M4OGNIQW9MeENjRWtXRUhuMkova1VDdndORzhvaUd4CjA4MmlnUmlzYUpyVWhPR2dJcS9sZWd0K2wwREFPQ3dJaEpoRXpKREZTTkdmTEx3M0dndjdhL1NiQkh5T3BwSG1TSWxlUjhPanFMaFoKSkJvc0NkNFFQVmtzU2RIU2dkVVhMcFRFMGw2eEVLSmxvTEZRdjRIRTRtQ2phU0E2Q2NueGlHYkYvWW1Bc1poTEVtUElTTTRscGI0awoxSkRSSnhZaWhWVDRKOFVXUVBQTE1ZWXNXOENYYSt5c09GYVJKQlo2QURqNWtsaVAxRnlSNUREa2VPVG1ERjkra1NLcVNGSlpBajVTCkZCTk5FOUVrTGdTUkZvNG16MkttZUZxRUZMM01TUjNnWkc2MUJLdWE0cmdXdlE0cGtaVVFsUVE3d2xkV2VWQk1paDdRc28vQTZpZTEKTXI4Z2tpTXFJc2cwajBrcFFEUTFWalQxc1NHMUpLd01VZG9TamJFWmp1TE9GL1NKSWorY0JobzgvS2N2TlAxYzJWYW51aUV6OW5rTApXUHJmQkgvRGlHVTNZSzljMU1oUnZSY25qUzRmMk1YZ3ZLTE1idSsxQU11bHNNcThtOXVML005Z21DbE9WM3NidFpYeHpJblE2VlhlCm5lR2JIZTUzNU00QVgwem8wS0NCRmhaZVo1b3kvVjRyems5dWp4TjRXNWc1MDdFN1l2bmx1ZGt2Vkt2N0hIOUN1ZUU4Mjl3VVhqSnYKZzhNRVdMRHJ6Qjh1eE9KR3ZrUDhHVnNQazk0UnVSVS85WnVjSDhBSjdleUtVMC9Mbnd0N0NDL1BiOFJ6azBWaDdpcVdPZHNLV2twUAo1eGhIR05vSys0eSs4RnczdmhFQlhGUG8yOGJXNVBhYk1zOHVBSFNENEdmZXJiL2UvTXpDY1FGTENWanJvZjR0em9rRzF3UmFTamM2Ckp4cGNFOXJxWXpuVUM2ZEd1Y3dDMzNldzRqeVU2TnAzS05OcjBMYXVhUHVvKzVSRFppeEhKdzdVLzF1ZUJYS2tHWCtxL0ZtbzVzSjIKZ01BNXlUVXlINjVjdy9wMVF1WXdWQlVSQ1RyeGZSSEU5cWx3MVZlby9FYkdITm1BWndFSlI1TS91eWxIT3V0YUsvdlNyZ0oyQjlKSgpNU2MzRURtbmdxSm5LaHgyc1FKUXlxM0RpdzlrL1J1c25QMS9PZEQ1UzZHMHNzNkJlRFdTTUtneEJaODhwOUxPTkluRjNJbmdzeEJzClRjWkpTZG9wRFZQS3ZxMHpidkRyeFI2Zk50TXZSS1p0ak5HZWFzczFCOFJhQlZXY2ZxSy9NdWVYbjhZNEdQQnd5S3orbHc5TTF3ZGsKUDdlcXU0RWo4dm9kWkFVa0RoMkRRVzVGUEJxNUJ5eXpZL0haM3hxRHkvUWs4SkhET1RKWi9QU3hVQ0xrNE10bEpvdWJNNUJLd2hyNwppMUtjNWJINFA2S1JhZTBwMTNqTmdOMmk5L2RNRC9UY3MyWWJ6MjhkNkdzekQ5NS9BelNOdHpZc3NqcFBYckhhenJjdUhWL21CeEZtCldpcEZ1TytOMldCbFdaTE1IQTdMZGpCZWYwOGpCQWtzblJoQjd5NlBmY2Z5RlI3c2wxMnVuQXVlY3hqdWFSK05nalg0R0dSamlhcVgKZmRBSzduMC9XZVNMRlQyWmcrbXBUTHFjbmlCUVYrL0F2L0hNTm1CM2FwTGswR2xjd3BrR01zbkpZSk41YjdodDhhWDc3UWRzZnJGZgp4T2M4N1h3UndDaDdDMDhIMTRqaXNVdU9NUlpUMUp1cy9BcUFhWk5lSXB5MW9SMFF4bUhVNURmQUJSMlZwWmMzRUNpOXdCWGNmdWQvCmpqOWhHRlh3ZmpTeHArSStMNEtvd2N2bmdXMFl3YzdOU0c4QW1DL1ZOWG9samtlTndPWW5uYjNYVTdiYXcyZlVNajVXbjJlRjJWL00Kd3pDZnd3VDg5RFJXeUpmTjFHYmszUnBUUGlvUVFFWFAzaDlEOGZuSU5xZnBWSDcxNTlhK0pnNkF2bTJRMldHd1VoaWYyOUd2NG5CZwovUUNEWHhneis5ekF3ZTVZUzVidmdOWU5XNnlBRXJFSzNITVd4WW0vL3lPMFlaREcva0MrTzcxZjF4YkFVb3E4MTNkdndyMmJlak9TCkRCVG54Y0hMWkFoc2t3UlVPTi9PNHRTTmZXYURqbGdEcUwvM0tHVUwwT2dENWt3eTNiWVZLbjgvSDdsUXArdkxObHl2aTJ5ejVRYWUKUmNsYStvcmtWdmpzd0RkVnFBM2wwVitvZk5SYzBOaG9vbGVBZGs2K1NRMCsyckpaM3NpaDJkWXJmQU42NWRaUmk2Znc1SEE2a0hIRAp5ajZ5SkJiV0ZlQ0ptQ254YlhsYlNpQU5nazIwZVN5NlhlRnYvdllHMTJvM3pBMHlIY3ZweEdLdWdTVmVHTU9uR3RrRW5CeHErYnlDClIzWWpWdmtvSldrUTdQNktJclQxMysvditJKzlVUWNtblNkTEZucGxsOVRVQk8rRjNBWnI0bXY3OGFuMFVzY1ZlWjRtWGtTdkpPWUQKNXdyRjBlTFBpOFJQMGJQdDQwQXpodkRzVzRqOEt2MmRYSGI0M2krallTNjR5SHdFZTBsOVNSa3k2WEMzbEgvT2Q5SWkzcUJQTHc3RwpEMkEyNzV0Z2tUTjdublZJZzhKOGdMenZPNE1WSFlCSXJJRVB5R2MxSEg0azkrVmtZVjBMY1VZd3ZZZ0RzN0h3L1RiYTVuOU1saEJnCjdPQkxkcE9xYktWNENLNExaTHFYN1hRWHEzLzc4ZXhMdkZqTkpISTd2dVZOanl6cHlCeThzVFBVeVpsczVHdjJYUGlPamZ6Ri92eGsKRTlFejludXd0OEMrUCtobHQ4c1hGNkNZa0ZsWWN3bjkrY2tOSGd3cVhlRnhTZW94SEpCaWtOZ3FXMnhsSTdFbU1odUhoZS9HRWV6SQpsS3FnUjFzMmsvbnMxNExNWnhiTnN1VVJyMlJqL2xWVytwV3M2emZUYVZYR1pMSnc0bzBHYUdLektSTi9ueTh5MGQxaGlPS1NsMWlBClErZENhd1c0NkhXcGRhYVVtU2VFa2l5dDYzL3VWS1JVQnZ2THZwL29zUXNWQUlxeWVJUmgrZ2Jnamw5YnB2UGRPdk5ncDhKUDU0S3YKT2ZObTRzKy9LNTV4RHY1cGhVM2tKazhmdmJEaXVzOUM3elgzQjNZL0FmbnRhLzl5UGhneHBKNEgrZVFvdjV3TDVjSHhXUEJNeUdMUwphYkcyQVMvMjlybS9sMVRva2w4YUpyQjlyZnJRVWtyZ0RxRG5nbFpncXJYOGhlL1dBVGg3MlBjZlR3SXJ4NjlSeVVvYXE1bURlWEFxCmZHOFBQZ21HallGZGMrR0VaenRkTXYrZERuRGlRL3N2MERpSXdFQXNaV0tSbHU3dkQwYUVWc3RDS1dXZEF4TWpGcVppc05OTy84aUMKVFpDT3l1d2RPcGVmVUVkMEMvUGtBTG9LWnpmbkFES25QQUdYd3hrTkhSTHZ3Q29xL1daZjhGd0F1Tm5wVlNhWjJwbjVzc2lZUWVEVAp6TStqSW5iMDlNT3BkckNZRFhtckRhZ284RXNzNkNVMDBBRkp0Q1QzSkJOWjJ4U25vUTNBN1BVNUpZaGx6MGJlNmlSWWc5NXJkcHY5CmRraGpBWlN2UnVWQU9JRDFoQzNpZFhjQTdLUXRtMUdUcEFyWW5sb1hPY1puZysvWUNRaHhhNVBGaURUWkFFdzF3b3JUM2lGVXlKUjMKZStabHp4WWFkR0dLa1VxOXpCb2RITkxXaGZ2eHFmL0VvdzdZZGI1eTYwakdnbHdKK2lBekRBOFhJcTR6MUJmTnVQYzdlV0JueWc5QgoxTXRPTExkZTcwWWlCNTdqTWQ2MlBTbE9zS1NkYjhIVEVNUFVnK3g2UEllbkVtWjRXSU5sbTl2YXFlQjUyYzhZUzJsMVprZk4zMThRCkZsdHhhZ3AvQVUvR2VDQ0wwV1paYkd6c3k4MGFjcFFUMzM4MkczRGJmOE9jTFNSaEFkU0J4aTc2MEdJRExBbzJSZUs5Nkg2eHV3RUIKQjE2bDkxSjFJSVpWQzhCTStxUXdHNndzN3ZkTWNrMDYrVGE0R05pYnZOeEJFZDdubjR1eEVkcFEzS2xqUDhZcFI2QXQ0WlFlSFExbgpEM29vei80ZmN6eEpHN1RNUytSdzdROERIUmxlWnlMZFJURWJMUjFQOE9qYlN6cWV2SjA4M3Y0N0p1WngyeCtZYzlQSng1emEveUVyCkV1cms5SFF1MUpibmJIU3lXL0MyQktSdys0MXhHZGg2V3l6eGZaNGZ4S3AzNnlyNGViOWdENFlSMHhpczRUMGVld1AyOWh0SlpoYUEKeWQvT3JaazYvNGFSMDR2b0FQMkFPbUNmZHdMWUk4bDY5czJUQWV1eWNHMzRXQkJ6RnFkbjB6QWI5ZnBNMENBTWk5Z0xiRWViZVM2NAptczNDbmZaWEp4cEo1OHY1NWNmTUJEMHNVeWJaQ0F3SzFXclN4QjRXaTM4RzdERnliQ3RtRWw5dFlIWkdHbTZNOE95blFNdjNWMkRQCi9kNWxtK3ZRdTBBSmo3dVpvN05yS2xTZkFvRHR6Ym1kbEVKbFR1MWYrNUhXOEx3eFdNR2IrY2ZDSWhRMkpUM256NTl3YXU4cUFpL0kKdFNlVDVKVG1SbFkvc2FxSjBVbTBIanZTVHNXcnpmMllpWmdLS0hjQURPUXJIM21iYlpQQUd0OVpCbDR1dUJtZnphYXRPREYydmViVwpYcnhWNnZhN0c3QXVweHdYemFOZWVSNU16dVRvNjd5RTJSWjI1QnQ1TjU1V0VDMEVmN0V4ZUxNMHRScmZmdTg3V1BpMkk3dVpMSTZzCmJoTkFIQkIvYUEvazVWZ0l3SGFBOFRvNEF3TDJyRUN0TmM3Y1U0ckgzck90ZHFURjBTNjJ5dnpXd016LzNnVWhTMGpsMFR2WW9uYlkKTVlzbG4zZThtQ2dpVnM2ZWJCVmNQLzVwMHRPcjVySlJUd1hDcy8yQXVTQlpaUTBpT2NvemkwTmJlRVZnVE9XbTJaZHFHUjZYTG5xUQpEb1JnU0EwVDJCem1ING54Ky9ZWGVLLys0cStWTE1hMlA4VkJkOWtHQ3NmYmszaDlGRzYvZlB6U0dxWkdmc3JDSnAybnhMWTREVHRECnViclQrUlBaVkJwdTZJbUxvOUVOc08vWFNUdGdaMHUvNk8xRkY3bmdlOERMVy8yZmdXZVJiUldPcnZDK09rNC9BdC9ZQVp5ckpDN0UKeDBKeFBRSjVtY1JYajRXdm41Nlp4MmdzSE9UV2d6M25UdzVFb0pGZlptYmVRbVdkZnhkdCtSalIrRFNUam1vdUN0Ymx0ZS9aOHVZdgppSU9IV2Mzd0EvM1BVSEhTS29ZNHpTakEzSWVHUTNtR0F0Wk1oS1FmUXpFVjZDTUhzSmt3NWhJdmprZS9udXdtYlh3bFI0NHZBZ3JEClorSEorSTF4dzJOZ0R3ZUp5a3U1R1k1MzJ4TXdsemxKbTRpQTFXaHQ4WVF0d09vRE04OEpFSHp5WS9YaHVQbExVMnBSS0RaMEYrdnUKR0pBNnR3ZklVT3RZbktiS0FGVGJ0clBFMnNVUFMyenhEbnhrTUxJeWpNdjdCdG5XY25xQ1kveXZGSnVDSjh4N0s2Q3J3elJrK1RrKwpLdXYxR1NXNGJ3OFB2QWFlT3F2WlpMS1NOVitqTHAwUURGTUxYd3ZrRUYyQlU5aitlME5kRGl2K3NXUnlzdlpyNTZXU09Sbk1UUDArCmowcXdpQi9RSkQvZVVhWDhQL09qSWh5Q2dvTXlUQ3ViS1lDRExyNVkveWdWbUdpNTNFY3lBWjhaT0hXTEUzblkvbVVQcDM5dkQ2dWMKU3ZFajgwUFlneWZQWmEvVE54VEpaN015djJzZGZ2N2dkU01LNmVjc1VjZUxlWVZmZWlFL0RlYW1lNjJ2OG1lcE5JVG1mTUVickJJOQpHRm9BTW1vZFJKTXJlM0MwbG9DQkhzYUgrY05wT1grZ0t4a2Z3TkpBSGpvKy9IczUzendjeC8rQ1FqYmVQUENGRWZMTHcvZ0l2K1p5ClpKbjdRSHdQN1NNQ0NmNFZBdnZQOXZ5d0E5ejlBS1JzVHJFcVFrMkJXOERlNUQ4Q1JKNEhnSXo5NlFhTS8rRzBoU0NtODRjZlZFNDkKZmxpUC93T3ZLUm52ZG9CdEtRaytucWRMT0x6S3BvQ3E4RGd3RkxZTklORVpqRzc3emFIL09UNmNOeXZZTk5pbnJHSVlzUUdncDRlZgpuWEk1QUVON0VvajQrM3dDTDJCVFgxYXFLOFlQVTE2dDlDcGJlcTQrQktTb2N2d1NJRm1OMERvQWFySlhpMGkvR0VRMG9JdVF0WEpmCmphdFpWVUQvZGhwdlpyQnppVUxsQmdPMnZ0MXNwNmdMakx3T1ZzSkY3UURnTVdCY1doSENuVU9ERGhUbzRPejBzSjJNVDdYeGYrWU0KZ1lPYVJzMVRaOVVmNXJvcmdwRFpOVmd0eUUyMkFvdHhzNVB0dnpUc0dJZ0Y1QlNvUHBYTjF5UGE1SVdpZEg2N21hSGl5OG9NVVB6bgorNGVwTWRDdzA4ZzNkRkhlb29VRUUzTkhXQWZCdUNYU1gvekNxZ0wyaWoycXFFQ3lLNHpZVU5IUUlVWjE0VGtTY05NSUtoSU9zcWs4CnA4a1ZUWXAzM2ZmNXJieWlMc1pBR1lQZGhMNTNraE5kamVoNHYzN1dla2tCODF1NlhmcC91Qi9xRXlSdEtwRm1SQT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJV0RBbU50RVVyUm5xNWdXNEN0eTlPakkvVUxwTVVrbEdSRVlaeDF1S2xoeEVNejdOVzh2ejMyUXovbGxyTU0xb3dXbnhMZ3BUMzhxQQpLWUtJME9KVk94TWh0Zm1BdCtlOGE3TVVkNkMzOHdTdTZIWnpha0lPMG1iRlNzbWlWR0VvMzlJRDBFWDNjMmsxemJsU1JPV3BJeTRUCnpWM24zc3ZUa0hwRS9XSUQxcklSaXJTYVdEL3BrMFBXa0FjK0szaUIwczE1MXAwVVZhL0J1NitnNFl2dWh0Nk54ZTRhZmNNV3hSb24KbHZFU2wwNXVndTFOSit5Z0JzZEpkVmFEdHhReHp3eG8vUHh2Skl2RzNLblFLT0pPZHlaK3pPK3VlOTNwNVNrQVB4SEJaQ01lWUI4MAoyRS9vUVNLUWJwMXloZTlZYVZVMk54L0g4T3dxeFQ0bDNJL044TkxvREpRZmpWNi92V213R3QycFZkTG9mTzdIako3bEQzZzArdllaCjNlZjRtOUZUN3hhTVhxeE9ZUDdIbmdPaER4bnp6a2J3U0J6cllIQ0ZWVEQ5TWtvRmN0RkFOTndQLy9WUjRnWUtpSEJQc2ZKd25qZFkKRDRmVTR5VHIyVDAvWmFxeFl5cGFUcjc3eUcwLzJDa2VQdnRZb1UvMld1Umo5bkdLdTdLUkRZMGxZR200ODE1WEUrQ3JoZVdtUzAzSQpGNmpGTElCaUZ5L1Zqb2RENHRnQ1NEd1Z6Qjk4bzZiQmpld1lKVThkWXJCZDJiQ1pEVWVZWHppd2h5L3NtQVN3bzJkM3FtUzBvWW1qCmRTbXNmUDVqdEJTTTdoTy80TS9TR3Z5NlZ4QWkvVHg4MVQ4YjBraExrVUVvWGhuNEpKRU9MTTgxZzFXRWxrTWEvcW1Xck5KSWswYkgKNFloYkQ5SklYL0hQb0ltSXV6aWtCaXVIOW1qMzFMMHlTRU5MeDlqZUswb2pEVG8vM1VsaUlUMVRFemtJR3F6bTltNWRrNW9yUnVhcQpHUm1rWWJObGMzQSt5aUR0RFREeSsvbU5RNHFLTEZtMEpXUFNoajhOVzVKSVMyV2lKVXRlb3A5cWpSQlN3SXVUb25CTjN3RW5mNTVUCk5ZaldlYm1xamw3Z1k0MDdBZExnOW9LVmFsaU9SdnBxdDR1UWhrSi93eDJIbE9Oa0N1M3c4TFhjdEdTUVpzYmhDR25CSlpFTzBxT20KSE5LeXdXb0pPQktmMG5OTkdyK081dVc4S1kyMGtYZW05cGEvbWhSU2QySWRUM0ZJd2JvSVdjbVY3bWRlcEpFR2V6Mk1qR0hQa2toTgo1Q0ppYWY0UkwxSklEVmFNL1BvaVplWWFObHRYbTNwZUR1a1lLM2xHSFdta0pTeHJuenNpUFlUVVlCVVQrR1QycEdta1BhOURST0QwCmMvQ1JKbS94YzBVS2tQYVRXQzNpeFNGU2x3aXB3WG8wbGVmYmNIUHNKUURheUU3TXY3V1AvbGdHYWRnY1djM0lnUnpTQWxaM2ZjVVEKVXNoam9ybFc5dkhNNytHNUtZbjBMV0VQeUNLdHp0OENtQlJTcVBtREgxN3NiZWcwU2MzMWFLcVc1ODhmZmFkREVtbkh2bG5JSW4xYgpOa1lUaE5SZ3ZaenJSd25yVkhkSmFhUzFnTFZOWnBLUDBraDNUeVlwcEVBblE3U2RZY2wwa2lId3h3djJucTRXcEpIV1U4WGhvUEgxCkpZbjA2MlZWUVVqaC9uSTUxOTllYUViS0lQMk1ZbC9IdFZjYTZmUHY3dThsRmcySWtNSlVBb2gyV1BGdVpBbDhzTDY1alRKSWUyMnMKK1BQM0pJazArdXcxR1ROZlhxREhBTnI0WGl3MDUyRmtTQ09kQkp3aW9YSDJhMk1YUWtyWVU0NnljS1kxYk9TS1p5RlNENGNVWUlGbwpBZGpmUGFQMEh3OWlwTWV0N1pGR2VrcTdSVE0xZm80L25CVFNYQSt2Q0JXaDUzQnNwNHdBQzBEcnU5UktGUythSzBDYVAxMm93cklwClRDRk40MVd2U0JGNmRzRm5hcWNKV09MNUtrSUtzZEJvTFlmRGVMS0JTREVSMHNNaE85OHkvUHRrRkNFOWhuN2o5RTZUampkOEl2S2EKdHZOUnkwRGJEL1B4TUN3Y1ZMbHRHMnpqc2s5N0RmeXZJL2QwaVpWSDFqUDM5RUx6QTlWYnNNbjhHcXlCeloxbnh2VTdqNGllaHFQNApzRVUvUGE2aUYxSVozaXhOWGFubmxGSjhyY1EvWlo5R0NWZHpKUDkwT1I3WVdZcGRQczhTdHY1VTltbk52VWtUOGs4bkw5OEo3cW1JClltSHppMms2S2N2OE9scTJsWlB0SS9YMDI3NlBpWDdiZHY4d3h1azNib3BmVUt3OWVYcmJTRDJudEZ6QnZUcklQbjIzVHZ4RythZWYKaGZnalF6R0o1eVBySUdLUmZmcDdldDJWWlordU9rU3V3VDI5b05qZmlrd041SDROaHRSSWhHU2ZQaEhCVkZlZVl0YnBadkpXay91MQp6V2lyZkxwa254YXo5Y2xjOXVrVGtUYmg4aFRMR2dtTE15N3pORlRHaW84dVpzNXhSMEwwMU4xcUhOUDAwN3d2S1piS2NtdFVjbVc1CjU4RmowTk1VZW1CNWJHYi95TlA2WjJSZHc2YzcyaDBsaHlGSzllUzJ4MmZxazBDUEVTY0w5RER6UnU5VHJBYzh6TjhXL01jSHZ5T04KbmtJekQvOTVSLzRiNjcxUk9vTEdOMjdtR00xM01CSDJ4MWN2cmMrQm55T3d4MUxtZ0IzODhPa1B5UWIwZEhnUzRhOUhOM2JneDNiUApRTFdhYkFEZmQ1TEZaL2IvUEU2Y1FEdVppb2Z6eU9jVnFOdUR5V0RsMENKUFJ3WnAyQXo5bkE5cHBNRmVYeFlwMkVSK2NaRTl4cDhyCjhuUmtrY0l0YnlxSGRNWkhHbndEUGpJUGJmUTU5OFpET3JQWnpCeFNaUDJ6U0FNaThrTHJuNTFwYVMxQTZ1d2g3NVdQVmtEZ1IwSVcKS2JMK1paQUNmeEJZL3dNT0taaUxZSzVmc2tnQmdZOUJlYVRRK3BkRmFyQkMrMzhwUGRlazBhZUV0R2FYUllwc0NnNHBsSDBCV21oVAp0QVNyT284eDZORW5laUdjK2Y3c1Q4dDdnL1BmaGkvN01tK0d6ZnRCc2Z1aStsNW9TZk1kclMxeVlNNmZXVnBiWG9qdXlQRUhxZFBrCkJXaFM1UGxBS3hmYlcwWWs4VEMxMGNQOWs5bzVaaTNhV1ljNmlmTDNVenRuY3NkQ2RDQVFxVmpaOG90R2tZcVZQZ3BBY1kyTEVIT0EKVTAwMCtsVFJTdi9qcVc5cEJNZ0VaaEN3dGlVWUQ1amM0NDU2U1JSOUFtb3Y1MS9PQzFiMm55YmZZcVpqWmkvc3l3V0lwY1NmN3VncApUY2VVd0pEYnIrQUxHelJvenk2R09wVGRUcXRqM2d4ZWJVSUNzaEUzTUdEc0tXUzFvbjhnUTc0THZTU3BRYTFpRmRHZytFTWFudTJBCmRlc2V3cDdHZUhFMFVUZ1FrUnhveTY5enJxcEdkUGdQUFhqS21aZVluOTBvT3orRGxac2gra2QyQlpuMWV6T3ByNStablorTDVqR3AKR1VManRhMUFMTzNydDBYOFNYa1dHb21sQUF4MzVYNUpXVkFHTGV6T0VDdmgxY1ZaSEY4SmRtU0tzMHFmZS8yVWw2UTdNRzc2TkNmZgpUSGtpMTQ4OUtkUGRJRThzb2VweFhxcWVRVkdvZWdLeXFzZWd4cnJGejhxQlQwQjJ5QUlDMmlqVkkwMjdBYnlLaGlHd3pIZzhSUS8xCkQwMDdmQ05OdTU3WEtLdTI1YVFTeGVNa3A5WXo2WjhhZnhkRGt3dWtPNjI2R3FsTFZSdDltaUE1a0lKYk5DditMc2FiMWZ6Vlo2TlgKLzBLTkZnR1d6bGJYaEtRMERPQWQ0TTUxZWJSbHhaRFA1eU9uRFF5dVc1QlpKczhPSDF2c1pUUUdLczUvSldFdytWMTZ1WFUvTXB6RAo0MlFVMUpFR2htc0ZKZ1VLMFoxM3l1TzZsTHN4cVZYdWxLVU83SlZqVS9lc3VKWm0zTlBHUGZDZkR6ZnZrT0tDTFlCbWRCU3JVc3NKCktNWmZVUGdQV0ZBdUVIM0pHMkIrckJhODVJMlhCanNrM3JqUVhFNGx5WkdsU3BPajNDUXoyUHpVOWw5WVlaZEx3bmw4ZGZmbGtzeEsKYWxhWVZ0V3ppaDROVnRtTlNZUE55TDBNckptUGsreTJaTERxV2Q4U3BFbFp2L1VrSmZ1emtsQlBTeENydnVVMlhvVWhuZEpQc252bApTNU1lanhhVERneEozcUJqbHM1ZzFiYUN3TXRYTkN6VWJIN0JDbTZzaW9hRmp2VXJ2ZjV4b0NoT3ZnSFkyMGJEdU9CVloxcUFDVGNVCnZlTmlUbmhwWUQxbEowYlBKUGxXM2MwVUd4N3VTREdoUnRNNXJwMHdEak9LSEM0YzExUHFlS1YxTERoL0FlTXBTem1EY2dhdDVBWk4KNUxwTG80b2U0eG5ZMGxKNVNwdnVKcFc1N3Q2aXoxR216bU12VnJJTWoweHFIRzJnSjY2Zk9qMlRUYXZ6d2RxV1l1cWtqdXkyckJwRwprRFlNeXlLbElDa3Y2c3VVT2lxN2VLS0IwSng4T1JRVkxhQnBJQ0xiOGpxYXFJZzlieUNVait5VzJhSVdaUmk2L3REaGsxT0hrc0lrCkl6YTNoL2c2VG55OGdEYjRWSUU4WDlTQlFDYW9BU3hCbzRJTkk2aytaQVNrb214MkdLeDZCbVcrVWdFSXRTVVV1YStUVlhsYjFxb0EKS3B3QzBPcnZ5OC9Qcm9Ia0JtMUVGOWdLNnZFTXNTWFB4ZnArSy9CUTcvMGVURlU0NGNXdlk0blRESUpjT0VuSFI4WW5CN3kvREdqbApUL3JFU29sWTJrVmMxdTFCWGhJZ2xrNFJGeENMRlhIMGk0QWx0dklMQmZ3SnprVW80cEpldDNwMENYQ3RTVnVFUkNtVzhDUU95WXBICm94U1NGVVZJL3A2RUZyeU1VNndla2dWVDR3a1N2ZnE2d3lSUE1DK2hyalc4d1hwSjRxRThuWVhiOGhYaERjQUhld0pOQ0dXcVhLeVcKOXJVUzdha3ljUitEVlNsSzkvY0VEM2Q3TjhWOUlGblVZMzBpVlNBVHI0SFVDV3ZpWko3ZUZFUWFkZ0pUT25NV205S0FEK1NEaWJSTwoxbUJNUXlwWHhhYTBIdHBSOWhnYWoxVjVQRnJqQzRGME8ySVhjUVRQRjlPMUVWWkZsdkIxOGhKdk9KV25wczduVlc3elkvZVhLNkxSCnU2cDQzNU9ia01HcXdPbUFkNFNiM2pVS0lOMjJoZ3dxYTY2Tk1FT1ZJTDFCdE5YSjJiS0FPcUtOVG0wZjR0dXkxRng0V3gwOHJMTHoKOHpmZ3JNQjNXcGhCVnVwNG5Kd0g0L0hwaTNsZnpybjdKclhiQ1U5Rk5SOUJBbUJ5OFZhcGFDdmxXY2pGV3lFd0hjSW43UzhCb1RGWQpiejZrUUt1bXZPVVpOTU1ST3BMNlJrT2ZpVk53YnBWQUNncnJUWEpRR0oyc0I0NytmWS8xOXlXQUNVNEk5VzJpd3ZOQk83SXVQQTZoCnhabEhhYXAzT1dISW8rUlRnMVZkRERWc054QVk0ZE45eGlkalJNQmNUcGNtVXZMTXovaGVzSXVKTmRweDVielFhTWVWK0FCUGcwYVQKakpEa0FXRXkrMXMxV3B2VGFJejNxc2VDRndOVDEyZ0dqU2RJRU5qMUdvMlZ5bEpuZXdlTkJsWk5TcVBwbG4wQVI3OUd1NGhkVUhCdQoxMmdReXUxbnJ3aU93bW1RSitkZ1QzSDhCcXVVTlNEWWhEcGJKUSthZjNRb2JhTUVQVTFodmlYd2VTNWk0L0M3V3c5Z21mM2wyNzZYClYwTWFMYk04a1BPWGs1UXdVeEY0ZldvV0FPc1lkU2xaZzFWV3pWNlovU0FVNWc3eXBRV2UrRFVHQ2xnMVVWS1RuSVpSaDZQTnRKZUYKZ3VJd0FNN3RLUkFJaW94RExUcDVWNGNqUEhUWEdGNldPRTFBd080UnE2YjJRakEvTjdRdWhMc2grTlo3aTMzUFk5S3hxWG04MmVQcgp2bXVOWnZGaUY3Szc0YnN1KzE0NUJndUIzVzdmZis2bDlrSzl1eGhjTmYwaHJjdGRETUs1eGI3blFXSDN3dXQzTVFSSHdyNlhnc0o2CnJ6Snd0TzZHeW5zaDBqQ2YrNXQzUThGZU9CU2ZFeU1zNE51N3BDTkIvU09aaDBYdmxZTGtLUzROUTlZV0dCNFVsb1JQU28rRzA3ZHUKOTVaWXRTak9ENERkS3QyOGNVbkl0bkt1dFFMRk5KbTd6RXBDTEhJZUVWaExueVk1VnpnOXpRTjZkdndLa1NzTjZYU2lJY2x5Qk1ydApVUlV2OGNhVTkza3Z0cVc4ejYvSnI5U3dNUkc1N2t3MlBVOCtjMDNHM3djakk3U1JVb053VFk3M3k3WUZ3R1RQYnRob2o5YUVlZ0FzCllFbFVhL2VJa0FDS1JmU1pHREk1aUhBbGh6NU5NVmdLbUl5dzl4U1RieW1PTUREeGUwMkQwc2dSRjhKRjc4aVU2SEw3V1NwVTMwaDYKRnZRYW9HcTU0OExvM1RTR1JrOW5VRFI2czRFQnJKc3JTbFhRVVRlMzNGNUR4OS9GTGl2b1JCVkRWOWZRS1ZmUUlaMThoeG82V2FTbwpnazYyV2xCbkRaMXlCUjIvV3ZDV0dqcmxDanFCMVhkRERaMXlCWjJnV3ZDR0dqcmxDanFGYWtGZE5YVEtGWFNvV3ZBT05YVEs3OUcxCjFUZlgwRjBJcnFDQ2p2VXNicXloVTY2Z1EvYVllZzJkSUNGWm9jTHMxVTVLV2R2eU9mRHlsVUNpWkJjTlE1S005QmJVRTdkZDJ1cWwKU0pHUGZIVXliRUZzL1VvRUI3UkdlajhLd3QxZTM5THh6dmdLNG9TTnErbVVjQW5ZVlZncnl1N3hEaTNGWVBQZmVVY3RpQ0k4NDFNQwpwcHlIcFcxK0tOYW5Wam1uZVg2aXlOV2x2R2dudWwvL2tHUXFIMVVpVjBwRGtpcVpnM1BSVlRTblNkY1VSYWtnWEc2UHZnS3FrNjVVCkVDcC9URElaUkgvWVdDNW9qRzRHdXpsZTFmT2FsYWRtMEZqc0pzeUUxQnNZUkp3TTF2eldpREZWN0NaM2JDUDBrZz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJMUlyZDVQMVQ1UU1IbnJZc0tpYURDRnhUTlRla0tLcG1FRlFLYXdNbVRNSENMMDlzSnNSZXJmSlJtODRhazdxY1hpU1ZjbTd2SkNDZgpRcW1yeEJUVnZ0VTFWaDJxKy9hQVdGSm4yYXc5ZGhrNlV3c2NycUk3VVpnRS9MWXRtVFI3eFlsVlNkYnNrcTBDNDFsSzRqb3dWYk5MCmN4M2ZXUzBIWGtjZDMwU3Qvc1VzQ0ZySmw2YkpGOEVvbTlLWE9mRDJ4NlZaYmxBWnFSSk4rZlZUellIWG5FVXdLd2tqbUpMcnA3V08KTDNwUXE0bFJZZ1poZGpvRWRyY0swUU4zOGlGWG1hSUhtRHJIYTZlWU1NQjZJOFZVeTBTMVQ1TFRhRmRSVEJqNEpYTGRubGVZNkZRVwpKZFpMVkhKcHMyVXZZcjZYYmhZcjZrM3B1S1Z5dlp3SWhBeUFsVW00STB1QUdCRURIUDR6VXBUdXNnWm5qL1g0Vkp5OVJWbXpzeWNIClFKeWplQVVJdGF0UndEcXp0MnBvSzVXN2FvV0dtUHIrd3BnMDBtSUJTOXpVZ3d3R3haQTByRXFUY2VJdTJGNnBSczZnVXVhczRjZ1QKVFVqNXloUERoWHVsWUVmR2p4ZXAvSTdDU1FQSmVTY2pjcHo4VzlFWDNwRndsU2hMU2FWQTdrTHJ5RmRFblNSMzh5dnNzWXBXaWVmawpYZFllKzYzb0MrL0lsOW1KTTRpdXB0TlNKZEpnRUxrUzhpeWdNeUlqR0pLZ0hoa01pdEFUL2xBWWtsQ1kxWGN4QlRvRnRRNUpPVHRkCnZTaE9hVWppdXp0OG92Z3ZWWkowajRqTUU0ckkzSmlsQml4aGk0YUlESDNHcDFLZVk0bjdiTGRFWkhqKy90UHRFUmtBd0tHUTNhR2oKRE8yYWlNeEZ0ZUR0RVJsWWhpYUt5TWhWcEtyVjV3VjFSV1FrNC94UEdzcHp0Qlhud0JJOUlIRHFuS3d0ZlErMjZzQ0V4bktWTTVibAo4OGExR011QmROdHQxc0FNeWpXSnUrcDlNaG5ndXNRYjl2c0VkY0RVNGc2Vm5GNHRaV2hYSnJEejl4ZFlRQ1lmWGRKUlBuYVJ1aUJaCkxhaFdWNmM3YWZZeVF4WFYxV25NMTFPc3ErT1ZqaXBVQ3FzSFFhdUtsMVR4cEpkbld5cW02cjFlM2tVS3Y5TzQwVEUxNzNLSlNmZXAKaDROWTdwRlRwMXdQcC8vK3NXdnE0YVN5b0YvLzdsMFBkME9kaFk1Nk9LVU0xZnZWdzhHbzlZMFNxS0VlVHVKR1VOa2lydXZyNFFUWgpVTXh2N1BldWh4UGZPMHBWeE4yN0hrNzFsb0M3MU1NSlRxdzBwRnBlVnc4bjlzWGtqblZnSmR2dGxmVndnN3RiVGlRQUpjeUpsTE10CnRlUkVkcmFhY2lMVlpQKzRDdHhxQnVSUmdzVHQxZWdJamtkdDlUWEMwVkpISWd1RnNzY1FuRHRVY2wxa01pamt3S3RyTkZoY0p4OFEKdTh4MzV2V3lrY2w0dHU5ZEZ4blA5cjNLUW9pRVVEYmEwKzNjSW9aYzhSRXZxbkNyR0FKZ2l0ZktTT2t4V1RIczdiU0tvWUkxRHVtdAp6MStVWkxUNzNBYU00T2dUSDVsNlpBRG5Mb1dRQmIrMlhVd1ZqdnlWd0RLM2FjbWxXVjllQ2F4Nkc1b3dLaTI2cmRIcHVZeGh3ZUl6CjVZdWRWQjFxdGlJVk4xMmJJTUticzl6RndsZFZwSTVOUXcxRnBCb3JVc2VtaVphUWdVcXAzMTBxVWovMzk2bEloWER1VVpFSzY4VnUKcjBpRlVPNVJrUXJoYUxzR1d1UVNTK1gxSVFHUnZ6UlZkNUtSMDB1dnZsQU11emVMb2FnVVRscVAzYnNVRHVYeksyYjAzS01VVG5KZAo3bDRLZDMzY1VrZ3haZDlkaDE5NVF5a2MvdzRpVkF6M2o1VENTVVFWL29GU09PbjRtRWIvcktka0dQSlZBZi8rWktYQ3A2VnNycXFXCkcrR0Y5bGplcCtJV2FVMzBncUJDbWlLS0dnTElFSmo4elhZNmJaaUxTNGIxSGk3d3F0RjdHbzl3bEc3bkJhcGNhc3Zqc0tnWE40dnIKNmo1bHI4TVJidzVNZEZSaWUrRGtGM2FOODBqaG85Y0F0YmhNdlN4TDY4NTRXRERQemtVeVpza015RmJxdFpqeG4reEE4NU90ZEtTSAoyb1lYK3NWREpsc090NTd5T2Q4MG44LzVxN0FKd2R1TzJZNnNhK0dRNllpVHNCWkxxdW9NZWtsMGpkS0hmTjFadEJGLzViT1hzTmd0Ck1YMnA4NFBQUEtTaHBjdWFORzdsS3V5Q3ZhNVNzZHZNTDRzVUkxczVwUW83Njh0UGZTeUhkS1NBdE9TTjhaQ0thN0hpN2lNdkNpY3UKZGd0Mmx6VzJNNkdvQk16a1VDcDJ3MzBpcE1KK2ZJay91UXE3c05tOEQ1OC81WXJkUHBTcXp2NlVLK3kyclpZc1VsdHR2cHpKSVowcgo5K05yZE9XUkZwOC9TckxrdFd3Q2lhRWMwb2F3d2c2dEtoQllldGJvRTgzbmNZM3ZKYVRlbzZJS2dqZURuelZORUlQT1YrbzlldXVzCmhTVU0wV2ZHUzBxUjU3VjRRMVdLSFd0SXVSVmFzTzROTlJmQkdkR3JiWHVmMGg3MURGVngxRVMrajlWZXc1QjQ1MklLZzFKTFRsWEwKODZDODEzdDJrcE5xT3lKeHA0cUc2SkpTSnpsZFViaVBndVlVU1pWcVNIUW5wSTY4UHJVbWNvcDVmZHI1U2FXSm5PejhwUHErcVRVYgowVG8vOVY0RG1vbXUxbWRFZEpmYURmM2p0TXRMYVM3ZmlFZzIzVlZyQXpybFNPODExWFRYeFdIMFZ0TkorUUZNTDg3N1ZkTnB2Qi9tCnhtbzZxWmlnV0Y1dXI2YVRxcVc3dHZKUnZwcE9LaFl2bldsL1N6V2RnQ3kwekVyZVAzWlROWjBVS0pVYlFxNm9wcnR5UjlaWlRTZDEKVHNQdWxYZXJwcE9xcGVQNysvZXBwcE9xcGRPWTI2T2ptazRxMXM1MFlMOWZOWjNVNnFKSTcxMnI2YVNNRzM3dTZIMnE2YVJxNldSdQptNytobXU1eVNFdXp2S1YwYlRXZGxIRnFzTjY3bWs1cS9TU3lvVzZzcGhPRFV1MHBmRlUxblp4dGVkOXFPdTBVdTZXYVRnUktmQ1orCnAycTZxeWltdTVwT3NjYnFidFYwMHJYVjk2Nm1rd0lBc055NW1rN3F0RVNVQTMrSGFqb3A5U0QwWHU5UlRhZHlNbktuYWpyMS9lVWUKMVhSU3hPQ3M4WHRWMDZsVmN0Mm5tazZxbGs2Mkg5L3REcUNYY3dBRi9jVjBYZDBrMzhYcnNrbWx1RGVIeHNLbnZZcXdDNGdmM01yMQpzcm0rWDkzRmtFN3dsT2ZlL2Vya3JRdHRkRHE1N2Jyb3hGRkprQWtKYXpKVkRRdU5MQ0FvVUpDcGVwWWJsR2hJV2xXQmh1NXl3b09wCnE0WUVLUVlHcGF2dHN0S1FwQk1wWkRXTUFwMFUyaTdMcUV5NGl3azlvalQrZEJSNVJBRkxiS2ZTMkZ3NU1zZDJ5MUpxZEhkem16dmUKUFZjYVRmSnIydHpKZHBlcmFxcFYwdGptVHE2U1Mxc2huYVlFQ2VYOFpLN1IzVzFyVmVydHRIU1pVWXhTYVdwenB4cFJoSVM1dWMwZApiY01vTjdxN3VjMGRWZnVtMHVoTzIrSFJybnFIZTY0QzZUYXV2SWphNnl5cWQwdFIybFU1ay92cVdoNHd0VWVuVnY2VVBlV0JEZXBVCmN3L1YrQk9XMFVIbldURUNyNm1RVGt0R3MwRzl3bEEraVVOemZTSEFvcEIycUNPSkRGSkhOdlFuNUdSdEhiWUFack5kWEU4RUwwMlYKbmJQVTNWQnk1ejJsMTc4ckM3c0UyVkNBcVZUeXdUVm5Rd0ZRcXZsam1yT2hBREJ0R2QzS2ljMUk5OTJsOHZIMm94RFlVVkRoT25CNgo5VFhDMGRUYlZRb0tMN1ByN1I0U2lLQmNWTEpMVmdxcjE3SWpZSm9LYStWNlFJZ0xhOTgybDRXMWI1djczUW9JZ2QybGtUSGlNVEJhCkxjcU12M2ZKa3JJbFJVclZmcTlLT2RTdXl5TmRXSWwzbVVHdDZJbkw1VkRQajZ1N1ZLYXdOeC9ydHlRdVFkWE85K3BnQ0I2OHloOFIKYTViOXRxNUcxWEtuYjdEeDJzM21CQVZGS2F0VEI1dzczQkpBd2JtMVd6VUZoUkZDcVJzTzllV3F0alVVTXVnNjRYVzZKY1N3bzZHUQpRVk1FL3VZT2QweDlwVnlQdXl2RVVDSzU0dHErMWZvNjNDbFZQdDRnaGd5ajBSM3U3dElwVDdXZVNGdW52RnZyaWRoT2ViZUxvVUtICk8yRmRrcGI2a0dzNjNDbjFlNFU5N3ZSM3VOTjZxem1zZnJxOXNQWnp6NWs1Y3Q2cjVzSmFBRXpkekRGb01YU28zblRYRmRhS0t4OUQKdDhhUFlIMWhYYkljV3Q4OVZ3ak9GWHJ6NG80SUNPY3U5WngxVEpzdnBncEh2aFpXVk1Sa1VMdG1BclhMMDFQZkxsWEV4RDhWaFFyQwpjeUdHY1lmS1FtaUlhekY5M3pTV01Xa3BZckkvRHNTZUtHOFgwMUxHSkp5a1N1eVVBOFd1aTJ3WlU5eWhxVUpkMFcxbnBUTHUwQ0tWCldvcVk3SThic2ZGOXJWL1oxVlhFSkpkQkJIc25LdXkwK2d6RExsM0RlOE90L0NMRFVPS3l4RzZQNmdGeGp4clh2RS8rVmpIb3YranQKU0tlcjNTTWlwYnc5MXJ2ZjFWUTlkUHAycHhyWG51elZWUHB0bU1sUlY3dEhsVTU1L2l0clhIbnl5YVpoYUtoNlZxOXhCVVBTWFBXcwpWT05LUjREZ1F0UWkwdmdZMHc5V3ZKVWZXNFVWbnMvNW45NEw1dm5UbThGYVNMdmZXby9ia1RzTVBwVmVVWFVmMmYwa1o0UTlaU3BRCkd3OEs3ZkppeC9RblFSSGVjN3JKUnlxb2h6TllqK1pPNzVVZnFoTDJZWXZuUDNveTlYQk8rU0s4dzNrVXg0VTZXVlNHaDdua2F2L0MKWmt1azZmeVNLOEw3bEVVSzVtSWlkd0hadVdJbGM3OHRpOVJlbWZnV2NuM1luQnhTdHBLTEkvQnJpT0FoRlphbUhlMFJHOWRITUNDcQpjblE2MGg5ckthUUdLeVN3dU9lY29BeHZMSzc5NDVPM21ueVZRUm8yVzRQUGhSRVhneFZYeEEwVmtKWXNZWG1rcFlhNUw0VVU5WDJMCmxnV2xsV0trVGFVK2d0V09QTkppc1UwS01yc0FXZ3Q4N21FLzBVV1laOWVqWDdqNk11OEY4cGpzZS96NlNtemtpbWMxUUhUSHQ2Y2kKdDAyQ09mZUNZcU9UUGF5aG96M0N6VE92RUNkbU56cU5hWk9BcUNFbk9nMFNkREZUVDV6VXNEY0QySS9pd2liVkNJbDhTZEpsYXBYcwprQ0FueXc4cXI2VzBTYjJ3Nlhpdnppd29vS3ZTbVVWckpLbWduRm9sdTNRWG5Wa0FnamM5S1doS1ZYZWlteWR2cUVwVHlkQ2txR1RRCnhrK2lReSs5ODJPajFyRHVUbmVYUlpraFhXUnBpZVZGTTlGMVpXa1pyRXFEa284VDZ4c1MwSmJ5ZVZyMGVDNXlZMFhxcXZpWmNna1AKcjR1WFBWT3VjeVVBN0lxR3E2OVZZckNENHIwdWIreDVqZHB1T05SUWphUVlYdFo2eWpNbzNuSVRMVmVUZUhGbDNGWEZidEpIMXJydQp0UjRVcjR3c0MyL1JoRldBdDE1Q0szVlhuR3k5bUpyWGNubFhuSUlESkIwZkV5YU5TQnl4amtsZC9RN2tOUXg1Tng5NVRJbzF6TFduCjNsVEJuWndjb3hNcjlmWjFncEdKNC96cUlUdlVqMDgycWhMY1h5UVFyS0lIL2ZhSVRPOG4rMk5YL25CUVh5SFpSVCtMRzlxeUtWY3kKNmFxeGl1bTY1NEEzSkZIOXk2eWtGRHRXdGNZRlErS2w2dE9uMVR5cDFWa0t1TEpvS09YazMzT2xVQXFvMXRKWVpmMjRrL2RaU2IxUQpSek16aVBvYVMzYVowUUZNcll1T1hWeGRxd0JNdEt2Y1JqSFZXaDd0a3hTZVhONUlNZFYrT3Jvb1ZndCt5Z0M3S0JlV3NnNXBpbDFaCkJhaTFCbER6UFNSeUJXS2FhZ0FWc3RNMVZBRnFyUUc4akNmcnFRTFVXZ1BJditGUWZ4V2cxaHBBRkxXK3VncFF3Q29LSHFZNEQxWmYKRmFEV0drQklzZXVyQURsNkt1c05ZWTJWM2lwQXJUV0FySTk4VlJXZ3pKQXVhZ0Q1NTVYVWVDNW5kWHRUUHUwZDJXNXB5c2VieXovWQpsTzhpcXZDUE5PVlQ2OGgybjZaOFZNMklzb0Z5ZTFNK2cxWENLcng3VTc3TC9zai9SRk0rK2Y3STkyektwNmwvNVMxTitmaFJCVGlvCnNPeWdhQktwbFFiTGQvVzcvVzZvcDN2Y0RhV3hyNSttdTZGdTd1dkhtOW9kN29hUzYrdW5MMC9wMnI1K3lsMzlycm9iU3FLdm4zSlUKU05vVDE5L1hUNXI1Rk8rR3VxS3Zuem9uMzZPdm4zSm1DQnU1dXJHdm4xb2wxNTBDdW9wZC9ReFdYUUZkMmI1K3lsTVR4UzZ1N3VzbgpHTWhGVjc4ck9vQkk5dlZUbnBCVWh1bzFmZjBreXhHVmJqWFhUaGpObGJLU2QwRmYwZGRQK2VZdjlrejh4cjUraWh0ZEh1MWlkK2pyCnB4d1cxblVEbFVKZlAyVlhWeklMK29xK2ZsSmxrcHpvM2RhUDd3NDE3N3I2K2lsRGdYSCtlL1QxVXo1UU1XaUdjMHZodTdnaTlkcSsKZmhLbUpxK3JINzg3QTNaRFh6K1JHR0xDcm42Q083dDBsNmRnYkY4LzFiclh1L1QxVSs3cXgzTHlqWDM5bEJPdWVSV3BOL1gxNCtxMwpwSGhSZE0vVjFYMzlsQW1JTE5nNzlQVlRrTFhqS25DbmZud3VaU2lhKy9HcHRKUFQzSS92cHI1K0xCUko4ZEZielhIUjEwK2g3a0UxCjIzYXV0Nitmc2lVUGJ6eTRSMTgvdVQyOGMzbVNlRjM5bGdaem5qbEpWQk5EdGI1K3l1Zk53aXlDNi92NkNha3Q5Zz09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJRnEvdHg2Y3ZTVU8rSDk4ZHhJZnQ2bmQ3UHo0dFJVeHF0emRjNW1yb2E4VW5ya2k5cmErZklMQjkwZFdQcWhpNnZhOGZXeXFsMUZmMAo1cjUreW1hT0RJL3A3dXVuM05YdjVsdWI2TDUrTjhiNk5QYjEwMUQzZW9lK2ZzcGQvWFQzNDd1cUNsZWtMUy83K2wxZkRNL3I2c2ZECmNsTmZ2OHRRTXIrcm45d3RaM3I3K2ltbmQ0bDJzYXY3K2lrbk9vbGlTbGYzOVpOWUYxNVhQK1Y3NGJUMzlicytiaW1rbVA2YUp4bS8KOG9hK2ZwemlrdXJxZDNYMm9LaXZueUpIOERxQTNOYlhUOWt3cEx6WDIvdjZLUmZFMGp2eXpYMzloSk1VZC9XN3NNYzAzMlFsN09zbgpid1JSTm96S1RWWWErL3Bwc21GdTd1dkhYOG5MQXh5RjZsb1orWlR1NjZlOE9TaFdjZDZ0SUpiYTkvMFMyME5meVVaN2FVdG5mSWp2CmxWcFNOeXFLOWtyd3JYeG9Wem54WGxTMktPQW1oc2U0MDV2VTQ0NWZCMC9GcG1oVURzRU82ZHR5aE1IOEwvTXdTODluZ1NkYU84TGEKTitLNE5JSnQ1Y25vL25pMllQN3VLa0s5Qk91cGpKWjZxdUxESEJ1amYvcXhDd1lPeXljeXRmNktOZG91aC9mUGFjL3RjMWg1OFp3MQo3OCs1c1BGelBBdUFUNk9NTlJSTmxHMzFsMzB6dEYvMVBneldTTWdYbkViclpOZFg5UjJNb1ZvcE9NbzFWKzMwN0swZWZsKzI1cytoCmZjY3gvN2E4WjUzQndxK3AxYXl1NHV0a2R6dFBPemI3WVdSdlBXUTI0V2ZqKzNQYWJnazRTbUhyOTBlbDRqd3Z6Ui9CN2VjOFRzcysKcXUxTXRWdHZIYVBQUGNnWWljVys0MDRHN0JtTXpGVnpHUGs5ZmNKS1lWdnpjQmpGSElkajlMMXlOQS9DazJObzh2SEtWbHEyM0k5Kwo3N3N2VUl1Wm1MSzNYLy9oK0xtRnplWmlacXEyK2xLSENPcExFLzNhTUphZE5uYlFvV1piUU1KK2t1Y1JiblZsTTdVbktXSWhZb0RwCm5teHdYUUlXbyt4Y1VVV2pZMnlmdjVvNy9WYktzam40U1JzUlhKek42NWx4Qml0Sm41amlVT1Bac3dzK3czeTErZ2JWSkJxelpLdGwKeFN6ekdmTDNYN2RDazMwbkVoOHd0ZGM4cjJpT2k3ZnlwZ2JVbWhRZFVtRnIvbXl3RnZya3ZJc2FhT2EvU3MxdStEay9NMlg4cCtSVAp4bitjSmNpWWVmVmFmTThFQm9Cc20xUnAzZWwrWnN0aDR3VEE2ZXdac0ZUN1RBZnVYa1ZnS1dOdmtFblVqQWMwcSt6ZkZuSnl0dnIrClBuUVhPOTB5L0FRbS9McjJ3YUpkRCtYV3AwSzlBL0tjTVAvSkFiTVJoaWRLRS90RFBqdnpDWGVnMkRHZ1NHUUQvb3k0VU5VdlVEaVYKSGZqejBZTTBqTmtPZHJHUEEvZ2k3Mk4rU1BySklsR1BBckJsekYzd1dNK2tvMXlwNEI3NzhxODRYZTFqbUQ4VmNhS0J3c0Y5NGE0OApuc2ovalAwNGVQRG81ajBZVy9LUEJpdnpLTy9sSGhHNWZqdk5QS2l3K0twOGZGNUxaY2tRNXRrSlZNWjJoWmRxcGpIN25adDcyV0RGClN3UGZsSDNrNWNFcDdlSVkvTTVQMjBlaFp3eFdHZjNoNVhDTmdIOFNGT3pKd01yQ2JyaXBWeVk3SEZyb0RTOHZCbXNoN0NrSFRMZHEKK0tpWHB1RThoTlBBWUhMWUFaK1dYOUNmTk5ocC93T24xNnBIdXYzMXQxVUFQRzE1MEZQQ0dZN1BHS1F0SDRNRmMxcWlHTEYrc3hZOQpNU2ZnRHF4ajRmdjdJd2Q5TEVjeHcwWDhpQTc1QUloK2l0a2w0QW56eHJWRHhIZ1FjYi81bUhTM0U0ZHdxaDE4emthK1pnNG9kVG5DCjJYOHpJbGFCRk90OUVPekNqd0tXWkhKRURwMGZWY0o1bnEyWXFiOTd1QVVqQ3VWSHlOUHZQb3BFaFg0Rmh4dTVoU2hNWDBMMHAzV2IKb0QrZFB3YTBiVWtVbmVNaC9ZbFloQ0NJRUFWaTJNOUFpSDBmck5mWUVzTjFEYU4rUFR3M2FkZ2paNWZsdGo3Qkc4MG9NZnRGRHhERgora0ZXS2llOHVZemVqWG00MHlSZ21rSXp0L1l0WE5uWDZYZXRVSzBZM3poRndoN201Tm5nZ0kwWGMrQk9Fbk1CNjdreVo4Yno1VWZUCkNOaUlkaENOTm1CTGZJVG9UNFh4RC90ZWdINnY4NWNWaitZbDMvb2lDMnZUTk50c2Zkb04xdUxFbTNtSG0zVVM1YlBSc24vYWhTbFoKRkpvT3drTmdtMEJ2c21ydFVtOHl1eGpVbkNLOUdmOTYyVHFLNzduNlU4NjVQc0JMQ3NMbFhCSHp0UzcxcHJqdHNCMU5IS2kxS0xxaQoxc21lOFFIMVQwK2p1M0pUcTVyckRvNllmMlh5K1g5TXI3YUFKZlozd2pDSDcwanJNYUF0VVNJNGlyZUNQNE5PVk9hTUZ6OHo4TSs0Ckd3YzdSd29xUXVnak13di9DWDdUZ1NxbDdFTjJIVXk5OGFGSUJGUkNNTkpROXN2RWxNcVl4T3JEQjFVSHpIM2R3em9MYkROaUZyVHEKb25TSUoyK2RzTjk1Nk8vYU9PVHBLdUNOU0tlY0g2MnlNNEZXcldGWWFGS0ZDMUhGK2Rvd3R3OGpPM254QW5VZlFORGJvcTBGVHYwegpDNy9ER2ZPc0ZxRXBVU0s5NkJYZ3lUWDM0TSthbjRFTmRTa2NEZENsbkxLbU5CK1lDMUNwWHMyc1JGbDRGeWNNZEhSRjNLQVdOYlIrCjlQL1NjWDdFYU5qY0ZQSnpaaVg2RHQ1YTBNQm0xazBCdkg1dzg4MGNCQ0taZnVNQnFIeDZmRUlBeTFSNzZLTHkrcG9uYUdDMkJSZEoKMUk3d096UDRMbnFtckV4Ym4zamltZHFVMlpWOEtiSUFHcGNBemhRQU1CY0tSTmxXNWx1OWdPZmQ4RVlFdEFiQUVIOG1ZSGtyMkpnOApaVGY4NUdPLzg3UGZnWFh4dk8yaGZYQ2dtQjJ3ZlorM2k5azQyd3YyekI0eEp0WWp3VmhYd0NpYldKcWZ3R0FJbHNROW8ra05BMDZJCnVtNGladVI2UjhOZTVFYnFKb29lc2pQcHZ0M3I5alBmQndGYko3MHVPV2ZWemR4dFVmN3ltWDdiWUFadUVpRDFlWkZZWDE1NlFWZjQKTWhRTFJGNVd0TlZMbkhITzRFWE53cUhiWUdKR3c3c0R4TVVqd2NjaE1PRmRRY0tTd0pudnp3YW93ZzRSd2VjVkVBRVYxMUZFR1BDSgpRQ3dhaUFqd1FaOVBBbEhyY2lEeEhBbTRmdUpDSW95VHp4d1JBaDlyUE1wclp2VjFuUGo0U0duRC8wdVdCT0srMVQ2Tys3dkdyYjlJCkVlR1E2MzFvNHdOME5UOWk3TE9SSTRIQmFnd2VQMndzRWJvS2ZBQXZMR1dvK0NuRlNySUF1RHlsM2s2Wkc5VjRFVWFKNWNZQTVVWEwKTklhSEs2ZkJSRWhRT09LMmFTd2xWNE5iQzBvcWxhYngrcm03amFkZmgzdFdXNHBBYUtNRUFERS9LazhEQUhCWkVDY3pJTkw1L1ZZQQpZbXRVSDRPaWRqcVlFQUJFc2Q0ZkJ3SXIxM3RUL3B1OVQ1Wi9lN3lGeDhpdnNqRG04dnAzTTQraDY4QnY0N0g1NlJZUUFNRGFlRE9QCmRUWTM4bGh2Szhkam1rRk1EdW9NMGhmdzJNVTBsamVTOG5WMTVuanNPaEM5b1FaeDdUTFJIbWtRRTNuZHAyME1jeDRwS1lycFhZM2UKVWwzaStXTUFjN2tZeGVyRzFlaHR6aUpTTWhUVFBvMkRpbXhJalVGUW01RHFuVTIzTVhiZmJGYmZrWW5qSndmQ2FPOHMzL2dnSnBOcgo5d0IyUjU3TTkycVVVQm5EVWtZK3dSaDQybEtKRXBQVjhWWnRPZG1jTk94RUN2dlFaSDlXSG9PNnRweWNqZncvcDJhVDRFK2JtWWwwCmxpa2ZFaDdMeFhoaGI4ZUhLd2hXM3pPY2RZMmUvYTVtOU5TN0w1UVJDSDdEUmNxNjBPTUw4MXd6bEU4eFAyV2l5QzVsWXpnRG5qYzUKdHZ4OVVoRVErMk12Uk5XOVFxOTc2SUcraXBlSlZuNGU0WjlzQUs1c1p6ODVLTWR1Zmx4QmE3eE0rYnZ3NE4rRHZHNzZ6N3pQUjduYgpURTR2OUxCWmR6eVE3dXh5ak5zYmRMREJSQ2MvZUJrN3NjRzlxcHYvb09abXc0WlZyeUNpNkJsRTJCQk1sY1AzRmJBa0Zsa0czd3ZPCkVZWU9VWlFlWGR4My9PaG9LZS9oUFJCRVIwc1ZId3dPdVFEdFB0MlVyLzExL0FXOFUycmk2TTlBdW0wRWY3NW1PTmhET216eFNpSnEKdTRDL01NOGh0d0NlMDBBdm9SWUZWdnZMQWNyTDY0c2Z4alk4QU1IZUF2NXNJN0F1S2lUaWYvMElzSkZKdHp0bFRwcmhVemRBNEFORwpTNi9HaFc1SERKWVJvSEc0N3kzNHgwbGJmTEd4dHcxV01sa00vdkxDNjFUd3ZkUnQ4SS9mbVRONDlyUW9Cc2E0eVV0QmxJSkg1U21wClFHUnV1a1FRblUvUlJpeGFJalAyZWF0UW1UMGFhYSs2MThVWlZtbnhBMzdPOVJEc2xYU0VxRGNPc2xNZkF5eW5HTDFna3hjVUV2SFEKNGZWSjIwOEhlU2RkREovVUtrN3c2UU9uNDFHVEFaR0tWUWl3VnBOeGdQazBDMUlSZUErOXRLc21ndWhGUitUZ3p5NFRsRnQ5WU15bgpBY2R0RThLZXJxWG8wYXhtQWQ2RGdURXdwT09TOWIyVm5jdnFMNHk4dGtBdUdvZ201Z1BuZDM0WkpsOXk2Nmc1S2hWcG9TYkUvVU5sCmJIT2htbFBYeHlLZG9vdzd5Q3luQVlhWDhJd0RmQnJqektjWkZ3S2Uwa0drMDIvd1lqU2hVNGQwT1ZPSlNNZHJxcE9qdWpWR0JVTnQKdzJNRWV5cTVRMGoydjhLMExIWXhNM0pTbGVPRCtxT0RMTVYweEFmMVJ3ZWw3dXRUaXcvcWp3NUNpdW1ORCtxUERsS2NyQzgreUk4TwpadjhyWllqaUVmd0IvZU52bnRmenc4dmhaL0d6ZWZBWUVnWi90b0xqN2Mxc1N4N204OWI4ZjUwSzIrbjViNzQ1UGNRZi9ObTNmS1VTCkRSWG0wKzFzL29DU21VS2pDS3MydkRTMzB2ek5QOUlXaEJoUnNEeFgrSTZWVm1WejgzRmMrTVo2S1o1WVVGYktyclUzZXBhL1piQ2gKdXFjK3RLdCtHRDBFK1daMGxJOGUrR2VQMnVVcElra2VyQ0t0NURJWDVvZmN1ZWl1MTk1RnB3UW9HQk1idFVwZ2V5aSs1K2JOalArNApTUHJMMlg2MTBDYzdieG4vQ2RzeUFVRjRuaW5JSHVDZGVrc2NrZ05ENW1Rek9wUFBRYVBYYisvQXcvSUNtSXVsRVBVYlBjNU1Cbjc3ClpIUTNUTTl3ZnMvd1Fkcm96UVptMEdvb0cxMTdNekRMeHZzb1gxWFlDRGN0Y3R4V1J3Nkh6SEsvYnBHYU5WRG54N2huc1VpaFl3TmEKWDBMeFF5Y1pqUGo1MFo4d3BkRkJmL3FkSStQQVNWc0Q5ajFzSVZOMjAzL0dIVjdLa0tHQ3p2Q0dHQnJFZHp0K1lpUzVnbkduajN3OQpydU1va3p2SU5GaGxqeks1QnplYkNkUXg4SUNQK3g4d0UxQmFIbTBvL0hObWdzR3F2SzNmeDB5Z1F0dWFJVjVwSmdCRGh6VVUvamt6CkFSbzZqS0h3ejVrSllDNnNvZkRQbVFrOGVabitjMmFDd2NvWkNtSXpRVGtqaE01Q3JNWGxOZ2RhL2J1K3lETjBwWmEvd0pYcURJcEEKS2I1M2pjN252b1BXbStmNEsxU3BaYUJMMHkyajgyZWRNbm8zalUrb1MyTndYN0NJM1N0cXdSNTdsS29EUXBOMUlJSGpEalhmZHZSTAo0NzJYU2Z3SU9yakVEelpYdzA1NWYwQmJ1bWx0Q1U5WWVOcVMzc3J0aEQxai9FTExEclZsM3VkQnQwOFRYK2RreGYvVFBma293K1BICkUzZWp0QzJneDRwUFVrWG53Tzk2cm9wV0h6NW9BeXlSQTZkYUtUMzJSWXVBU0sraWJCU2dVdUhJSFBBNFk1VmJtemFrMFBuQ09CdUcKNyt5VjRrN0lHM1ZrakNBRE5FcWQ1dU9sdGlQS1dqRzBTVlJhQkNIRm5uMjBSV0xKK0F0UHlTMEc0THhnbFBMa2FleFNtMEFMQWY1eAo4NnA2MkRSVzdpUVNLSE51OTBXTWVGR2d3WjcvaTFxcVVtbHB4VUZJZERqSXBlVlI1NHRlL3ZuaU5Gb1ZuUzhhalNjSEd6ODd2bDZjCkx3WmRKVit3bkdXTzJ4NjNUT3Ntd1VHb3Q4NGRCUkdPV0M4Z09BZ05tNDNXbktYQWdKamFMazVTYzM0ZUFKenNsbkIwNEVzbk5OQmkKRHpnZG1leituMVM3aUl3RHlHZ3B4T2ZNZDUwOTgxM3Y0UDk1MmJpNVJBdkVjZ0p6bnc2dENNT09Xa04rU0lYQkNkSG5udlQxK3E3TQp1MnZNSEVFaU81bytmVE5ZeFllUTZGYjA2NDhnblZLbmJ6QWtwUG44RGI4NVZMZUs3aGdpWkFJOElxQlRZWm9JUVQ5SEJEaWFGZThJCkV0MmtKMGNDSm9TYWpxODJEQkZjb2lOSTc0MUgwWThuN25oZWU5aVZ4d2ZwK090SncxRTBuV0lvZFJoTkZ4WlRWRVRueDFLc3BIQ2EKVFpkRUFnQmc5VFZ5NHdVSTFOUk4yeGlvMUp4TEVLZ2gxUzNUUUFuOGpFanhqclIxVEtNZDkrdEtETGlVU2svczVMcUZwNkZMN1JZQgpvQ21tbFJMUUEvZXFUZVB5YUo5ZkpJTjdkbmxNY2hvYVNRbFRLSEVFNEdncWxYTnV0cFdId1FvVEh2YThONEhiNzZUZkpIOXh0MFRUCkQ1clB4MFA3alR3R0RCVEhiVHhXQy9vNEFMSThwZ2hpa01GdTVUSDcwbkViajJFYnB3eVBhUVpSYzN0VUdZUVBBQT09DQoJXV0+DQoJPCFbQ0RBVEFbDQoJVFVMSVk0T0k3NWJWQVA3OG8xOEtBT0F4clNCS05aTzZ1RW9Eb05OWW9jR25XZmRKajJIZ2t5WWxIQU82bEZ4MU5ZQ0JxU3J4U21PQQpTY3k3K0cyckFhMWFaVktpL1VWcEdtVXZxVTgyTHNZUXJ1SFFHcjlCTnNycEpxRXloa1BLai9OMjVITHc1R3NuZVNBbUErdHRlOEJrCjRYWXpPN0xpS0JUR3NNUGxtVW8wQmxyMnhhT1lXaUxlbXpoaTZuMzAzWlprQmhQY0pVVmNkZ3lzdGtRSm55SGNnWXhFcnY4WVFoLzEKR3FmQlF0bzk3QmJPNDFJdC8vVVVNOU1oZzFDR2lTUlJJZjZuUDJHZU9sVTZZYkJLRlUvY3UzUUNZcmtzbnRCZE9zR0dRNldQSmRHNgphRHFZRkI5TDhoN3M4Z3YyZ1NpbUR3T296ekNxd1B1MjdXQ1BEWjQ5L0FlTElGdVRBTDFsNENWQnZVS2x6RVAxQURrWm5RTnNrYVRDClB4c3V3ZEVwa0IxSTJ3YnRlZ01tanRQZWhxZU96c2R6a0M4aEt6WDhkQkZGdWdMSjFxREJUaHR0QkRiQWl6S1dod2Y2Z05ucERWTFcKZU12TFA3aExaeEs2ay9BdkRtRllMNTV3bHNrazVTWGREV0tqOXNpZE5ZUk9yNVo1NmFzZlhSaXMyYmV6NWFmWW43M0NrNk1XenZFdgo4WFVlWk9pWVJHOFFZRU9SUVg3QlJEckNzc3E3bDY2YWFPU2hYbm4zTTJ6L2pxR0VoaS9vY3I3alRIVHBIWjBTd05PRTl3RDdLVWlCCktDYit3dURQUGcxeE9FVUwzMmVQOGZzWSt3bm5yejY4VTV5dFNlZ0grTFVVaFpXRmlUTCtocGk1OU1PeVZTcHV1Um9WQTMzQkNWZXYKd3E5UytmSnhTQU0yNXdzazZoZjRwL2NPb3c5Zk9QdUo0TDlYWHdUaGQwRnVORUQybWZFMHlGR25seSs0bzA2U0xOWTdVU2E0K1JmQwo1cE40R0hLb2o0MVQ5S2txeVA4ZjYvc2ZHK3ZiL2xmS2tBQ1RxZUQ0c0xpWjhjOGNEVllyK09adGZqcnY0QXVoWVc2KytOblV4ditaCkh3ejRBL1VmQnY2RC8wWmlEemdSZlNCQ0lmQkhDSDVibXhnYzZOMEgzUGxRQTd2TDBKODluQW8vMDlQUGRqTSsvT2NoRHIvcTFtdnQKU3VFaC9rQzlPd1R2Smg0Y1lEVFlFTHdOSGpuaFFlY1FqSEJvd0I2eTRIL2RmeHZPNEQvczRjV0FQV0QwQU9CLzNiRUJvMGNFM3ZvUAorT01KZlBnRlgvMzdBY2NlNmc4Zlg5akRESXk4MnpRRVlnOWVISC80b3o1Z0QydjBJZW9MRVRFd2t3RDRnTWRDNEl1SFFBVCtPelVFCjRQK3REYmd2R01FSUhQNkJVKzlqR0FFL0FoaFRBNDZBcnRIL0U3NVFNQWJlb2wveDRvR0hBUG9Yd0lxZ0R3QW5BRUE5aVZHL0lCN28KZ1UwTjM0WTJuSjh2QWdrS1VhQVpnYUdqMmMwTTRRZUg4Nkg3emxBQ3ZBZkhoZm5Da1ZnNEdrQWZNSUlnd0ljWWhvZURERmxFTk9LSQpnZEhFaU5HMGlJaHBFVUcwaUZDMGlQQnBFWUVFaUhDMGlOR2t3R2hTNEdKU0VJZ1VCRU1LZ2lNRlF3Q1dGQmhOaWhnOTNiWTBIOUg4CkFjakFYLzlMYWxtSDdHU3BqM0M2MUNlRkNZTVhxQ2xiaDRxVEJvL3BhVk9mRkNZT1FESlRCeDhWSm04ZHlvbE85bkFZVXgrQnZEeGsKejZmdFEzTjhQTTBQUC85Nzduend2NTBPUDVzRkpUMUFvbWJieVh5WXJjU0dMNVBqL1BDditXeFluZjluU0wxMDVBa1pEdDdkYkRjUApnWENBNWk4dHNzYnlFZUE0aW84SW5HWWtQQVlKRytYb2lzY1FZZkVZeFVwNGpNOUw0QytjL2czOFFPQVVMeEVFeFV0RVFFUlNJZ2hKCkN2NmxlQWw4WUhnSlBvbFJ2NEJvNmNIOXR3c1d3V2daZ3RFeWVGUk1FRXJONExTZXdRV0tCa2VhQnYwR0VZVFdNd1N0WndpeG5pR1EKbmlFWVBVTndlb1lJMEVUZ0VRVC9KOFFMVFpuK2lMSHlwVGh0SkdGbzRqd1JrNW82RWl3MGVlcVR3dlFwRVVNRVlFUk1qZ1QvdzRRcwpFS0I1S2tEUVBCWEFJSEVESEhFREdDSnVBS040S29EeGVTb0F3YUxmd0hkeEJBZHhWU0JBY1ZVZ0tDSnJJQVRKQ3Y2bHVBcDhZTGdxClFKRVIvb0loYVNEdzN5OW1BVWJ2QkJpOVE4UkVKQ0VvdlVQUWVvY1E2QjBDNlIzNEc0WWt0T1lKMEpvbklOWThBYVI1QW96bUNYQ2EKSnhDa3ljQWpDZkZQQ0JxYU5QMFJad1ZOY2VKSTBJaVlVTkNrSjQ4RUxFQXdvcVpFQUVyVUVBa1lVWk1qd3Y5dFVhdmxLRnNXV0xiSQo0dlFDLzlyNk9sN01XNGZ4enhwWXRvdmorRi96aC9GbXN6Mk5UL01kZVBLd09NeVBwKzFoL25CY2J2OE52d0UvWVY0SE52SUxhZmcvCndrbmVyZz09DQoJXV0+DQo8L2k6cGdmPg0KPC9zdmc+DQo=); -webkit-background-size: cover; -moz-background-size: cover; background-size: cover; position: relative; display: inline-block; vertical-align: top; overflow: hidden; vertical-align: middle; width: 1.3rem; height: 1.3rem; } #download-btn { display: none; } /* Content */ #content { width: 100%; -moz-box-sizing: border-box; box-sizing: border-box; padding: 20px; } /* Code */ #content section.code { display: none; background: #FFF; border: 1px solid #E0E0E0; -moz-box-sizing: border-box; box-sizing: border-box; padding: 15px; font-size: 12px; -moz-border-radius: 1px; -webkit-border-radius: 1px; border-radius: 1px; font-weight: 400; } article.component { padding: 0 0 10px; } #content section.code h3 { margin: 0; font-size: 12px; color: #000; font-weight: 400; } #content header h2 { font-weight: 300; margin: 10px 0 25px; font-size: 20px; position: relative; display: inline-block; padding-right: 10px; } body.light #content header h2 { background: #F4F4F4; } body.dark #content header h2 { background: #4A4D4E; } #content header{ position: relative; } #content header:before { content: ''; width: 100%; display: block; position: absolute; left: 0; top: 23px; } body.light #content header:before { border-bottom: 1px solid #E0E0E0; } body.dark #content header:before { border-bottom: 1px solid #58595A; } #content pre { padding: 0; margin: 2px 0 10px; } .showcode { margin: 10px 0; } .showcode a, section.examples a { color: #288edf; text-decoration: none; } .showcode a:hover, section.examples a:hover { text-decoration: underline; } section.examples ul { margin: 0 0 20px; padding: 0 0 0 20px; } section.examples h4 { margin-bottom: 5px; } section.examples li { color: #58595A; } /* Side Nav */ #sideNav { background: #4A4D4E; position: absolute; width: 100%; z-index: 1; height: 100%; left: 0; } #sideNav ul { list-style: none; margin: 0; padding: 0; } #sideNav li a { color: #F0F1F1; display: block; height: 46px; font-size: 16px; -moz-box-sizing: border-box; box-sizing: border-box; padding: 12px 0 0 20px; text-decoration: none; } #sideNav nav.site, #sideNav .combo { border-bottom: 1px solid #58595A; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; display: block; } #pageNav li { border-bottom: 1px solid #58595A; } select.docNav { -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; background: #595B5B; background-image: none; box-shadow: 0 0 0 1px #303233; border: none; border-top: 2px solid #666767; color: #FFF; text-shadow: 0 -1px 0 #000; overflow: hidden; font-size: 14px; -moz-box-sizing: border-box; box-sizing: border-box; padding: 20px; -webkit-appearance: none; -moz-appearance: button; } @media screen and (min-width: 650px) { #site.open { transform: translate3d(0, 0, 0); -webkit-transform: translate3d(0, 0, 0); } #main-header nav { display: inline-block; position: absolute; right: 0; top: 40px; } #main-header ul { list-style: none; } #main-header nav li { display: inline-block; margin: 0 18px; } #main-header nav li#download-btn { display: none; } #main-header nav li a { text-decoration: none; font-size: 20px; color: #7F7F7F; } #main-header nav li.selected a { color: #373435; } #slide-menu-button{ display: none; } #main-header hgroup { text-align: left; position: absolute; display: inline-block; top: 24px; } #main-header hgroup h1 { font-size: 60px; } #main-header hgroup p { font-size: 15px; } #main-header { color: #373435; background: #fff; height: 148px; } #content { padding-left: 240px; } /* Side Nav */ #sideNav { background: transparent; width: 220px; z-index: 20; left: 10px; top: 150px; height: auto; } #sideNav nav.site { display: none; } #sideNav .combo { border-bottom: none; padding: 36px 0; } #sideNav li a { padding: 12px 0 0 10px; } body.light #sideNav li a { color: #797B7B; } body.light #pageNav li { border-bottom: 1px solid #E0E0E0; } select.docNav { background: #595B5B; box-shadow: 0 0 0 1px #303233; border: none; border-top: 2px solid #666767; color: #FFF; text-shadow: 0 -1px 0 #000; padding: 3px 20px 4px 8px; -webkit-appearance: none; } body.light select.docNav { box-shadow: 0 0 0 1px #949696; background: #DDE1E1; border-top: 1px solid #FFF; color: #454545; text-shadow: 0 -1px 0 #FFF; width: 192px; } } @media screen and (min-width: 880px) { #content { padding-left: 300px; } #sideNav li a { display: block; height: 60px; padding: 22px 0 0 10px; text-decoration: none; } #content header h2 { font-size: 28px; } #content header:before { top: 30px; } section.code div { display: inline-block; width: 48%; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; } section.code div:first-child { padding-right: 5px; } section.code div:last-child { padding-left: 5px; } .max-width { max-width: 1180px; position: relative; margin: 0 auto; } header#main-header .max-width { top: -10px; } #main-header nav li a { font-size: 22px; } #main-header nav { display: inline-block; } #main-header nav li { margin: 0 25px; } #main-header nav li:last-child { margin-right: 0; } } @media screen and (min-width: 940px) { #main-header nav li#download-btn { display: inline-block; } #main-header nav li a#download-btn{ position:relative; top: -15px; display:inline-block; box-sizing:border-box; -moz-box-sizing:border-box; background-clip:padding-box; font:inherit; background:transparent; -webkit-user-select:none; -moz-user-select:none; user-select:none; text-overflow:ellipsis; white-space:nowrap; overflow:hidden; font-size:16px; line-height:3rem; letter-spacing:1px; color:#454545; text-shadow:0 1px #fff; vertical-align:top; background-color:#e5e9e8; box-shadow:inset 0 1px #fff; border:1px solid #a5a8a8; border-radius:6px; margin:0; padding:0 1.25rem; } #main-header nav li a#download-btn, #main-header nav li a#download-btn:hover { border:1px solid #143250; background-color:#288edf; box-shadow:inset 0 1px rgba(255,255,255,0.36); color:#fff; font-weight:500; text-shadow:0 -1px rgba(0,0,0,0.36); } #main-header nav li a#download-btn:hover { background-color:#2f9cf3; } #main-header nav li a#download-btn:active, #main-header nav li a#download-btn.is-active { background-color:#0380e8; box-shadow:inset 0 1px rgba(0,0,0,0.12); } #main-header nav li a#download-btn:disabled, #main-header nav li a#download-btn.is-disabled { opacity:.3; cursor:default; pointer-events:none; } } ================================================ FILE: doc/css/prism.css ================================================ /** * prism.js Twilight theme * Based (more or less) on the Twilight theme originally of Textmate fame. * @author Remy Bach */ code[class*="language-"], pre[class*="language-"] { color: white; direction: ltr; font-family: source-code-pro, Consolas, Monaco, 'Andale Mono', monospace; text-align: left; text-shadow: 0 -.1em .2em black; white-space: pre; word-spacing: normal; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } pre[class*="language-"], :not(pre) > code[class*="language-"] { background:#181818; } /* Code blocks */ pre[class*="language-"] { margin: .5em 0; overflow: auto; } pre[class*="language-"]::selection { /* Safari */ background:hsl(200, 4%, 16%); /* #282A2B */ } pre[class*="language-"]::selection { /* Firefox */ background:hsl(200, 4%, 16%); /* #282A2B */ } /* Inline code */ :not(pre) > code[class*="language-"] { border-radius: .3em; border: .13em solid hsl(0,0%,33%); /* #545454 */ box-shadow: 1px 1px .3em -.1em black inset; padding: .15em .2em .05em; } .token.comment, .token.prolog, .token.doctype, .token.cdata { color: hsl(0, 0%, 47%); /* #777777 */ } .token.punctuation { opacity: .7; } .namespace { opacity: .7; } .token.tag, .token.boolean, .token.number { color: hsl(14, 58%, 55%); /* #CF6A4C */ } .token.keyword, .token.property, .token.selector { color:hsl(53, 89%, 79%); /* #F9EE98 */ } .token.attr-name, .token.attr-value, .token.string, .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color:hsl(76, 21%, 52%); /* #8F9D6A */ } .token.atrule { color:hsl(218, 22%, 55%); /* #7587A6 */ } .token.regex, .token.important { color: hsl(42, 75%, 65%); /* #E9C062 */ } .token.important { font-weight: bold; } .token.entity { cursor: help; } pre[data-line] { padding: 1em 0 1em 3em; position: relative; } /* Markup */ .language-markup .token.tag, .language-markup .token.attr-name, .language-markup .token.punctuation { color: hsl(33, 33%, 52%); /* #AC885B */ } /* Text Selection colour */ ::selection { background: hsla(0,0%,93%,0.15); /* #EDEDED */ } ::-moz-selection { background: hsla(0,0%,93%,0.15); /* #EDEDED */ } /* Make the tokens sit above the line highlight so the colours don't look faded. */ .token { position:relative; z-index:1; } .line-highlight { background: -moz-linear-gradient(left, hsla(0, 0%, 33%,.1) 70%, hsla(0, 0%, 33%,0)); /* #545454 */ background: -o-linear-gradient(left, hsla(0, 0%, 33%,.1) 70%, hsla(0, 0%, 33%,0)); /* #545454 */ background: -webkit-linear-gradient(left, hsla(0, 0%, 33%,.1) 70%, hsla(0, 0%, 33%,0)); /* #545454 */ background: hsla(0, 0%, 33%, 0.25); /* #545454 */ background: linear-gradient(left, hsla(0, 0%, 33%,.1) 70%, hsla(0, 0%, 33%,0)); /* #545454 */ border-bottom:1px dashed hsl(0, 0%, 33%); /* #545454 */ border-top:1px dashed hsl(0, 0%, 33%); /* #545454 */ left: 0; line-height: inherit; margin-top: 0.75em; /* Same as .prism’s padding-top */ padding: inherit 0; pointer-events: none; position: absolute; right: 0; white-space: pre; z-index:0; } .line-highlight:before, .line-highlight[data-end]:after { background-color: hsl(215, 15%, 59%); /* #8794A6 */ border-radius: 999px; box-shadow: 0 1px white; color: hsl(24, 20%, 95%); /* #F5F2F0 */ content: attr(data-start); font: bold 65%/1.5 sans-serif; left: .6em; min-width: 1em; padding: 0 .5em; position: absolute; text-align: center; text-shadow: none; top: .4em; vertical-align: .3em; } .line-highlight[data-end]:after { bottom: .4em; content: attr(data-end); top: auto; } ================================================ FILE: doc/css/topcoat-desktop-light.css ================================================ /** * * Copyright 2012 Adobe Systems 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. * */ .button-bar { display: table; table-layout: fixed; white-space: nowrap; margin: 0; padding: 0; } .button-bar__item { display: table-cell; width: auto; border-radius: 0; } .button-bar__item > input { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .button-bar__button { border-radius: inherit; } .button-bar__item:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .button, .topcoat-button, .topcoat-button--quiet, .topcoat-button--large, .topcoat-button--large--quiet, .topcoat-button--cta, .topcoat-button--large--cta, .topcoat-button-bar__button, .topcoat-button-bar__button--large { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none; } .button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .button--disabled, .topcoat-button:disabled, .topcoat-button--quiet:disabled, .topcoat-button--large:disabled, .topcoat-button--large--quiet:disabled, .topcoat-button--cta:disabled, .topcoat-button--large--cta:disabled, .topcoat-button-bar__button:disabled, .topcoat-button-bar__button--large:disabled { opacity: 0.3; cursor: default; pointer-events: none; } .topcoat-button, .topcoat-button--quiet, .topcoat-button--large, .topcoat-button--large--quiet, .topcoat-button--cta, .topcoat-button--large--cta, .topcoat-button-bar__button, .topcoat-button-bar__button--large { padding: 0 1.25rem; font-size: 16px; line-height: 3rem; letter-spacing: 1px; color: #454545; text-shadow: 0 1px #fff; vertical-align: top; background-color: #e5e9e8; box-shadow: inset 0 1px #fff; border: 1px solid #a5a8a8; border-radius: 6px; } .topcoat-button:hover, .topcoat-button--quiet:hover, .topcoat-button--large:hover, .topcoat-button--large--quiet:hover, .topcoat-button-bar__button:hover, .topcoat-button-bar__button--large:hover { background-color: #edf1f1; } .topcoat-button:active, .topcoat-button--large:active, .topcoat-button-bar__button:active, .topcoat-button-bar__button--large:active, :checked + .topcoat-button-bar__button { background-color: #d3d7d7; box-shadow: inset 0 1px rgba(0,0,0,0.12); } .topcoat-button:focus, .topcoat-button--quiet:focus, .topcoat-button--large:focus, .topcoat-button--large--quiet:focus, .topcoat-button--cta:focus, .topcoat-button--large--cta:focus, .topcoat-button-bar__button:focus, .topcoat-button-bar__button--large:focus { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; outline: 0; } .topcoat-button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .topcoat-button--quiet:hover, .topcoat-button--large--quiet:hover { text-shadow: 0 1px #fff; border: 1px solid #a5a8a8; box-shadow: inset 0 1px #fff; } .topcoat-button--quiet:active, .topcoat-button--large--quiet:active { color: #454545; text-shadow: 0 1px #fff; background-color: #d3d7d7; border: 1px solid #a5a8a8; box-shadow: inset 0 1px rgba(0,0,0,0.12); } .topcoat-button--large, .topcoat-button--large--quiet, .topcoat-button-bar__button--large { font-size: 1.3rem; font-weight: 400; line-height: 4.375rem; padding: 0 1.25rem; } .topcoat-button--large--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .topcoat-button--cta, .topcoat-button--large--cta { border: 1px solid #143250; background-color: #288edf; box-shadow: inset 0 1px rgba(255,255,255,0.36); color: #fff; font-weight: 500; text-shadow: 0 -1px rgba(0,0,0,0.36); } .topcoat-button--cta:hover, .topcoat-button--large--cta:hover { background-color: #509bef; } .topcoat-button--cta:active, .topcoat-button--large--cta:active { background-color: #0380e8; box-shadow: inset 0 1px rgba(0,0,0,0.12); } .topcoat-button--large--cta { font-size: 1.3rem; font-weight: 400; line-height: 4.375rem; padding: 0 1.25rem; } .button-bar, .topcoat-button-bar { display: table; table-layout: fixed; white-space: nowrap; margin: 0; padding: 0; } .button-bar__item, .topcoat-button-bar__item { display: table-cell; width: auto; border-radius: 0; } .button-bar__item > input, .topcoat-button-bar__item > input { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .button-bar__button { border-radius: inherit; } .button-bar__item:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Button Bar description: Component of grouped buttons modifiers: :disabled: Disabled state markup:
examples: mobile button bar: http://codepen.io/Topcoat/pen/kdKyg tags: - desktop - light - dark - mobile - button - group - bar */ .topcoat-button-bar > .topcoat-button-bar__item:first-child { border-top-left-radius: 6px; border-bottom-left-radius: 6px; } .topcoat-button-bar > .topcoat-button-bar__item:last-child { border-top-right-radius: 6px; border-bottom-right-radius: 6px; } .topcoat-button-bar__item:first-child > .topcoat-button-bar__button, .topcoat-button-bar__item:first-child > .topcoat-button-bar__button--large { border-right: none; } .topcoat-button-bar__item:last-child > .topcoat-button-bar__button, .topcoat-button-bar__item:last-child > .topcoat-button-bar__button--large { border-left: none; } .topcoat-button-bar__button { border-radius: inherit; } .topcoat-button-bar__button:focus, .topcoat-button-bar__button--large:focus { z-index: 1; } /* topdoc name: Large Button Bar description: A button bar, only larger modifiers: :disabled: Disabled state markup:
tags: - desktop - light - dark - mobile - button - group - bar - large */ .topcoat-button-bar__button--large { border-radius: inherit; } /** * * Copyright 2012 Adobe Systems 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. * */ .button { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none; } .button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .button--disabled { opacity: 0.3; cursor: default; pointer-events: none; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .button, .topcoat-button, .topcoat-button--quiet, .topcoat-button--large, .topcoat-button--large--quiet, .topcoat-button--cta, .topcoat-button--large--cta { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none; } .button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .button--disabled, .topcoat-button:disabled, .topcoat-button--quiet:disabled, .topcoat-button--large:disabled, .topcoat-button--large--quiet:disabled, .topcoat-button--cta:disabled, .topcoat-button--large--cta:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Button description: A simple button modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: examples: mobile button: http://codepen.io/Topcoat/pen/DpKtf tags: - desktop - light - mobile - button */ .topcoat-button, .topcoat-button--quiet, .topcoat-button--large, .topcoat-button--large--quiet, .topcoat-button--cta, .topcoat-button--large--cta { padding: 0 1.25rem; font-size: 16px; line-height: 3rem; letter-spacing: 1px; color: #454545; text-shadow: 0 1px #fff; vertical-align: top; background-color: #e5e9e8; box-shadow: inset 0 1px #fff; border: 1px solid #a5a8a8; border-radius: 6px; } .topcoat-button:hover, .topcoat-button--quiet:hover, .topcoat-button--large:hover, .topcoat-button--large--quiet:hover { background-color: #edf1f1; } .topcoat-button:active, .topcoat-button--large:active { background-color: #d3d7d7; box-shadow: inset 0 1px rgba(0,0,0,0.12); } .topcoat-button:focus, .topcoat-button--quiet:focus, .topcoat-button--large:focus, .topcoat-button--large--quiet:focus, .topcoat-button--cta:focus, .topcoat-button--large--cta:focus { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; outline: 0; } /* topdoc name: Quiet Button description: A simple, yet quiet button modifiers: :active: Quiet button active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - quiet */ .topcoat-button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .topcoat-button--quiet:hover, .topcoat-button--large--quiet:hover { text-shadow: 0 1px #fff; border: 1px solid #a5a8a8; box-shadow: inset 0 1px #fff; } .topcoat-button--quiet:active, .topcoat-button--large--quiet:active { color: #454545; text-shadow: 0 1px #fff; background-color: #d3d7d7; border: 1px solid #a5a8a8; box-shadow: inset 0 1px rgba(0,0,0,0.12); } /* topdoc name: Large Button description: A big ol button modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - large */ .topcoat-button--large, .topcoat-button--large--quiet { font-size: 1.3rem; font-weight: 400; line-height: 4.375rem; padding: 0 1.25rem; } /* topdoc name: Large Quiet Button description: A large, yet quiet button modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - large - quiet */ .topcoat-button--large--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } /* topdoc name: Call To Action Button description: A CALL TO ARMS, er, ACTION! modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - call to action */ .topcoat-button--cta, .topcoat-button--large--cta { border: 1px solid #143250; background-color: #288edf; box-shadow: inset 0 1px rgba(255,255,255,0.36); color: #fff; font-weight: 500; text-shadow: 0 -1px rgba(0,0,0,0.36); } .topcoat-button--cta:hover, .topcoat-button--large--cta:hover { background-color: #509bef; } .topcoat-button--cta:active, .topcoat-button--large--cta:active { background-color: #0380e8; box-shadow: inset 0 1px rgba(0,0,0,0.12); } /* topdoc name: Large Call To Action Button description: Like call to action, but bigger modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - large - call to action */ .topcoat-button--large--cta { font-size: 1.3rem; font-weight: 400; line-height: 4.375rem; padding: 0 1.25rem; } /** * * Copyright 2012 Adobe Systems 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. * */ input[type="checkbox"] { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .checkbox { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox__label { position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox--disabled { opacity: 0.3; cursor: default; pointer-events: none; } .checkbox:before, .checkbox:after { content: ''; position: absolute; } .checkbox:before { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ input[type="checkbox"] { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .checkbox, .topcoat-checkbox__checkmark { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox__label, .topcoat-checkbox { position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox--disabled, input[type="checkbox"]:disabled + .topcoat-checkbox__checkmark { opacity: 0.3; cursor: default; pointer-events: none; } .checkbox:before, .checkbox:after, .topcoat-checkbox__checkmark:before, .topcoat-checkbox__checkmark:after { content: ''; position: absolute; } .checkbox:before, .topcoat-checkbox__checkmark:before { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } /* topdoc name: Checkbox description: Default skin for Topcoat checkbox modifiers: :focus: Focus state :disabled: Disabled state markup:

examples: mobile checkbox: http://codepen.io/Topcoat/pen/piHcs tags: - desktop - light - mobile - checkbox */ .topcoat-checkbox__checkmark { height: 2rem; } input[type="checkbox"] { height: 2rem; width: 2rem; margin-top: 0; margin-right: -2rem; margin-bottom: -2rem; margin-left: 0; } input[type="checkbox"]:checked + .topcoat-checkbox__checkmark:after { opacity: 1; } .topcoat-checkbox { line-height: 2rem; } .topcoat-checkbox__checkmark:before { width: 2rem; height: 2rem; background: #e5e9e8; border: 1px solid #a5a8a8; border-radius: 3px; box-shadow: inset 0 1px #fff; } .topcoat-checkbox__checkmark { width: 2rem; height: 2rem; } .topcoat-checkbox__checkmark:after { top: 1px; left: 2px; opacity: 0; width: 28px; height: 11px; background: transparent; border: 7px solid #666; border-width: 7px; border-top: none; border-right: none; border-radius: 2px; -webkit-transform: rotate(-50deg); -ms-transform: rotate(-50deg); transform: rotate(-50deg); } input[type="checkbox"]:focus + .topcoat-checkbox__checkmark:before { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .button, .topcoat-icon-button, .topcoat-icon-button--quiet, .topcoat-icon-button--large, .topcoat-icon-button--large--quiet { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none; } .button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .button--disabled, .topcoat-icon-button:disabled, .topcoat-icon-button--quiet:disabled, .topcoat-icon-button--large:disabled, .topcoat-icon-button--large--quiet:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Icon Button description: Like button, but it has an icon. modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - icon */ .topcoat-icon-button, .topcoat-icon-button--quiet, .topcoat-icon-button--large, .topcoat-icon-button--large--quiet { padding: 0 0.75rem; line-height: 3rem; letter-spacing: 1px; color: #454545; text-shadow: 0 1px #fff; vertical-align: baseline; background-color: #e5e9e8; box-shadow: inset 0 1px #fff; border: 1px solid #a5a8a8; border-radius: 6px; } .topcoat-icon-button:hover, .topcoat-icon-button--quiet:hover, .topcoat-icon-button--large:hover, .topcoat-icon-button--large--quiet:hover { background-color: #edf1f1; } .topcoat-icon-button:active { background-color: #d3d7d7; box-shadow: inset 0 1px rgba(0,0,0,0.12); } .topcoat-icon-button:focus, .topcoat-icon-button--quiet:focus, .topcoat-icon-button--quiet:hover:focus, .topcoat-icon-button--large:focus, .topcoat-icon-button--large--quiet:focus, .topcoat-icon-button--large--quiet:hover:focus { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; outline: 0; } /* topdoc name: Quiet Icon Button description: Like quiet button, but it has an icon. modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - icon - quiet */ .topcoat-icon-button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .topcoat-icon-button--quiet:hover, .topcoat-icon-button--large--quiet:hover { text-shadow: 0 1px #fff; border: 1px solid #a5a8a8; box-shadow: inset 0 1px #fff; } .topcoat-icon-button--quiet:active, .topcoat-icon-button--large--quiet:active { color: #454545; text-shadow: 0 1px #fff; background-color: #d3d7d7; border: 1px solid #a5a8a8; box-shadow: inset 0 1px rgba(0,0,0,0.12); } /* topdoc name: Large Icon Button description: Like large button, but it has an icon. modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: tags: - desktop - light - mobile - button - icon - large */ .topcoat-icon-button--large, .topcoat-icon-button--large--quiet { width: 4.375rem; height: 4.375rem; line-height: 4.375rem; } .topcoat-icon-button--large:active { background-color: #d3d7d7; box-shadow: inset 0 1px rgba(0,0,0,0.12); } /* topdoc name: Large Quiet Icon Button description: Like large button, but it has an icon and this one is quiet. modifiers: :active: Active state :disabled: Disabled state :hover: Hover state markup: tags: - desktop - light - mobile - button - icon - large - quiet */ .topcoat-icon-button--large--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .topcoat-icon, .topcoat-icon--large { position: relative; display: inline-block; vertical-align: top; overflow: hidden; width: 1.62rem; height: 1.62rem; vertical-align: middle; top: -1px; } .topcoat-icon--large { width: 2.499999998125rem; height: 2.499999998125rem; top: -2px; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .input { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; vertical-align: top; outline: none; } .input:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .list { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; overflow: auto; -webkit-overflow-scrolling: touch; } .list__header { margin: 0; } .list__container { padding: 0; margin: 0; list-style-type: none; } .list__item { margin: 0; padding: 0; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .list, .topcoat-list { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; overflow: auto; -webkit-overflow-scrolling: touch; } .list__header, .topcoat-list__header { margin: 0; } .list__container, .topcoat-list__container { padding: 0; margin: 0; list-style-type: none; } .list__item, .topcoat-list__item { margin: 0; padding: 0; } /* topdoc name: List description: Topcoat default list skin markup:

Category

  • Item
  • Item
  • Item
tags: - mobile - list */ .topcoat-list { border-top: 1px solid #bcbfbf; border-bottom: 1px solid #eff1f1; background-color: #dfe2e2; } .topcoat-list__header { padding: 4px 20px; font-size: 0.9em; font-weight: 400; background-color: #cccfcf; color: #656565; text-shadow: 0 1px 0 rgba(255,255,255,0.5); border-top: 1px solid rgba(255,255,255,0.5); border-bottom: 1px solid rgba(255,255,255,0.23); } .topcoat-list__container { border-top: 1px solid #bcbfbf; color: #454545; } .topcoat-list__item { padding: 1.25rem; border-top: 1px solid #eff1f1; border-bottom: 1px solid #bcbfbf; } .topcoat-list__item:first-child { border-top: 1px solid rgba(0,0,0,0.05); } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .navigation-bar { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; white-space: nowrap; overflow: hidden; word-spacing: 0; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .navigation-bar__item { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; position: relative; display: inline-block; vertical-align: top; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; } .navigation-bar__title { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .navigation-bar, .topcoat-navigation-bar { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; white-space: nowrap; overflow: hidden; word-spacing: 0; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .navigation-bar__item, .topcoat-navigation-bar__item { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; position: relative; display: inline-block; vertical-align: top; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; } .navigation-bar__title, .topcoat-navigation-bar__title { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } /* topdoc name: Navigation Bar description: A place where navigation goes to drink markup:

Header

tags: - desktop - light - mobile - navigation - bar */ .topcoat-navigation-bar { height: 4.375rem; padding-left: 1rem; padding-right: 1rem; background: #e5e9e8; color: #000; box-shadow: inset 0 -1px #b9bcbc, 0 1px #d4d6d6; } .topcoat-navigation-bar__item { margin: 0; line-height: 4.375rem; vertical-align: top; } .topcoat-navigation-bar__title { font-size: 1.3rem; font-weight: 400; color: #000; } /* Copyright 2012 Adobe Systems 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. */ /** * * Copyright 2012 Adobe Systems 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. * */ .notification { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none; } /* Copyright 2012 Adobe Systems 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. */ /** * * Copyright 2012 Adobe Systems 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. * */ .notification, .topcoat-notification { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none; } /* topdoc name: Notification description: Notification badge markup: 1 tags: - desktop - light - mobile - notification */ .topcoat-notification { padding: 0.15em 0.5em 0.2em; border-radius: 2px; background-color: #ec514e; color: #fff; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ input[type="radio"] { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .radio-button { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .radio-button__label { position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .radio-button:before, .radio-button:after { content: ''; position: absolute; border-radius: 100%; } .radio-button:after { top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } .radio-button:before { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } .radio-button--disabled { opacity: 0.3; cursor: default; pointer-events: none; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ input[type="radio"] { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .radio-button, .topcoat-radio-button__checkmark { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .radio-button__label, .topcoat-radio-button { position: relative; display: inline-block; vertical-align: top; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .radio-button:before, .radio-button:after, .topcoat-radio-button__checkmark:before, .topcoat-radio-button__checkmark:after { content: ''; position: absolute; border-radius: 100%; } .radio-button:after, .topcoat-radio-button__checkmark:after { top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } .radio-button:before, .topcoat-radio-button__checkmark:before { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } .radio-button--disabled, input[type="radio"]:disabled + .topcoat-radio-button__checkmark { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Radio Button description: A button that can play music, but usually just plays ads. modifiers: markup:





examples: Mobile Radio Button: http://codepen.io/Topcoat/pen/HDcJj tags: - desktop - light - mobile - Radio */ input[type="radio"] { height: 1.875rem; width: 1.875rem; margin-top: 0; margin-right: -1.875rem; margin-bottom: -1.875rem; margin-left: 0; } input[type="radio"]:checked + .topcoat-radio-button__checkmark:after { opacity: 1; } .topcoat-radio-button { color: #454545; line-height: 1.875rem; } .topcoat-radio-button__checkmark:before { width: 1.875rem; height: 1.875rem; background: #e5e9e8; border: 1px solid #a5a8a8; box-shadow: inset 0 1px #fff; } .topcoat-radio-button__checkmark { position: relative; width: 1.875rem; height: 1.875rem; } .topcoat-radio-button__checkmark:after { opacity: 0; width: 0.875rem; height: 0.875rem; background: #666; border: 1px solid rgba(0,0,0,0.1); box-shadow: 0 1px rgba(255,255,255,0.5); -webkit-transform: none; -ms-transform: none; transform: none; top: 7px; left: 7px; } input[type="radio"]:focus + .topcoat-radio-button__checkmark:before { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } /* Copyright 2012 Adobe Systems 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. */ /* Copyright 2012 Adobe Systems 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. */ .range { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; vertical-align: top; outline: none; -webkit-appearance: none; } .range__thumb { cursor: pointer; } .range__thumb--webkit { cursor: pointer; -webkit-appearance: none; } .range:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* Copyright 2012 Adobe Systems 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. */ /* Copyright 2012 Adobe Systems 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. */ .range, .topcoat-range { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; vertical-align: top; outline: none; -webkit-appearance: none; } .range__thumb, .topcoat-range::-moz-range-thumb { cursor: pointer; } .range__thumb--webkit, .topcoat-range::-webkit-slider-thumb { cursor: pointer; -webkit-appearance: none; } .range:disabled, .topcoat-range:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Range description: Range input modifiers: :active: Active state :disabled: Disabled state :hover: Hover state :focus: Focused markup: examples: mobile range: http://codepen.io/Topcoat/pen/BskEn tags: - desktop - mobile - range */ .topcoat-range { border-radius: 6px; border: 1px solid #a5a8a8; background-color: #d3d7d7; height: 1rem; border-radius: 30px; } .topcoat-range::-moz-range-track { border-radius: 6px; border: 1px solid #a5a8a8; background-color: #d3d7d7; height: 1rem; border-radius: 30px; } .topcoat-range::-webkit-slider-thumb { height: 3rem; width: 2rem; background-color: #e5e9e8; border: 1px solid #a5a8a8; border-radius: 6px; box-shadow: inset 0 1px #fff; } .topcoat-range::-moz-range-thumb { height: 3rem; width: 2rem; background-color: #e5e9e8; border: 1px solid #a5a8a8; border-radius: 6px; box-shadow: inset 0 1px #fff; } .topcoat-range:focus::-webkit-slider-thumb { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } .topcoat-range:focus::-moz-range-thumb { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .search-input { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; vertical-align: top; outline: none; -webkit-appearance: none; } input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } .search-input:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .search-input, .topcoat-search-input, .topcoat-search-input--large { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; vertical-align: top; outline: none; -webkit-appearance: none; } input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } .search-input:disabled, .topcoat-search-input:disabled, .topcoat-search-input--large:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Search Input description: A text input designed for searching. modifiers: :disabled: Disabled state markup: tags: - desktop - light - mobile - text - input - search - form */ .topcoat-search-input, .topcoat-search-input--large { line-height: 3rem; font-size: 16px; border: 1px solid #a5a8a8; background-color: #d3d7d7; box-shadow: inset 0 1px rgba(0,0,0,0.12); color: #454545; padding: 0 0 0 2rem; border-radius: 30px; background-image: url("../img/search.svg"); background-position: 1em center; background-repeat: no-repeat; background-size: 16px; } .topcoat-search-input:focus, .topcoat-search-input--large:focus { background-image: url("../img/search_dark.svg"); background-color: #edf1f1; color: #000; border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } .topcoat-search-input::-webkit-search-cancel-button, .topcoat-search-input::-webkit-search-decoration, .topcoat-search-input--large::-webkit-search-cancel-button, .topcoat-search-input--large::-webkit-search-decoration { margin-right: 5px; } .topcoat-search-input:focus::-webkit-input-placeholder, .topcoat-search-input:focus::-webkit-input-placeholder { color: #c6c8c8; } .topcoat-search-input:disabled::-webkit-input-placeholder { color: #000; } .topcoat-search-input:disabled::-moz-placeholder { color: #000; } .topcoat-search-input:disabled:-ms-input-placeholder { color: #000; } /* topdoc name: Large Search Input description: A large text input designed for searching. modifiers: :disabled: Disabled state markup: tags: - desktop - light - mobile - text - input - search - form - large */ .topcoat-search-input--large { line-height: 4.375rem; font-size: 1.3rem; font-weight: 200; padding: 0 0 0 2.9rem; border-radius: 40px; background-position: 1.2em center; background-size: 1.3rem; } .topcoat-search-input--large:disabled { color: #000; } .topcoat-search-input--large:disabled::-webkit-input-placeholder { color: #000; } .topcoat-search-input--large:disabled::-moz-placeholder { color: #000; } .topcoat-search-input--large:disabled:-ms-input-placeholder { color: #000; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .switch { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } .switch__input { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .switch__toggle { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .switch__toggle:before, .switch__toggle:after { content: ''; position: absolute; z-index: -1; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } .switch--disabled { opacity: 0.3; cursor: default; pointer-events: none; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .switch, .topcoat-switch { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } .switch__input, .topcoat-switch__input { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .switch__toggle, .topcoat-switch__toggle { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .switch__toggle:before, .switch__toggle:after, .topcoat-switch__toggle:before, .topcoat-switch__toggle:after { content: ''; position: absolute; z-index: -1; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; } .switch--disabled, .topcoat-switch__input:disabled + .topcoat-switch__toggle { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Switch description: Default skin for Topcoat switch modifiers: :focus: Focus state :disabled: Disabled state markup:



examples: mobile switch: http://codepen.io/Topcoat/pen/upxds tags: - desktop - light - mobile - switch */ .topcoat-switch { font-size: 16px; padding: 0 1.25rem; border-radius: 6px; border: 1px solid #a5a8a8; overflow: hidden; width: 6rem; } .topcoat-switch__toggle:before, .topcoat-switch__toggle:after { top: -1px; width: 5rem; } .topcoat-switch__toggle:before { content: 'ON'; color: #0083e8; background-color: #e0f0fa; right: 1rem; padding-left: 1.5rem; } .topcoat-switch__toggle { line-height: 3rem; height: 3rem; width: 2rem; border-radius: 6px; color: #454545; text-shadow: 0 1px #fff; background-color: #e5e9e8; border: 1px solid #a5a8a8; margin-left: -1.3rem; margin-bottom: -1px; margin-top: -1px; box-shadow: inset 0 1px #fff; -webkit-transition: margin-left 0.05s ease-in-out; transition: margin-left 0.05s ease-in-out; } .topcoat-switch__toggle:after { content: 'OFF'; background-color: #d3d7d7; left: 1rem; padding-left: 2rem; } .topcoat-switch__input:checked + .topcoat-switch__toggle { margin-left: 2.7rem; } .topcoat-switch__input:focus + .topcoat-switch__toggle { border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } .topcoat-switch__input:disabled + .topcoat-switch__toggle:after, .topcoat-switch__input:disabled + .topcoat-switch__toggle:before { background: transparent; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .button, .topcoat-tab-bar__button { position: relative; display: inline-block; vertical-align: top; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none; } .button--quiet { background: transparent; border: 1px solid transparent; box-shadow: none; } .button--disabled, .topcoat-tab-bar__button:disabled { opacity: 0.3; cursor: default; pointer-events: none; } .button-bar, .topcoat-tab-bar { display: table; table-layout: fixed; white-space: nowrap; margin: 0; padding: 0; } .button-bar__item, .topcoat-tab-bar__item { display: table-cell; width: auto; border-radius: 0; } .button-bar__item > input, .topcoat-tab-bar__item > input { position: absolute; overflow: hidden; padding: 0; border: 0; opacity: 0.001; z-index: 1; vertical-align: top; outline: none; } .button-bar__button { border-radius: inherit; } .button-bar__item:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Tab Bar description: Component of tab buttons modifiers: :disabled: Disabled state markup:
examples: mobile tab bar: http://codepen.io/Topcoat/pen/rJICF tags: - desktop - light - dark - mobile - tab - group - bar */ .topcoat-tab-bar__button { padding: 0 1.25rem; height: 3rem; line-height: 3rem; letter-spacing: 1px; color: #454545; text-shadow: 0 1px #fff; vertical-align: top; background-color: #e5e9e8; box-shadow: inset 0 1px #fff; border-top: 1px solid #a5a8a8; } .topcoat-tab-bar__button:active, .topcoat-tab-bar__button--large:active, :checked + .topcoat-tab-bar__button { color: #0083e8; background-color: #e0f0fa; box-shadow: inset 0 0 2px #c0ced8; } .topcoat-tab-bar__button:focus, .topcoat-tab-bar__button--large:focus { z-index: 1; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .input, .topcoat-text-input, .topcoat-text-input--large { padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; vertical-align: top; outline: none; } .input:disabled, .topcoat-text-input:disabled, .topcoat-text-input--large:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Text input description: Topdoc text input modifiers: :disabled: Disabled state :focus: Focused :invalid: Hover state markup:



tags: - desktop - mobile - text - input */ .topcoat-text-input, .topcoat-text-input--large { line-height: 3rem; font-size: 16px; letter-spacing: 1px; padding: 0 1.25rem; border: 1px solid #a5a8a8; border-radius: 6px; background-color: #d3d7d7; box-shadow: inset 0 1px rgba(0,0,0,0.12); color: #454545; vertical-align: top; } .topcoat-text-input:focus, .topcoat-text-input--large:focus { background-color: #edf1f1; color: #000; border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } .topcoat-text-input:disabled::-webkit-input-placeholder { color: #000; } .topcoat-text-input:disabled::-moz-placeholder { color: #000; } .topcoat-text-input:disabled:-ms-input-placeholder { color: #000; } .topcoat-text-input:invalid { border: 1px solid #d83b75; } /* topdoc name: Large Text Input description: A bigger input, still for text. modifiers: :disabled: Disabled state :focus: Focused :invalid: Hover state markup:



tags: - desktop - light - mobile - form - input - large */ .topcoat-text-input--large { line-height: 4.375rem; font-size: 1.3rem; } .topcoat-text-input--large:disabled { color: #000; } .topcoat-text-input--large:disabled::-webkit-input-placeholder { color: #000; } .topcoat-text-input--large:disabled::-moz-placeholder { color: #000; } .topcoat-text-input--large:disabled:-ms-input-placeholder { color: #000; } .topcoat-text-input--large:invalid { border: 1px solid #d83b75; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .textarea { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; vertical-align: top; resize: none; outline: none; } .textarea:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /** * * Copyright 2012 Adobe Systems 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. * */ /** * * Copyright 2012 Adobe Systems 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. * */ .textarea, .topcoat-textarea, .topcoat-textarea--large { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; vertical-align: top; resize: none; outline: none; } .textarea:disabled, .topcoat-textarea:disabled, .topcoat-textarea--large:disabled { opacity: 0.3; cursor: default; pointer-events: none; } /* topdoc name: Textarea description: A whole area, just for text. modifiers: :disabled: Disabled state markup:

tags: - desktop - light - mobile - form - input - textarea */ .topcoat-textarea, .topcoat-textarea--large { padding: 2rem; font-size: 2.5rem; font-weight: 200; border-radius: 6px; line-height: 3rem; border: 1px solid #a5a8a8; background-color: #d3d7d7; box-shadow: inset 0 1px rgba(0,0,0,0.12); color: #454545; letter-spacing: 1px; } .topcoat-textarea:focus, .topcoat-textarea--large:focus { background-color: #edf1f1; color: #000; border: 1px solid #0940fd; box-shadow: 0 0 0 2px #6fb5f1; } .topcoat-textarea:disabled::-webkit-input-placeholder { color: #000; } .topcoat-textarea:disabled::-moz-placeholder { color: #000; } .topcoat-textarea:disabled:-ms-input-placeholder { color: #000; } /* topdoc name: Large Textarea description: A whole area, just for text; now available in large. modifiers: :disabled: Disabled state markup:

tags: - desktop - light - mobile - form - input - textarea */ .topcoat-textarea--large { font-size: 3rem; line-height: 4.375rem; } .topcoat-textarea--large:disabled { color: #000; } .topcoat-textarea--large:disabled::-webkit-input-placeholder { color: #000; } .topcoat-textarea--large:disabled::-moz-placeholder { color: #000; } .topcoat-textarea--large:disabled:-ms-input-placeholder { color: #000; } @font-face { font-family: "Source Sans"; src: url("../font/SourceSansPro-Regular.otf"); } @font-face { font-family: "Source Sans"; src: url("../font/SourceSansPro-Light.otf"); font-weight: 200; } @font-face { font-family: "Source Sans"; src: url("../font/SourceSansPro-Semibold.otf"); font-weight: 600; } body { margin: 0; padding: 0; background: #dfe2e2; color: #000; font: 16px "Source Sans", helvetica, arial, sans-serif; font-weight: 200; } :focus { outline-color: transparent; outline-style: none; } .topcoat-icon--menu-stack { background: url("../img/hamburger_dark.svg") no-repeat; background-size: cover; } .quarter { width: 25%; } .half { width: 50%; } .three-quarters { width: 75%; } .third { width: 33.333%; } .two-thirds { width: 66.666%; } .full { width: 100%; } .left { text-align: left; } .center { text-align: center; } .right { text-align: right; } .reset-ui { -moz-box-sizing: border-box; box-sizing: border-box; background-clip: padding-box; position: relative; display: inline-block; vertical-align: top; padding: 0; margin: 0; font: inherit; color: inherit; background: transparent; border: none; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } /* This file should include color and image variables corresponding to the dark theme */ /* Call To Action */ /* Icons */ /* Navigation Bar */ /* Text Input */ /* Search Input */ /* List */ /* Checkbox */ /* Overlay */ /* Progress bar */ /* Checkbox */ /* Radio Button */ /* Tab bar */ /* Switch */ /* Icon Button */ /* Navigation bar */ /* List */ /* Search Input */ /* Textarea */ /* Checkbox */ /* Radio */ /* Range input */ /* Search Input */ /* Switch */ /* This file should include color and image variables corresponding to the light theme */ /* Call To Action */ /* Icons */ /* Navigation Bar */ /* Text Input */ /* List */ /* Overlay */ /* Progress bar */ /* Checkbox */ /* Range input */ /* Radio Button */ /* Tab bar */ /* Switch */ /* Containers */ /* Icon Button */ /* Navigation bar */ /* List */ /* Search Input */ /* Text Area */ /* Checkbox */ /* Radio */ /* Range input */ /* Search Input */ /* Switch */ /* Text Input */ /* Radio input */ /* Overlay */ /* Textarea */ /* Progress bar container */ /* Progress bar progress */ /* Search input */ /* Switch */ /* Notification */ ================================================ FILE: doc/fonts/stylesheet.css ================================================ @font-face { font-family: 'source-sans-pro'; src: url('sourcesanspro-light-webfont.eot'); src: url('sourcesanspro-light-webfont.eot?#iefix') format('embedded-opentype'), url('sourcesanspro-light-webfont.woff') format('woff'), url('sourcesanspro-light-webfont.ttf') format('truetype'), url('sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg'); font-weight: 300; font-style: normal; } @font-face { font-family: 'source-sans-pro'; src: url('sourcesanspro-regular-webfont.eot'); src: url('sourcesanspro-regular-webfont.eot?#iefix') format('embedded-opentype'), url('sourcesanspro-regular-webfont.woff') format('woff'), url('sourcesanspro-regular-webfont.ttf') format('truetype'), url('sourcesanspro-regular-webfont.svg#source_sans_proregular') format('svg'); font-weight: 400; font-style: normal; } @font-face { font-family: 'source-sans-pro'; src: url('sourcesanspro-semibold-webfont.eot'); src: url('sourcesanspro-semibold-webfont.eot?#iefix') format('embedded-opentype'), url('sourcesanspro-semibold-webfont.woff') format('woff'), url('sourcesanspro-semibold-webfont.ttf') format('truetype'), url('sourcesanspro-semibold-webfont.svg#source_sans_prosemibold') format('svg'); font-weight: 600; font-style: normal; } @font-face { font-family: 'source-code-pro'; src: url('sourcecodepro-regular-webfont.eot'); src: url('sourcecodepro-regular-webfont.eot?#iefix') format('embedded-opentype'), url('sourcecodepro-regular-webfont.woff') format('woff'), url('sourcecodepro-regular-webfont.ttf') format('truetype'), url('sourcecodepro-regular-webfont.svg#source_code_proregular') format('svg'); font-weight: normal; font-style: normal; } ================================================ FILE: doc/js/prism.js ================================================ /** * Prism: Lightweight, robust, elegant syntax highlighting * MIT license http://www.opensource.org/licenses/mit-license.php/ * @author Lea Verou http://lea.verou.me */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g}; ; Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}}); ; ================================================ FILE: doc/reference.html ================================================ Snap.svg API Reference

Snap(…)

Creates a drawing surface or wraps existing SVG element.

Parameters

  1. width number string width of surface
  2. height number string height of surface

or

Parameters

  1. DOM SVGElement element to be wrapped into Snap structure

or

Parameters

  1. array array array of elements (will return set of elements)

or

Parameters

  1. query string CSS query selector

Returns: object Element

Snap.url(value)

Wraps path into "url('<path>')".

Parameters

  1. value string path

Returns: string wrapped path

Snap.format(token, json)

Replaces construction of type {<name>} to the corresponding argument

Parameters

  1. token string string to format
  2. json object object which properties are used as a replacement

Returns: string formatted string

Usage

// this draws a rectangular shape equivalent to "M10,20h40v50h-40z"
paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
    x: 10,
    y: 20,
    dim: {
        width: 40,
        height: 50,
        "negative width": -40
    }
}));

Snap.rad(deg)

Transform angle to radians

Parameters

  1. deg number angle in degrees

Returns: number angle in radians

Snap.deg(rad)

Transform angle to degrees

Parameters

  1. rad number angle in radians

Returns: number angle in degrees

Snap.sin(angle)

Equivalent to Math.sin() only works with degrees, not radians.

Parameters

  1. angle number angle in degrees

Returns: number sin

Snap.tan(angle)

Equivalent to Math.tan() only works with degrees, not radians.

Parameters

  1. angle number angle in degrees

Returns: number tan

Snap.cos(angle)

Equivalent to Math.cos() only works with degrees, not radians.

Parameters

  1. angle number angle in degrees

Returns: number cos

Snap.asin(num)

Equivalent to Math.asin() only works with degrees, not radians.

Parameters

  1. num number value

Returns: number asin in degrees

Snap.acos(num)

Equivalent to Math.acos() only works with degrees, not radians.

Parameters

  1. num number value

Returns: number acos in degrees

Snap.atan(num)

Equivalent to Math.atan() only works with degrees, not radians.

Parameters

  1. num number value

Returns: number atan in degrees

Snap.atan2(num)

Equivalent to Math.atan2() only works with degrees, not radians.

Parameters

  1. num number value

Returns: number atan2 in degrees

Snap.angle(x1, y1, x2, y2, [x3], [y3])

Returns an angle between two or three points

Parameters

  1. x1 number x coord of first point
  2. y1 number y coord of first point
  3. x2 number x coord of second point
  4. y2 number y coord of second point
  5. x3 number x coord of third point
  6. y3 number y coord of third point

Returns: number angle in degrees

Snap.len(x1, y1, x2, y2)

Returns distance between two points

Parameters

  1. x1 number x coord of first point
  2. y1 number y coord of first point
  3. x2 number x coord of second point
  4. y2 number y coord of second point

Returns: number distance

Snap.len2(x1, y1, x2, y2)

Returns squared distance between two points

Parameters

  1. x1 number x coord of first point
  2. y1 number y coord of first point
  3. x2 number x coord of second point
  4. y2 number y coord of second point

Returns: number distance

Snap.closestPoint(path, x, y)

Returns closest point to a given one on a given path.

Parameters

  1. path Element path element
  2. x number x coord of a point
  3. y number y coord of a point

Returns: object in format

Snap.is(o, type)

Handy replacement for the typeof operator

Parameters

  1. o any object or primitive
  2. type string name of the type, e.g., string, function, number, etc.

Returns: boolean true if given value is of given type

Snap.snapTo(values, value, [tolerance])

Snaps given value to given grid

Parameters

  1. values array number given array of values or step of the grid
  2. value number value to adjust
  3. tolerance number maximum distance to the target value that would trigger the snap. Default is 10.

Returns: number adjusted value

Snap.getRGB(color)

Parses color string as RGB object

Parameters

  1. color string color string in one of the following formats:
  • Color name (red, green, cornflowerblue, etc)
  • #••• — shortened HTML color: (#000, #fc0, etc.)
  • #•••••• — full length HTML color: (#000000, #bd2300)
  • rgb(•••, •••, •••) — red, green and blue channels values: (rgb(200, 100, 0))
  • rgba(•••, •••, •••, •••) — also with opacity
  • rgb(•••%, •••%, •••%) — same as above, but in %: (rgb(100%, 175%, 0%))
  • rgba(•••%, •••%, •••%, •••%) — also with opacity
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (hsb(0.5, 0.25, 1))
  • hsba(•••, •••, •••, •••) — also with opacity
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • hsba(•••%, •••%, •••%, •••%) — also with opacity
  • hsl(•••, •••, •••) — hue, saturation and luminosity values: (hsb(0.5, 0.25, 0.5))
  • hsla(•••, •••, •••, •••) — also with opacity
  • hsl(•••%, •••%, •••%) — same as above, but in %
  • hsla(•••%, •••%, •••%, •••%) — also with opacity

Note that % can be used any time: rgb(20%, 255, 50%).

Returns: object RGB object in the following format:

  1. {
    1. r number red,
    2. g number green,
    3. b number blue,
    4. hex string color in HTML/CSS format: #••••••,
    5. error boolean true if string can't be parsed
  2. }

Snap.hsb(h, s, b)

Converts HSB values to a hex representation of the color

Parameters

  1. h number hue
  2. s number saturation
  3. b number value or brightness

Returns: string hex representation of the color

Snap.hsl(h, s, l)

Converts HSL values to a hex representation of the color

Parameters

  1. h number hue
  2. s number saturation
  3. l number luminosity

Returns: string hex representation of the color

Snap.rgb(r, g, b)

Converts RGB values to a hex representation of the color

Parameters

  1. r number red
  2. g number green
  3. b number blue

Returns: string hex representation of the color

Snap.color(clr)

Parses the color string and returns an object featuring the color's component values

Parameters

  1. clr string color string in one of the supported formats (see Snap.getRGB)

Returns: object Combined RGB/HSB object in the following format:

  1. {
    1. r number red,
    2. g number green,
    3. b number blue,
    4. hex string color in HTML/CSS format: #••••••,
    5. error boolean true if string can't be parsed,
    6. h number hue,
    7. s number saturation,
    8. v number value (brightness),
    9. l number lightness
  2. }

Snap.hsb2rgb(h, s, v)

Converts HSB values to an RGB object

Parameters

  1. h number hue
  2. s number saturation
  3. v number value or brightness

Returns: object RGB object in the following format:

  1. {
    1. r number red,
    2. g number green,
    3. b number blue,
    4. hex string color in HTML/CSS format: #••••••
  2. }

Snap.hsl2rgb(h, s, l)

Converts HSL values to an RGB object

Parameters

  1. h number hue
  2. s number saturation
  3. l number luminosity

Returns: object RGB object in the following format:

  1. {
    1. r number red,
    2. g number green,
    3. b number blue,
    4. hex string color in HTML/CSS format: #••••••
  2. }

Snap.rgb2hsb(r, g, b)

Converts RGB values to an HSB object

Parameters

  1. r number red
  2. g number green
  3. b number blue

Returns: object HSB object in the following format:

  1. {
    1. h number hue,
    2. s number saturation,
    3. b number brightness
  2. }

Snap.rgb2hsl(r, g, b)

Converts RGB values to an HSL object

Parameters

  1. r number red
  2. g number green
  3. b number blue

Returns: object HSL object in the following format:

  1. {
    1. h number hue,
    2. s number saturation,
    3. l number luminosity
  2. }

Snap.parsePathString(pathString)

Utility method Parses given path string into an array of arrays of path segments

Parameters

  1. pathString string array path string or array of segments (in the last case it is returned straight away)

Returns: array array of segments

Snap.parseTransformString(TString)

Utility method Parses given transform string into an array of transformations

Parameters

  1. TString string array transform string or array of transformations (in the last case it is returned straight away)

Returns: array array of transformations

Snap.select(query)

Wraps a DOM element specified by CSS selector as Element

Parameters

  1. query string CSS selector of the element

Returns: Element the current element

Snap.selectAll(query)

Wraps DOM elements specified by CSS selector as set or array of Element

Parameters

  1. query string CSS selector of the element

Returns: Element the current element

Element.node()

Gives you a reference to the DOM object, so you can assign event handlers or just mess around.

Usage

// draw a circle at coordinate 10,10 with radius of 10
var c = paper.circle(10, 10, 10);
c.node.onclick = function () {
    c.attr("fill", "red");
};

Element.type()

SVG tag name of the given element.

Element.attr(…)

Gets or sets given attributes of the element.

Parameters

  1. params object contains key-value pairs of attributes you want to set

or

Parameters

  1. param string name of the attribute

Returns: Element the current element

or

Returns: string value of attribute

Usage

el.attr({
    fill: "#fc0",
    stroke: "#000",
    strokeWidth: 2, // CamelCase...
    "fill-opacity": 0.5, // or dash-separated names
    width: "*=2" // prefixed values
});
console.log(el.attr("fill")); // #fc0

Prefixed values in format "+=10" supported. All four operations ( +, -, * and /) could be used. Optionally you can use units for + and -: "+=2em".

Snap.parse(svg)

Parses SVG fragment and converts it into a Fragment

Parameters

  1. svg string SVG string

Returns: Fragment the Fragment

Snap.fragment(varargs)

Creates a DOM fragment from a given list of elements or strings

Parameters

  1. varargs SVG string

Returns: Fragment the Fragment

Paper.el(name, attr)

Creates an element on paper with a given name and no attributes

Parameters

  1. name string tag name
  2. attr object attributes

Returns: Element the current element

Usage

var c = paper.circle(10, 10, 10); // is the same as...
var c = paper.el("circle").attr({
    cx: 10,
    cy: 10,
    r: 10
});
// and the same as
var c = paper.el("circle", {
    cx: 10,
    cy: 10,
    r: 10
});

Element.children()

Returns array of all the children of the element.

Returns: array array of Elements

Element.toJSON()

Returns object representation of the given element and all its children.

Returns: object in format

  1. {
    1. type string this.type,
    2. attr object attributes map,
    3. childNodes array optional array of children in the same format
  2. }

Snap.ajax(…)

Simple implementation of Ajax

Parameters

  1. url string URL
  2. postData object string data for post request
  3. callback function callback
  4. scope object scope of callback

or

Parameters

  1. url string URL
  2. callback function callback
  3. scope object scope of callback

Returns: XMLHttpRequest the XMLHttpRequest object, just in case

Snap.load(url, callback, [scope])

Loads external SVG file as a Fragment (see Snap.ajax for more advanced AJAX)

Parameters

  1. url string URL
  2. callback function callback
  3. scope object scope of callback

Snap.getElementByPoint(x, y)

Returns you topmost element under given point.

Returns: object Snap element object

Parameters

  1. x number x coordinate from the top left corner of the window
  2. y number y coordinate from the top left corner of the window

Usage

Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});

Snap.plugin(f)

Let you write plugins. You pass in a function with five arguments, like this:

Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
    Snap.newmethod = function () {};
    Element.prototype.newmethod = function () {};
    Paper.prototype.newmethod = function () {};
});

Inside the function you have access to all main objects (and their prototypes). This allow you to extend anything you want.

Parameters

  1. f function your plugin body

Element.getBBox()

Returns the bounding box descriptor for the given element

Returns: object bounding box descriptor:

  1. {
    1. cx: number x of the center,
    2. cy: number x of the center,
    3. h: number height,
    4. height: number height,
    5. path: string path command for the box,
    6. r0: number radius of a circle that fully encloses the box,
    7. r1: number radius of the smallest circle that can be enclosed,
    8. r2: number radius of the largest circle that can be enclosed,
    9. vb: string box as a viewbox command,
    10. w: number width,
    11. width: number width,
    12. x2: number x of the right side,
    13. x: number x of the left side,
    14. y2: number y of the bottom edge,
    15. y: number y of the top edge
  2. }

Element.transform(tstr)

Gets or sets transformation of the element

Parameters

  1. tstr string transform string in Snap or SVG format

Returns: Element the current element

or

Returns: object transformation descriptor:

  1. {
    1. string string transform string,
    2. globalMatrix Matrix matrix of all transformations applied to element or its parents,
    3. localMatrix Matrix matrix of transformations applied only to the element,
    4. diffMatrix Matrix matrix of difference between global and local transformations,
    5. global string global transformation as string,
    6. local string local transformation as string,
    7. toString function returns string property
  2. }

Element.parent()

Returns the element's parent

Returns: Element the parent element

Element.append(el)

Appends the given element to current one

Parameters

  1. el Element Set element to append

Returns: Element the parent element

Element.appendTo(el)

Appends the current element to the given one

Parameters

  1. el Element parent element to append to

Returns: Element the child element

Element.prepend(el)

Prepends the given element to the current one

Parameters

  1. el Element element to prepend

Returns: Element the parent element

Element.prependTo(el)

Prepends the current element to the given one

Parameters

  1. el Element parent element to prepend to

Returns: Element the child element

Element.before(el)

Inserts given element before the current one

Parameters

  1. el Element element to insert

Returns: Element the parent element

Element.after(el)

Inserts given element after the current one

Parameters

  1. el Element element to insert

Returns: Element the parent element

Element.insertBefore(el)

Inserts the element after the given one

Parameters

  1. el Element element next to whom insert to

Returns: Element the parent element

Element.insertAfter(el)

Inserts the element after the given one

Parameters

  1. el Element element next to whom insert to

Returns: Element the parent element

Element.remove()

Removes element from the DOM

Returns: Element the detached element

Element.select(query)

Gathers the nested Element matching the given set of CSS selectors

Parameters

  1. query string CSS selector

Returns: Element result of query selection

Element.selectAll(query)

Gathers nested Element objects matching the given set of CSS selectors

Parameters

  1. query string CSS selector

Returns: Set array result of query selection

Element.asPX(attr, [value])

Returns given attribute of the element as a px value (not %, em, etc.)

Parameters

  1. attr string attribute name
  2. value string attribute value

Returns: Element result of query selection

Element.use()

Creates a <use> element linked to the current element

Returns: Element the <use> element

Element.clone()

Creates a clone of the element and inserts it after the element

Returns: Element the clone

Element.toDefs()

Moves element to the shared <defs> area

Returns: Element the element

Element.toPattern(x, y, width, height)

Creates a <pattern> element from the current element To create a pattern you have to specify the pattern rect:

Parameters

  1. x string number
  2. y string number
  3. width string number
  4. height string number

Returns: Element the <pattern> element

You can use pattern later on as an argument for fill attribute:

var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
        fill: "none",
        stroke: "#bada55",
        strokeWidth: 5
    }).pattern(0, 0, 10, 10),
    c = paper.circle(200, 200, 100);
c.attr({
    fill: p
});

Element.marker(x, y, width, height, refX, refY)

Creates a <marker> element from the current element To create a marker you have to specify the bounding rect and reference point:

Parameters

  1. x number
  2. y number
  3. width number
  4. height number
  5. refX number
  6. refY number

Returns: Element the <marker> element

You can specify the marker later as an argument for marker-start, marker-end, marker-mid, and marker attributes. The marker attribute places the marker at every point along the path, and marker-mid places them at every point except the start and end.

Element.data(key, [value])

Adds or retrieves given value associated with given key. (Don’t confuse with data- attributes)

See also Element.removeData

Parameters

  1. key string key to store data
  2. value any value to store

Returns: object Element

or, if value is not specified:

Returns: any value

Usage

for (var i = 0, i < 5, i++) {
    paper.circle(10 + 15 * i, 10, 10)
         .attr({fill: "#000"})
         .data("i", i)
         .click(function () {
            alert(this.data("i"));
         });
}

Element.removeData([key])

Removes value associated with an element by given key. If key is not provided, removes all the data of the element.

Parameters

  1. key string key

Returns: object Element

Element.outerSVG()

Returns SVG code for the element, equivalent to HTML's outerHTML.

See also Element.innerSVG

Returns: string SVG code for the element

Element.innerSVG()

Returns SVG code for the element's contents, equivalent to HTML's innerHTML

Returns: string SVG code for the element

Snap.deurl(value)

Unwraps path from "url(<path>)".

Parameters

  1. value string url path

Returns: string unwrapped path

Snap.animation(attr, duration, [easing], [callback])

Creates an animation object

Parameters

  1. attr object attributes of final destination
  2. duration number duration of the animation, in milliseconds
  3. easing function one of easing functions of mina or custom one
  4. callback function callback function that fires when animation ends

Returns: object animation object

Element.inAnim()

Returns a set of animations that may be able to manipulate the current element

Returns: object in format:

  1. {
    1. anim object animation object,
    2. mina object mina object,
    3. curStatus number 0..1 — status of the animation: 0 — just started, 1 — just finished,
    4. status function gets or sets the status of the animation,
    5. stop function stops the animation
  2. }

Snap.animate(from, to, setter, duration, [easing], [callback])

Runs generic animation of one number into another with a caring function

Parameters

  1. from number array number or array of numbers
  2. to number array number or array of numbers
  3. setter function caring function that accepts one number argument
  4. duration number duration, in milliseconds
  5. easing function easing function from mina or custom
  6. callback function callback function to execute when animation ends

Returns: object animation object in mina format

  1. {
    1. id string animation id, consider it read-only,
    2. duration function gets or sets the duration of the animation,
    3. easing function easing,
    4. speed function gets or sets the speed of the animation,
    5. status function gets or sets the status of the animation,
    6. stop function stops the animation
  2. }
var rect = Snap().rect(0, 0, 10, 10);
Snap.animate(0, 10, function (val) {
    rect.attr({
        x: val
    });
}, 1000);
// in given context is equivalent to
rect.animate({x: 10}, 1000);

Element.stop()

Stops all the animations for the current element

Returns: Element the current element

Element.animate(attrs, duration, [easing], [callback])

Animates the given attributes of the element

Parameters

  1. attrs object key-value pairs of destination attributes
  2. duration number duration of the animation in milliseconds
  3. easing function easing function from mina or custom
  4. callback function callback function that executes when the animation ends

Returns: Element the current element

Matrix.add(…)

Adds the given matrix to existing one

Parameters

  1. a number
  2. b number
  3. c number
  4. d number
  5. e number
  6. f number

or

Parameters

  1. matrix object Matrix

Matrix.multLeft(…)

Multiplies a passed affine transform to the left: M * this.

Parameters

  1. a number
  2. b number
  3. c number
  4. d number
  5. e number
  6. f number

or

Parameters

  1. matrix object Matrix

Matrix.invert()

Returns an inverted version of the matrix

Returns: object Matrix

Matrix.clone()

Returns a copy of the matrix

Returns: object Matrix

Matrix.translate(x, y)

Translate the matrix

Parameters

  1. x number horizontal offset distance
  2. y number vertical offset distance

Matrix.scale(x, [y], [cx], [cy])

Scales the matrix

Parameters

  1. x number amount to be scaled, with 1 resulting in no change
  2. y number amount to scale along the vertical axis. (Otherwise x applies to both axes.)
  3. cx number horizontal origin point from which to scale
  4. cy number vertical origin point from which to scale

Default cx, cy is the middle point of the element.

Matrix.rotate(a, x, y)

Rotates the matrix

Parameters

  1. a number angle of rotation, in degrees
  2. x number horizontal origin point from which to rotate
  3. y number vertical origin point from which to rotate

Matrix.skewX(x)

Skews the matrix along the x-axis

Parameters

  1. x number Angle to skew along the x-axis (in degrees).

Matrix.skewY(y)

Skews the matrix along the y-axis

Parameters

  1. y number Angle to skew along the y-axis (in degrees).

Matrix.skew(y, x)

Skews the matrix

Parameters

  1. y number Angle to skew along the y-axis (in degrees).
  2. x number Angle to skew along the x-axis (in degrees).

Matrix.x(x, y)

Returns x coordinate for given point after transformation described by the matrix. See also Matrix.y

Parameters

  1. x number
  2. y number

Returns: number x

Matrix.y(x, y)

Returns y coordinate for given point after transformation described by the matrix. See also Matrix.x

Parameters

  1. x number
  2. y number

Returns: number y

Matrix.determinant()

Finds determinant of the given matrix.

Returns: number determinant

Matrix.split()

Splits matrix into primitive transformations

Returns: object in format:

  1. dx number translation by x
  2. dy number translation by y
  3. scalex number scale by x
  4. scaley number scale by y
  5. shear number shear
  6. rotate number rotation in deg
  7. isSimple boolean could it be represented via simple transformations

Matrix.toTransformString()

Returns transform string that represents given matrix

Returns: string transform string

Snap.Matrix()

Matrix constructor, extend on your own risk. To create matrices use Snap.matrix.

Snap.matrix(…)

Utility method Returns a matrix based on the given parameters

Parameters

  1. a number
  2. b number
  3. c number
  4. d number
  5. e number
  6. f number

or

Parameters

  1. svgMatrix SVGMatrix

Returns: object Matrix

Paper.rect(x, y, width, height, [rx], [ry])

Draws a rectangle

Parameters

  1. x number x coordinate of the top left corner
  2. y number y coordinate of the top left corner
  3. width number width
  4. height number height
  5. rx number horizontal radius for rounded corners, default is 0
  6. ry number vertical radius for rounded corners, default is rx or 0

Returns: object the rect element

Usage

// regular rectangle
var c = paper.rect(10, 10, 50, 50);
// rectangle with rounded corners
var c = paper.rect(40, 40, 50, 50, 10);

Paper.circle(x, y, r)

Draws a circle

Parameters

  1. x number x coordinate of the centre
  2. y number y coordinate of the centre
  3. r number radius

Returns: object the circle element

Usage

var c = paper.circle(50, 50, 40);

Paper.image(src, x, y, width, height)

Places an image on the surface

Parameters

  1. src string URI of the source image
  2. x number x offset position
  3. y number y offset position
  4. width number width of the image
  5. height number height of the image

Returns: object the image element

or

Returns: object Snap element object with type image

Usage

var c = paper.image("apple.png", 10, 10, 80, 80);

Paper.ellipse(x, y, rx, ry)

Draws an ellipse

Parameters

  1. x number x coordinate of the centre
  2. y number y coordinate of the centre
  3. rx number horizontal radius
  4. ry number vertical radius

Returns: object the ellipse element

Usage

var c = paper.ellipse(50, 50, 40, 20);

Paper.path([pathString])

Creates a <path> element using the given string as the path's definition

Parameters

  1. pathString string path string in SVG format

Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:

"M10,20L30,40"

This example features two commands: M, with arguments (10, 20) and L with arguments (30, 40). Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.

Here is short list of commands available, for more details see SVG path string format or article about path strings at MDN.

Command Name Parameters
M moveto (x y)+
Z closepath (none)
L lineto (x y)+
H horizontal lineto x+
V vertical lineto y+
C curveto (x1 y1 x2 y2 x y)+
S smooth curveto (x2 y2 x y)+
Q quadratic Bézier curveto (x1 y1 x y)+
T smooth quadratic Bézier curveto (x y)+
A elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
R Catmull-Rom curveto* x1 y1 (x y)+

  • Catmull-Rom curveto is a not standard SVG command and added to make life easier.
  • Note: there is a special case when a path consists of only three commands: M10,10R…z. In this case the path connects back to its starting point.

    Usage

    var c = paper.path("M10 10L90 90");
    // draw a diagonal line:
    // move to 10,10, line to 90,90

    Paper.g([varargs])

    Creates a group element

    Parameters

    1. varargs elements to nest within the group

    Returns: object the g element

    Usage

    var c1 = paper.circle(),
        c2 = paper.rect(),
        g = paper.g(c2, c1); // note that the order of elements is different

    or

    var c1 = paper.circle(),
        c2 = paper.rect(),
        g = paper.g();
    g.add(c2, c1);

    Paper.svg(x, y, width, height, vbx, vby, vbw, vbh)

    Creates a nested SVG element.

    Parameters

    1. x number optional X of the element
    2. y number optional Y of the element
    3. width number optional width of the element
    4. height number optional height of the element
    5. vbx number optional viewbox X
    6. vby number optional viewbox Y
    7. vbw number optional viewbox width
    8. vbh number optional viewbox height

    Returns: object the svg element

    Paper.mask()

    Equivalent in behaviour to Paper.g, except it’s a mask.

    Returns: object the mask element

    Paper.ptrn(x, y, width, height, vbx, vby, vbw, vbh)

    Equivalent in behaviour to Paper.g, except it’s a pattern.

    Parameters

    1. x number optional X of the element
    2. y number optional Y of the element
    3. width number optional width of the element
    4. height number optional height of the element
    5. vbx number optional viewbox X
    6. vby number optional viewbox Y
    7. vbw number optional viewbox width
    8. vbh number optional viewbox height

    Returns: object the pattern element

    Paper.use(…)

    Creates a <use> element.

    Parameters

    1. id string optional id of element to link

    or

    Parameters

    1. id Element optional element to link

    Returns: object the use element

    Paper.symbol(vbx, vby, vbw, vbh)

    Creates a <symbol> element.

    Parameters

    1. vbx number optional viewbox X
    2. vby number optional viewbox Y
    3. vbw number optional viewbox width
    4. vbh number optional viewbox height

    Returns: object the symbol element

    Paper.text(x, y, text)

    Draws a text string

    Parameters

    1. x number x coordinate position
    2. y number y coordinate position
    3. text string array The text string to draw or array of strings to nest within separate <tspan> elements

    Returns: object the text element

    Usage

    var t1 = paper.text(50, 50, "Snap");
    var t2 = paper.text(50, 50, ["S","n","a","p"]);
    // Text path usage
    t1.attr({textpath: "M10,10L100,100"});
    // or
    var pth = paper.path("M10,10L100,100");
    t1.attr({textpath: pth});

    Paper.line(x1, y1, x2, y2)

    Draws a line

    Parameters

    1. x1 number x coordinate position of the start
    2. y1 number y coordinate position of the start
    3. x2 number x coordinate position of the end
    4. y2 number y coordinate position of the end

    Returns: object the line element

    Usage

    var t1 = paper.line(50, 50, 100, 100);

    Paper.polyline(…)

    Draws a polyline

    Parameters

    1. points array array of points

    or

    Parameters

    1. varargs points

    Returns: object the polyline element

    Usage

    var p1 = paper.polyline([10, 10, 100, 100]);
    var p2 = paper.polyline(10, 10, 100, 100);

    Element.stops()

    Only for gradients! Returns array of gradient stops elements.

    Returns: array the stops array.

    Element.addStop(color, offset)

    Only for gradients! Adds another stop to the gradient.

    Parameters

    1. color string stops color
    2. offset number stops offset 0..100

    Returns: object gradient element

    Element.setStops(str)

    Only for gradients! Updates stops of the gradient based on passed gradient descriptor. See Paper.gradient

    Parameters

    1. str string gradient descriptor part after ().

    Returns: object gradient element

    var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
    g.setStops("#fff-#000-#f00-#fc0");

    Paper.gradient(gradient)

    Creates a gradient element

    Parameters

    1. gradient string gradient descriptor

    Gradient Descriptor

    The gradient descriptor is an expression formatted as follows: <type>(<coords>)<colors>. The <type> can be either linear or radial. The uppercase L or R letters indicate absolute coordinates offset from the SVG surface. Lowercase l or r letters indicate coordinates calculated relative to the element to which the gradient is applied. Coordinates specify a linear gradient vector as x1, y1, x2, y2, or a radial gradient as cx, cy, r and optional fx, fy specifying a focal point away from the center of the circle. Specify <colors> as a list of dash-separated CSS color values. Each color may be followed by a custom offset value, separated with a colon character.

    Examples

    Linear gradient, relative from top-left corner to bottom-right corner, from black through red to white:

    var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");

    Linear gradient, absolute from (0, 0) to (100, 100), from black through red at 25% to white:

    var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");

    Radial gradient, relative from the center of the element with radius half the width, from black to white:

    var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");

    To apply the gradient:

    paper.circle(50, 50, 40).attr({
        fill: g
    });

    Returns: object the gradient element

    Paper.toString()

    Returns SVG code for the Paper

    Returns: string SVG code for the Paper

    Paper.toDataURL()

    Returns SVG code for the Paper as Data URI string.

    Returns: string Data URI string

    Paper.clear()

    Removes all child nodes of the paper, except <defs>.

    Element.addClass(value)

    Adds given class name or list of class names to the element.

    Parameters

    1. value string class name or space separated list of class names

    Returns: Element original element.

    Element.removeClass(value)

    Removes given class name or list of class names from the element.

    Parameters

    1. value string class name or space separated list of class names

    Returns: Element original element.

    Element.hasClass(value)

    Checks if the element has a given class name in the list of class names applied to it.

    Parameters

    1. value string class name

    Returns: boolean true if the element has given class

    Element.toggleClass(value, flag)

    Add or remove one or more classes from the element, depending on either the class’s presence or the value of the flag argument.

    Parameters

    1. value string class name or space separated list of class names
    2. flag boolean value to determine whether the class should be added or removed

    Returns: Element original element.

    mina(a, A, b, B, get, set, [easing])

    Generic animation of numbers

    Parameters

    1. a number start slave number
    2. A number end slave number
    3. b number start master number (start time in general case)
    4. B number end master number (end time in general case)
    5. get function getter of master number (see mina.time)
    6. set function setter of slave number
    7. easing function easing function, default is mina.linear

    Returns: object animation descriptor

    1. {
      1. id string animation id,
      2. start number start slave number,
      3. end number end slave number,
      4. b number start master number,
      5. s number animation status (0..1),
      6. dur number animation duration,
      7. spd number animation speed,
      8. get function getter of master number (see mina.time),
      9. set function setter of slave number,
      10. easing function easing function, default is mina.linear,
      11. status function status getter/setter,
      12. speed function speed getter/setter,
      13. duration function duration getter/setter,
      14. stop function animation stopper
      15. pause function pauses the animation
      16. resume function resumes the animation
      17. update function calles setter with the right value of the animation
    2. }

    mina.time()

    Returns the current time. Equivalent to:

    function () {
        return (new Date).getTime();
    }

    mina.getById(id)

    Returns an animation by its id

    Parameters

    1. id string animation's id

    Returns: object See mina

    mina.linear(n)

    Default linear easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    mina.easeout(n)

    Easeout easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    mina.easein(n)

    Easein easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    mina.easeinout(n)

    Easeinout easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    mina.backin(n)

    Backin easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    mina.backout(n)

    Backout easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    mina.elastic(n)

    Elastic easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    mina.bounce(n)

    Bounce easing

    Parameters

    1. n number input 0..1

    Returns: number output 0..1

    Paper.filter(filstr)

    Creates a <filter> element

    Parameters

    1. filstr string SVG fragment of filter provided as a string

    Returns: object Element

    Note: It is recommended to use filters embedded into the page inside an empty SVG element.

    Usage

    var f = paper.filter(''),
        c = paper.circle(10, 10, 10).attr({
            filter: f
        });

    Snap.filter.blur(x, [y])

    Returns an SVG markup string for the blur filter

    Parameters

    1. x number amount of horizontal blur, in pixels
    2. y number amount of vertical blur, in pixels

    Returns: string filter representation

    Usage

    var f = paper.filter(Snap.filter.blur(5, 10)),
        c = paper.circle(10, 10, 10).attr({
            filter: f
        });

    Snap.filter.shadow(…)

    Returns an SVG markup string for the shadow filter

    Parameters

    1. dx number horizontal shift of the shadow, in pixels
    2. dy number vertical shift of the shadow, in pixels
    3. blur number amount of blur
    4. color string color of the shadow
    5. opacity number 0..1 opacity of the shadow

    or

    Parameters

    1. dx number horizontal shift of the shadow, in pixels
    2. dy number vertical shift of the shadow, in pixels
    3. color string color of the shadow
    4. opacity number 0..1 opacity of the shadow

    which makes blur default to 4. Or

    Parameters

    1. dx number horizontal shift of the shadow, in pixels
    2. dy number vertical shift of the shadow, in pixels
    3. opacity number 0..1 opacity of the shadow

    Returns: string filter representation

    Usage

    var f = paper.filter(Snap.filter.shadow(0, 2, .3)),
        c = paper.circle(10, 10, 10).attr({
            filter: f
        });

    Snap.filter.grayscale(amount)

    Returns an SVG markup string for the grayscale filter

    Parameters

    1. amount number amount of filter (0..1)

    Returns: string filter representation

    Snap.filter.sepia(amount)

    Returns an SVG markup string for the sepia filter

    Parameters

    1. amount number amount of filter (0..1)

    Returns: string filter representation

    Snap.filter.saturate(amount)

    Returns an SVG markup string for the saturate filter

    Parameters

    1. amount number amount of filter (0..1)

    Returns: string filter representation

    Snap.filter.hueRotate(angle)

    Returns an SVG markup string for the hue-rotate filter

    Parameters

    1. angle number angle of rotation

    Returns: string filter representation

    Snap.filter.invert(amount)

    Returns an SVG markup string for the invert filter

    Parameters

    1. amount number amount of filter (0..1)

    Returns: string filter representation

    Snap.filter.brightness(amount)

    Returns an SVG markup string for the brightness filter

    Parameters

    1. amount number amount of filter (0..1)

    Returns: string filter representation

    Snap.filter.contrast(amount)

    Returns an SVG markup string for the contrast filter

    Parameters

    1. amount number amount of filter (0..1)

    Returns: string filter representation

    Element.click(handler)

    Adds a click event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.unclick(handler)

    Removes a click event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.dblclick(handler)

    Adds a double click event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.undblclick(handler)

    Removes a double click event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.mousedown(handler)

    Adds a mousedown event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.unmousedown(handler)

    Removes a mousedown event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.mousemove(handler)

    Adds a mousemove event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.unmousemove(handler)

    Removes a mousemove event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.mouseout(handler)

    Adds a mouseout event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.unmouseout(handler)

    Removes a mouseout event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.mouseover(handler)

    Adds a mouseover event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.unmouseover(handler)

    Removes a mouseover event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.mouseup(handler)

    Adds a mouseup event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.unmouseup(handler)

    Removes a mouseup event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.touchstart(handler)

    Adds a touchstart event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.untouchstart(handler)

    Removes a touchstart event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.touchmove(handler)

    Adds a touchmove event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.untouchmove(handler)

    Removes a touchmove event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.touchend(handler)

    Adds a touchend event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.untouchend(handler)

    Removes a touchend event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.touchcancel(handler)

    Adds a touchcancel event handler to the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.untouchcancel(handler)

    Removes a touchcancel event handler from the element

    Parameters

    1. handler function handler for the event

    Returns: object Element

    Element.hover(f_in, f_out, [icontext], [ocontext])

    Adds hover event handlers to the element

    Parameters

    1. f_in function handler for hover in
    2. f_out function handler for hover out
    3. icontext object context for hover in handler
    4. ocontext object context for hover out handler

    Returns: object Element

    Element.unhover(f_in, f_out)

    Removes hover event handlers from the element

    Parameters

    1. f_in function handler for hover in
    2. f_out function handler for hover out

    Returns: object Element

    Element.drag(onmove, onstart, onend, [mcontext], [scontext], [econtext])

    Adds event handlers for an element's drag gesture

    Parameters

    1. onmove function handler for moving
    2. onstart function handler for drag start
    3. onend function handler for drag end
    4. mcontext object context for moving handler
    5. scontext object context for drag start handler
    6. econtext object context for drag end handler

    Additionaly following drag events are triggered: drag.start.<id> on start, drag.end.<id> on end and drag.move.<id> on every move. When element is dragged over another element drag.over.<id> fires as well.

    Start event and start handler are called in specified context or in context of the element with following parameters:

    1. x number x position of the mouse
    2. y number y position of the mouse
    3. event object DOM event object

    Move event and move handler are called in specified context or in context of the element with following parameters:

    1. dx number shift by x from the start point
    2. dy number shift by y from the start point
    3. x number x position of the mouse
    4. y number y position of the mouse
    5. event object DOM event object

    End event and end handler are called in specified context or in context of the element with following parameters:

    1. event object DOM event object

    Returns: object Element

    Element.undrag()

    Removes all drag event handlers from the given element

    Snap.path.getTotalLength(path)

    Returns the length of the given path in pixels

    Parameters

    1. path string SVG path string

    Returns: number length

    Snap.path.getPointAtLength(path, length)

    Returns the coordinates of the point located at the given length along the given path

    Parameters

    1. path string SVG path string
    2. length number length, in pixels, from the start of the path, excluding non-rendering jumps

    Returns: object representation of the point:

    1. {
      1. x: number x coordinate,
      2. y: number y coordinate,
      3. alpha: number angle of derivative
    2. }

    Snap.path.getSubpath(path, from, to)

    Returns the subpath of a given path between given start and end lengths

    Parameters

    1. path string SVG path string
    2. from number length, in pixels, from the start of the path to the start of the segment
    3. to number length, in pixels, from the start of the path to the end of the segment

    Returns: string path string definition for the segment

    Element.getTotalLength()

    Returns the length of the path in pixels (only works for path elements)

    Returns: number length

    Element.getPointAtLength(length)

    Returns coordinates of the point located at the given length on the given path (only works for path elements)

    Parameters

    1. length number length, in pixels, from the start of the path, excluding non-rendering jumps

    Returns: object representation of the point:

    1. {
      1. x: number x coordinate,
      2. y: number y coordinate,
      3. alpha: number angle of derivative
    2. }

    Element.getSubpath(from, to)

    Returns subpath of a given element from given start and end lengths (only works for path elements)

    Parameters

    1. from number length, in pixels, from the start of the path to the start of the segment
    2. to number length, in pixels, from the start of the path to the end of the segment

    Returns: string path string definition for the segment

    Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t)

    Utility method Finds dot coordinates on the given cubic beziér curve at the given t

    Parameters

    1. p1x number x of the first point of the curve
    2. p1y number y of the first point of the curve
    3. c1x number x of the first anchor of the curve
    4. c1y number y of the first anchor of the curve
    5. c2x number x of the second anchor of the curve
    6. c2y number y of the second anchor of the curve
    7. p2x number x of the second point of the curve
    8. p2y number y of the second point of the curve
    9. t number position on the curve (0..1)

    Returns: object point information in format:

    1. {
      1. x: number x coordinate of the point,
      2. y: number y coordinate of the point,
      3. m: {
        1. x: number x coordinate of the left anchor,
        2. y: number y coordinate of the left anchor
      4. },
      5. n: {
        1. x: number x coordinate of the right anchor,
        2. y: number y coordinate of the right anchor
      6. },
      7. start: {
        1. x: number x coordinate of the start of the curve,
        2. y: number y coordinate of the start of the curve
      8. },
      9. end: {
        1. x: number x coordinate of the end of the curve,
        2. y: number y coordinate of the end of the curve
      10. },
      11. alpha: number angle of the curve derivative at the point
    2. }

    Snap.path.bezierBBox(…)

    Utility method Returns the bounding box of a given cubic beziér curve

    Parameters

    1. p1x number x of the first point of the curve
    2. p1y number y of the first point of the curve
    3. c1x number x of the first anchor of the curve
    4. c1y number y of the first anchor of the curve
    5. c2x number x of the second anchor of the curve
    6. c2y number y of the second anchor of the curve
    7. p2x number x of the second point of the curve
    8. p2y number y of the second point of the curve

    or

    Parameters

    1. bez array array of six points for beziér curve

    Returns: object bounding box

    1. {
      1. x: number x coordinate of the left top point of the box,
      2. y: number y coordinate of the left top point of the box,
      3. x2: number x coordinate of the right bottom point of the box,
      4. y2: number y coordinate of the right bottom point of the box,
      5. width: number width of the box,
      6. height: number height of the box
    2. }

    Snap.path.isPointInsideBBox(bbox, x, y)

    Utility method Returns true if given point is inside bounding box

    Parameters

    1. bbox string bounding box
    2. x string x coordinate of the point
    3. y string y coordinate of the point

    Returns: boolean true if point is inside

    Snap.path.isBBoxIntersect(bbox1, bbox2)

    Utility method Returns true if two bounding boxes intersect

    Parameters

    1. bbox1 string first bounding box
    2. bbox2 string second bounding box

    Returns: boolean true if bounding boxes intersect

    Snap.path.intersection(path1, path2)

    Utility method Finds intersections of two paths

    Parameters

    1. path1 string path string
    2. path2 string path string

    Returns: array dots of intersection

    1. [
    2. {
      1. x: number x coordinate of the point,
      2. y: number y coordinate of the point,
      3. t1: number t value for segment of path1,
      4. t2: number t value for segment of path2,
      5. segment1: number order number for segment of path1,
      6. segment2: number order number for segment of path2,
      7. bez1: array eight coordinates representing beziér curve for the segment of path1,
      8. bez2: array eight coordinates representing beziér curve for the segment of path2
    3. }
    4. ]

    Snap.path.isPointInside(path, x, y)

    Utility method Returns true if given point is inside a given closed path.

    Note: fill mode doesn’t affect the result of this method.

    Parameters

    1. path string path string
    2. x number x of the point
    3. y number y of the point

    Returns: boolean true if point is inside the path

    Snap.path.getBBox(path)

    Utility method Returns the bounding box of a given path

    Parameters

    1. path string path string

    Returns: object bounding box

    1. {
      1. x: number x coordinate of the left top point of the box,
      2. y: number y coordinate of the left top point of the box,
      3. x2: number x coordinate of the right bottom point of the box,
      4. y2: number y coordinate of the right bottom point of the box,
      5. width: number width of the box,
      6. height: number height of the box
    2. }

    Snap.path.toRelative(path)

    Utility method Converts path coordinates into relative values

    Parameters

    1. path string path string

    Returns: array path string

    Snap.path.toAbsolute(path)

    Utility method Converts path coordinates into absolute values

    Parameters

    1. path string path string

    Returns: array path string

    Snap.path.toCubic(pathString)

    Utility method Converts path to a new path where all segments are cubic beziér curves

    Parameters

    1. pathString string array path string or array of segments

    Returns: array array of segments

    Snap.path.map(path, matrix)

    Transform the path string with the given matrix

    Parameters

    1. path string path string
    2. matrix object see Matrix

    Returns: string transformed path string

    Set.push()

    Adds each argument to the current set

    Returns: object original element

    Set.pop()

    Removes last element and returns it

    Returns: object element

    Set.forEach(callback, thisArg)

    Executes given function for each element in the set

    If the function returns false, the loop stops running.

    Parameters

    1. callback function function to run
    2. thisArg object context object for the callback

    Returns: object Set object

    Set.animate(…)

    Animates each element in set in sync.

    Parameters

    1. attrs object key-value pairs of destination attributes
    2. duration number duration of the animation in milliseconds
    3. easing function easing function from mina or custom
    4. callback function callback function that executes when the animation ends

    or

    Parameters

    1. animation array array of animation parameter for each element in set in format [attrs, duration, easing, callback]

    Usage

    // animate all elements in set to radius 10
    set.animate({r: 10}, 500, mina.easein);
    // or
    // animate first element to radius 10, but second to radius 20 and in different time
    set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]);

    Returns: Element the current element

    Set.remove()

    Removes all children of the set.

    Returns: object Set object

    Set.bind(…)

    Specifies how to handle a specific attribute when applied to a set.

    Parameters

    1. attr string attribute name
    2. callback function function to run

    or

    Parameters

    1. attr string attribute name
    2. element Element specific element in the set to apply the attribute to

    or

    Parameters

    1. attr string attribute name
    2. element Element specific element in the set to apply the attribute to
    3. eattr string attribute on the element to bind the attribute to

    Returns: object Set object

    Set.clear()

    Removes all elements from the set

    Set.splice(index, count, [insertion…])

    Removes range of elements from the set

    Parameters

    1. index number position of the deletion
    2. count number number of element to remove
    3. insertion… object elements to insert

    Returns: object set elements that were deleted

    Set.exclude(element)

    Removes given element from the set

    Parameters

    1. element object element to remove

    Returns: boolean true if object was found and removed from the set

    undefined

    Inserts set elements after given element.

    Parameters

    1. element object set will be inserted after this element

    Returns: object Set object

    Set.insertAfter()

    Creates a clone of the set.

    Returns: object New Set object

    Snap.Set

    Set constructor.

    Snap.set()

    Creates a set and fills it with list of arguments.

    Returns: object New Set object

    var r = paper.rect(0, 0, 10, 10),
        s1 = Snap.set(), // empty set
        s2 = Snap.set(r, paper.circle(100, 100, 20)); // prefilled set

    Snap.mui()

    Contain Material UI colours.

    Snap().rect(0, 0, 10, 10).attr({fill: Snap.mui.deeppurple, stroke: Snap.mui.amber[600]});
    For colour reference: https://www.materialui.co.

    Snap.flat

    Contain Flat UI colours.

    Snap().rect(0, 0, 10, 10).attr({fill: Snap.flat.carrot, stroke: Snap.flat.wetasphalt});
    For colour reference: https://www.materialui.co.

    Snap.importMUIColors()

    Imports Material UI colours into global object.

    Snap.importMUIColors();
    Snap().rect(0, 0, 10, 10).attr({fill: deeppurple, stroke: amber[600]});
    For colour reference: https://www.materialui.co.
    ================================================ FILE: dr.json ================================================ { "title": "Snap.svg", "output": "doc/reference.html", "template": "template.dot", "files": [{ "url": "src/svg.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js" }, { "url": "src/element.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/element.js" }, { "url": "src/attr.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/attr.js" }, { "url": "src/animation.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/animation.js" }, { "url": "src/matrix.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/matrix.js" }, { "url": "src/paper.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/paper.js" }, { "url": "src/class.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/class.js" }, { "url": "src/equal.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/equal.js" }, { "url": "src/mina.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/mina.js" }, { "url": "src/filter.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/filter.js" }, { "url": "src/mouse.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/mouse.js" }, { "url": "src/path.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js" }, { "url": "src/set.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/set.js" }, { "url": "src/colors.js", "link": "https://github.com/adobe-webplatform/Snap.svg/blob/master/src/colors.js" }] } ================================================ FILE: history.md ================================================ #0.5.1 * Bug fix #0.5.0 * Added color palettes for Material and FlatUI * Added methods for gradients: `Element.stops()`, `Element.addStop()`, `Element.setStops()` * Fixed matrix splitting for better animation of matrices` * Various bug fixes * Better integration of tests and ESlint #0.4.1 * Bug fixes. #0.4.0 * Moved class and element related code into separate plugins * Added `Element.align()` and `Element.getAlign()` methods * Added animation support for `viewBox` * Added support for `` * Added method `Paper.toDataURL()` * Added method `Snap.closest()` * Added methods to work with degrees instead of radians: `Snap.sin()`, `Snap.cos()`, `Snap.tan()`, `Snap.asin()`, `Snap.acos()`, `Snap.atan()` and `Snap.atan2()` * Added methods `Snap.len()`, `Snap.len2()` and `Snap.closestPoint()` * Added methods `Element.children()` and `Element.toJSON()` * Various bug fixes #0.3.0 * Added `.addClass()`, `.removeClass()`, `.toggleClass()` and `.hasClass()` APIs * Added `Paper.mask()`, `Paper.ptrn()`, `Paper.use()`, `Paper.svg()` * Mask & pattern elements are sharing paper methods (just like group) * Added `Set.bind()` method * Added syncronisation for `Set.animate()` * Added opacity to the shadow filter * Added ability to specify attributes as `"+=10"` or `"-=1em"` or `"*=2"` * Fix negative scale * Fix for `path2curve` * Fixed shared `` issue * Various bug fixes #0.2.0 * Added support for text path * Added `getBBox` method to the paper object * Added `Element.appendTo()` and `Element.prependTo()` * Added `getElementByPoint()` * Added `Set.remove()` method * Get rid of internal SVG parser in favor of the browser * Fix for `xlink:href` setting for images * Fix `Element.animate()` * Fix for animate and stroke-dashoffset * Absolute transforms fix * Fix for animation of SVG transformations, matrices and polygon points * Various bug fixes #0.1.0 * Initial release ================================================ FILE: package.json ================================================ { "name": "snapsvg", "version": "0.5.1", "description": "JavaScript Vector Library", "main": "./dist/snap.svg.js", "repository": { "type": "git", "url": "git@github.com:adobe-webplatform/Snap.svg.git" }, "author": "Dmitry Baranovskiy", "license": "Apache-2.0", "dependencies": { "eve": "~0.5.1" }, "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-uglify": "~0.2.0", "grunt-contrib-concat": "~0.3.0", "grunt-exec": "~0.4.2", "grunt-prettify": "0.4.0", "mocha": "*", "expect.js": "*", "eslint": "*", "dr.js": "~0.1.0" }, "eslintConfig": { "globals": { "window": true, "console": true }, "rules": { "block-scoped-var": 0, "comma-dangle": 0, "no-extra-parens": 1, "no-shadow": 0, "consistent-return": 0, "eqeqeq": 0, "no-new-wrappers": 0, "no-sequences": 1, "radix": 2, "new-parens": 0, "no-underscore-dangle": 0, "no-path-concat": 0, "strict": 0, "camelcase": 0, "no-extend-native": 0, "no-loop-func": 0, "new-cap": 0, "no-unused-expressions": 0, "no-mixed-requires": 0, "quotes": [ 1, "double", "avoid-escape" ] } } } ================================================ FILE: src/align.js ================================================ // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var box = Snap._.box, is = Snap.is, firstLetter = /^[^a-z]*([tbmlrc])/i, toString = function () { return "T" + this.dx + "," + this.dy; }; /*\ * Element.getAlign [ method ] ** * Returns shift needed to align the element relatively to given element. * If no elements specified, parent `` container will be used. - el (object) @optional alignment element - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string > Usage | el.transform(el.getAlign(el2, "top")); * or | var dy = el.getAlign(el2, "top").dy; \*/ Element.prototype.getAlign = function (el, way) { if (way == null && is(el, "string")) { way = el; el = null; } el = el || this.paper; var bx = el.getBBox ? el.getBBox() : box(el), bb = this.getBBox(), out = {}; way = way && way.match(firstLetter); way = way ? way[1].toLowerCase() : "c"; switch (way) { case "t": out.dx = 0; out.dy = bx.y - bb.y; break; case "b": out.dx = 0; out.dy = bx.y2 - bb.y2; break; case "m": out.dx = 0; out.dy = bx.cy - bb.cy; break; case "l": out.dx = bx.x - bb.x; out.dy = 0; break; case "r": out.dx = bx.x2 - bb.x2; out.dy = 0; break; default: out.dx = bx.cx - bb.cx; out.dy = 0; break; } out.toString = toString; return out; }; /*\ * Element.align [ method ] ** * Aligns the element relatively to given one via transformation. * If no elements specified, parent `` container will be used. - el (object) @optional alignment element - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` = (object) this element > Usage | el.align(el2, "top"); * or | el.align("middle"); \*/ Element.prototype.align = function (el, way) { return this.transform("..." + this.getAlign(el, way)); }; }); ================================================ FILE: src/amd-banner.js ================================================ (function (glob, factory) { // AMD support if (typeof define == "function" && define.amd) { // Define as an anonymous module define(["eve"], function (eve) { return factory(glob, eve); }); } else if (typeof exports != "undefined") { // Next for Node.js or CommonJS var eve = require("eve"); module.exports = factory(glob, eve); } else { // Browser globals (glob is window) // Snap adds itself to window factory(glob, glob.eve); } }(window || this, function (window, eve) { ================================================ FILE: src/amd-footer.js ================================================ return Snap; })); ================================================ FILE: src/animation.js ================================================ // Copyright (c) 2016 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var elproto = Element.prototype, is = Snap.is, Str = String, has = "hasOwnProperty"; function slice(from, to, f) { return function (arr) { var res = arr.slice(from, to); if (res.length == 1) { res = res[0]; } return f ? f(res) : res; }; } var Animation = function (attr, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } this.attr = attr; this.dur = ms; easing && (this.easing = easing); callback && (this.callback = callback); }; Snap._.Animation = Animation; /*\ * Snap.animation [ method ] ** * Creates an animation object ** - attr (object) attributes of final destination - duration (number) duration of the animation, in milliseconds - easing (function) #optional one of easing functions of @mina or custom one - callback (function) #optional callback function that fires when animation ends = (object) animation object \*/ Snap.animation = function (attr, ms, easing, callback) { return new Animation(attr, ms, easing, callback); }; /*\ * Element.inAnim [ method ] ** * Returns a set of animations that may be able to manipulate the current element ** = (object) in format: o { o anim (object) animation object, o mina (object) @mina object, o curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished, o status (function) gets or sets the status of the animation, o stop (function) stops the animation o } \*/ elproto.inAnim = function () { var el = this, res = []; for (var id in el.anims) if (el.anims[has](id)) { (function (a) { res.push({ anim: new Animation(a._attrs, a.dur, a.easing, a._callback), mina: a, curStatus: a.status(), status: function (val) { return a.status(val); }, stop: function () { a.stop(); } }); }(el.anims[id])); } return res; }; /*\ * Snap.animate [ method ] ** * Runs generic animation of one number into another with a caring function ** - from (number|array) number or array of numbers - to (number|array) number or array of numbers - setter (function) caring function that accepts one number argument - duration (number) duration, in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function to execute when animation ends = (object) animation object in @mina format o { o id (string) animation id, consider it read-only, o duration (function) gets or sets the duration of the animation, o easing (function) easing, o speed (function) gets or sets the speed of the animation, o status (function) gets or sets the status of the animation, o stop (function) stops the animation o } | var rect = Snap().rect(0, 0, 10, 10); | Snap.animate(0, 10, function (val) { | rect.attr({ | x: val | }); | }, 1000); | // in given context is equivalent to | rect.animate({x: 10}, 1000); \*/ Snap.animate = function (from, to, setter, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } var now = mina.time(), anim = mina(from, to, now, now + ms, mina.time, setter, easing); callback && eve.once("mina.finish." + anim.id, callback); return anim; }; /*\ * Element.stop [ method ] ** * Stops all the animations for the current element ** = (Element) the current element \*/ elproto.stop = function () { var anims = this.inAnim(); for (var i = 0, ii = anims.length; i < ii; i++) { anims[i].stop(); } return this; }; /*\ * Element.animate [ method ] ** * Animates the given attributes of the element ** - attrs (object) key-value pairs of destination attributes - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends = (Element) the current element \*/ elproto.animate = function (attrs, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } if (attrs instanceof Animation) { callback = attrs.callback; easing = attrs.easing; ms = attrs.dur; attrs = attrs.attr; } var fkeys = [], tkeys = [], keys = {}, from, to, f, eq, el = this; for (var key in attrs) if (attrs[has](key)) { if (el.equal) { eq = el.equal(key, Str(attrs[key])); from = eq.from; to = eq.to; f = eq.f; } else { from = +el.attr(key); to = +attrs[key]; } var len = is(from, "array") ? from.length : 1; keys[key] = slice(fkeys.length, fkeys.length + len, f); fkeys = fkeys.concat(from); tkeys = tkeys.concat(to); } var now = mina.time(), anim = mina(fkeys, tkeys, now, now + ms, mina.time, function (val) { var attr = {}; for (var key in keys) if (keys[has](key)) { attr[key] = keys[key](val); } el.attr(attr); }, easing); el.anims[anim.id] = anim; anim._attrs = attrs; anim._callback = callback; eve("snap.animcreated." + el.id, anim); eve.once("mina.finish." + anim.id, function () { eve.off("mina.*." + anim.id); delete el.anims[anim.id]; callback && callback.call(el); }); eve.once("mina.stop." + anim.id, function () { eve.off("mina.*." + anim.id); delete el.anims[anim.id]; }); return el; }; }); ================================================ FILE: src/attr.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var has = "hasOwnProperty", make = Snap._.make, wrap = Snap._.wrap, is = Snap.is, getSomeDefs = Snap._.getSomeDefs, reURLValue = /^url\((['"]?)([^)]+)\1\)$/, $ = Snap._.$, URL = Snap.url, Str = String, separator = Snap._.separator, E = ""; /*\ * Snap.deurl [ method ] ** * Unwraps path from `"url()"`. - value (string) url path = (string) unwrapped path \*/ Snap.deurl = function (value) { var res = String(value).match(reURLValue); return res ? res[2] : value; } // Attributes event handlers eve.on("snap.util.attr.mask", function (value) { if (value instanceof Element || value instanceof Fragment) { eve.stop(); if (value instanceof Fragment && value.node.childNodes.length == 1) { value = value.node.firstChild; getSomeDefs(this).appendChild(value); value = wrap(value); } if (value.type == "mask") { var mask = value; } else { mask = make("mask", getSomeDefs(this)); mask.node.appendChild(value.node); } !mask.node.id && $(mask.node, { id: mask.id }); $(this.node, { mask: URL(mask.id) }); } }); (function (clipIt) { eve.on("snap.util.attr.clip", clipIt); eve.on("snap.util.attr.clip-path", clipIt); eve.on("snap.util.attr.clipPath", clipIt); }(function (value) { if (value instanceof Element || value instanceof Fragment) { eve.stop(); var clip, node = value.node; while (node) { if (node.nodeName === "clipPath") { clip = new Element(node); break; } if (node.nodeName === "svg") { clip = undefined; break; } node = node.parentNode; } if (!clip) { clip = make("clipPath", getSomeDefs(this)); clip.node.appendChild(value.node); !clip.node.id && $(clip.node, { id: clip.id }); } $(this.node, { "clip-path": URL(clip.node.id || clip.id) }); } })); function fillStroke(name) { return function (value) { eve.stop(); if (value instanceof Fragment && value.node.childNodes.length == 1 && (value.node.firstChild.tagName == "radialGradient" || value.node.firstChild.tagName == "linearGradient" || value.node.firstChild.tagName == "pattern")) { value = value.node.firstChild; getSomeDefs(this).appendChild(value); value = wrap(value); } if (value instanceof Element) { if (value.type == "radialGradient" || value.type == "linearGradient" || value.type == "pattern") { if (!value.node.id) { $(value.node, { id: value.id }); } var fill = URL(value.node.id); } else { fill = value.attr(name); } } else { fill = Snap.color(value); if (fill.error) { var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value); if (grad) { if (!grad.node.id) { $(grad.node, { id: grad.id }); } fill = URL(grad.node.id); } else { fill = value; } } else { fill = Str(fill); } } var attrs = {}; attrs[name] = fill; $(this.node, attrs); this.node.style[name] = E; }; } eve.on("snap.util.attr.fill", fillStroke("fill")); eve.on("snap.util.attr.stroke", fillStroke("stroke")); var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i; eve.on("snap.util.grad.parse", function parseGrad(string) { string = Str(string); var tokens = string.match(gradrg); if (!tokens) { return null; } var type = tokens[1], params = tokens[2], stops = tokens[3]; params = params.split(/\s*,\s*/).map(function (el) { return +el == el ? +el : el; }); if (params.length == 1 && params[0] == 0) { params = []; } stops = stops.split("-"); stops = stops.map(function (el) { el = el.split(":"); var out = { color: el[0] }; if (el[1]) { out.offset = parseFloat(el[1]); } return out; }); var len = stops.length, start = 0, j = 0; function seed(i, end) { var step = (end - start) / (i - j); for (var k = j; k < i; k++) { stops[k].offset = +(+start + step * (k - j)).toFixed(2); } j = i; start = end; } len--; for (var i = 0; i < len; i++) if ("offset" in stops[i]) { seed(i, stops[i].offset); } stops[len].offset = stops[len].offset || 100; seed(len, stops[len].offset); return { type: type, params: params, stops: stops }; }); eve.on("snap.util.attr.d", function (value) { eve.stop(); if (is(value, "array") && is(value[0], "array")) { value = Snap.path.toString.call(value); } value = Str(value); if (value.match(/[ruo]/i)) { value = Snap.path.toAbsolute(value); } $(this.node, {d: value}); })(-1); eve.on("snap.util.attr.#text", function (value) { eve.stop(); value = Str(value); var txt = glob.doc.createTextNode(value); while (this.node.firstChild) { this.node.removeChild(this.node.firstChild); } this.node.appendChild(txt); })(-1); eve.on("snap.util.attr.path", function (value) { eve.stop(); this.attr({d: value}); })(-1); eve.on("snap.util.attr.class", function (value) { eve.stop(); this.node.className.baseVal = value; })(-1); eve.on("snap.util.attr.viewBox", function (value) { var vb; if (is(value, "object") && "x" in value) { vb = [value.x, value.y, value.width, value.height].join(" "); } else if (is(value, "array")) { vb = value.join(" "); } else { vb = value; } $(this.node, { viewBox: vb }); eve.stop(); })(-1); eve.on("snap.util.attr.transform", function (value) { this.transform(value); eve.stop(); })(-1); eve.on("snap.util.attr.r", function (value) { if (this.type == "rect") { eve.stop(); $(this.node, { rx: value, ry: value }); } })(-1); eve.on("snap.util.attr.textpath", function (value) { eve.stop(); if (this.type == "text") { var id, tp, node; if (!value && this.textPath) { tp = this.textPath; while (tp.node.firstChild) { this.node.appendChild(tp.node.firstChild); } tp.remove(); delete this.textPath; return; } if (is(value, "string")) { var defs = getSomeDefs(this), path = wrap(defs.parentNode).path(value); defs.appendChild(path.node); id = path.id; path.attr({id: id}); } else { value = wrap(value); if (value instanceof Element) { id = value.attr("id"); if (!id) { id = value.id; value.attr({id: id}); } } } if (id) { tp = this.textPath; node = this.node; if (tp) { tp.attr({"xlink:href": "#" + id}); } else { tp = $("textPath", { "xlink:href": "#" + id }); while (node.firstChild) { tp.appendChild(node.firstChild); } node.appendChild(tp); this.textPath = wrap(tp); } } } })(-1); eve.on("snap.util.attr.text", function (value) { if (this.type == "text") { var i = 0, node = this.node, tuner = function (chunk) { var out = $("tspan"); if (is(chunk, "array")) { for (var i = 0; i < chunk.length; i++) { out.appendChild(tuner(chunk[i])); } } else { out.appendChild(glob.doc.createTextNode(chunk)); } out.normalize && out.normalize(); return out; }; while (node.firstChild) { node.removeChild(node.firstChild); } var tuned = tuner(value); while (tuned.firstChild) { node.appendChild(tuned.firstChild); } } eve.stop(); })(-1); function setFontSize(value) { eve.stop(); if (value == +value) { value += "px"; } this.node.style.fontSize = value; } eve.on("snap.util.attr.fontSize", setFontSize)(-1); eve.on("snap.util.attr.font-size", setFontSize)(-1); eve.on("snap.util.getattr.transform", function () { eve.stop(); return this.transform(); })(-1); eve.on("snap.util.getattr.textpath", function () { eve.stop(); return this.textPath; })(-1); // Markers (function () { function getter(end) { return function () { eve.stop(); var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end); if (style == "none") { return style; } else { return Snap(glob.doc.getElementById(style.match(reURLValue)[1])); } }; } function setter(end) { return function (value) { eve.stop(); var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1); if (value == "" || !value) { this.node.style[name] = "none"; return; } if (value.type == "marker") { var id = value.node.id; if (!id) { $(value.node, {id: value.id}); } this.node.style[name] = URL(id); return; } }; } eve.on("snap.util.getattr.marker-end", getter("end"))(-1); eve.on("snap.util.getattr.markerEnd", getter("end"))(-1); eve.on("snap.util.getattr.marker-start", getter("start"))(-1); eve.on("snap.util.getattr.markerStart", getter("start"))(-1); eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1); eve.on("snap.util.getattr.markerMid", getter("mid"))(-1); eve.on("snap.util.attr.marker-end", setter("end"))(-1); eve.on("snap.util.attr.markerEnd", setter("end"))(-1); eve.on("snap.util.attr.marker-start", setter("start"))(-1); eve.on("snap.util.attr.markerStart", setter("start"))(-1); eve.on("snap.util.attr.marker-mid", setter("mid"))(-1); eve.on("snap.util.attr.markerMid", setter("mid"))(-1); }()); eve.on("snap.util.getattr.r", function () { if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) { eve.stop(); return $(this.node, "rx"); } })(-1); function textExtract(node) { var out = []; var children = node.childNodes; for (var i = 0, ii = children.length; i < ii; i++) { var chi = children[i]; if (chi.nodeType == 3) { out.push(chi.nodeValue); } if (chi.tagName == "tspan") { if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) { out.push(chi.firstChild.nodeValue); } else { out.push(textExtract(chi)); } } } return out; } eve.on("snap.util.getattr.text", function () { if (this.type == "text" || this.type == "tspan") { eve.stop(); var out = textExtract(this.node); return out.length == 1 ? out[0] : out; } })(-1); eve.on("snap.util.getattr.#text", function () { return this.node.textContent; })(-1); eve.on("snap.util.getattr.fill", function (internal) { if (internal) { return; } eve.stop(); var value = eve("snap.util.getattr.fill", this, true).firstDefined(); return Snap(Snap.deurl(value)) || value; })(-1); eve.on("snap.util.getattr.stroke", function (internal) { if (internal) { return; } eve.stop(); var value = eve("snap.util.getattr.stroke", this, true).firstDefined(); return Snap(Snap.deurl(value)) || value; })(-1); eve.on("snap.util.getattr.viewBox", function () { eve.stop(); var vb = $(this.node, "viewBox"); if (vb) { vb = vb.split(separator); return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]); } else { return; } })(-1); eve.on("snap.util.getattr.points", function () { var p = $(this.node, "points"); eve.stop(); if (p) { return p.split(separator); } else { return; } })(-1); eve.on("snap.util.getattr.path", function () { var p = $(this.node, "d"); eve.stop(); return p; })(-1); eve.on("snap.util.getattr.class", function () { return this.node.className.baseVal; })(-1); function getFontSize() { eve.stop(); return this.node.style.fontSize; } eve.on("snap.util.getattr.fontSize", getFontSize)(-1); eve.on("snap.util.getattr.font-size", getFontSize)(-1); }); ================================================ FILE: src/attradd.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var operators = { "+": function (x, y) { return x + y; }, "-": function (x, y) { return x - y; }, "/": function (x, y) { return x / y; }, "*": function (x, y) { return x * y; } }, Str = String, reUnit = /[a-z]+$/i, reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/; function getNumber(val) { return val; } function getUnit(unit) { return function (val) { return +val.toFixed(3) + unit; }; } eve.on("snap.util.attr", function (val) { var plus = Str(val).match(reAddon); if (plus) { var evnt = eve.nt(), name = evnt.substring(evnt.lastIndexOf(".") + 1), a = this.attr(name), atr = {}; eve.stop(); var unit = plus[3] || "", aUnit = a.match(reUnit), op = operators[plus[1]]; if (aUnit && aUnit == unit) { val = op(parseFloat(a), +plus[2]); } else { a = this.asPX(name); val = op(this.asPX(name), this.asPX(name, plus[2] + unit)); } if (isNaN(a) || isNaN(val)) { return; } atr[name] = val; this.attr(atr); } })(-10); eve.on("snap.util.equal", function (name, b) { var A, B, a = Str(this.attr(name) || ""), el = this, bplus = Str(b).match(reAddon); if (bplus) { eve.stop(); var unit = bplus[3] || "", aUnit = a.match(reUnit), op = operators[bplus[1]]; if (aUnit && aUnit == unit) { return { from: parseFloat(a), to: op(parseFloat(a), +bplus[2]), f: getUnit(aUnit) }; } else { a = this.asPX(name); return { from: a, to: op(a, this.asPX(name, bplus[2] + unit)), f: getNumber }; } } })(-10); }); ================================================ FILE: src/class.js ================================================ // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var rgNotSpace = /\S+/g, rgBadSpace = /[\t\r\n\f]/g, rgTrim = /(^\s+|\s+$)/g, Str = String, elproto = Element.prototype; /*\ * Element.addClass [ method ] ** * Adds given class name or list of class names to the element. - value (string) class name or space separated list of class names ** = (Element) original element. \*/ elproto.addClass = function (value) { var classes = Str(value || "").match(rgNotSpace) || [], elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || [], j, pos, clazz, finalValue; if (classes.length) { j = 0; while (clazz = classes[j++]) { pos = curClasses.indexOf(clazz); if (!~pos) { curClasses.push(clazz); } } finalValue = curClasses.join(" "); if (className != finalValue) { elem.className.baseVal = finalValue; } } return this; }; /*\ * Element.removeClass [ method ] ** * Removes given class name or list of class names from the element. - value (string) class name or space separated list of class names ** = (Element) original element. \*/ elproto.removeClass = function (value) { var classes = Str(value || "").match(rgNotSpace) || [], elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || [], j, pos, clazz, finalValue; if (curClasses.length) { j = 0; while (clazz = classes[j++]) { pos = curClasses.indexOf(clazz); if (~pos) { curClasses.splice(pos, 1); } } finalValue = curClasses.join(" "); if (className != finalValue) { elem.className.baseVal = finalValue; } } return this; }; /*\ * Element.hasClass [ method ] ** * Checks if the element has a given class name in the list of class names applied to it. - value (string) class name ** = (boolean) `true` if the element has given class \*/ elproto.hasClass = function (value) { var elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || []; return !!~curClasses.indexOf(value); }; /*\ * Element.toggleClass [ method ] ** * Add or remove one or more classes from the element, depending on either * the class’s presence or the value of the `flag` argument. - value (string) class name or space separated list of class names - flag (boolean) value to determine whether the class should be added or removed ** = (Element) original element. \*/ elproto.toggleClass = function (value, flag) { if (flag != null) { if (flag) { return this.addClass(value); } else { return this.removeClass(value); } } var classes = (value || "").match(rgNotSpace) || [], elem = this.node, className = elem.className.baseVal, curClasses = className.match(rgNotSpace) || [], j, pos, clazz, finalValue; j = 0; while (clazz = classes[j++]) { pos = curClasses.indexOf(clazz); if (~pos) { curClasses.splice(pos, 1); } else { curClasses.push(clazz); } } finalValue = curClasses.join(" "); if (className != finalValue) { elem.className.baseVal = finalValue; } return this; }; }); ================================================ FILE: src/colors.js ================================================ // Copyright (c) 2017 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { // Colours are from https://www.materialui.co var red = "#ffebee#ffcdd2#ef9a9a#e57373#ef5350#f44336#e53935#d32f2f#c62828#b71c1c#ff8a80#ff5252#ff1744#d50000", pink = "#FCE4EC#F8BBD0#F48FB1#F06292#EC407A#E91E63#D81B60#C2185B#AD1457#880E4F#FF80AB#FF4081#F50057#C51162", purple = "#F3E5F5#E1BEE7#CE93D8#BA68C8#AB47BC#9C27B0#8E24AA#7B1FA2#6A1B9A#4A148C#EA80FC#E040FB#D500F9#AA00FF", deeppurple = "#EDE7F6#D1C4E9#B39DDB#9575CD#7E57C2#673AB7#5E35B1#512DA8#4527A0#311B92#B388FF#7C4DFF#651FFF#6200EA", indigo = "#E8EAF6#C5CAE9#9FA8DA#7986CB#5C6BC0#3F51B5#3949AB#303F9F#283593#1A237E#8C9EFF#536DFE#3D5AFE#304FFE", blue = "#E3F2FD#BBDEFB#90CAF9#64B5F6#64B5F6#2196F3#1E88E5#1976D2#1565C0#0D47A1#82B1FF#448AFF#2979FF#2962FF", lightblue = "#E1F5FE#B3E5FC#81D4FA#4FC3F7#29B6F6#03A9F4#039BE5#0288D1#0277BD#01579B#80D8FF#40C4FF#00B0FF#0091EA", cyan = "#E0F7FA#B2EBF2#80DEEA#4DD0E1#26C6DA#00BCD4#00ACC1#0097A7#00838F#006064#84FFFF#18FFFF#00E5FF#00B8D4", teal = "#E0F2F1#B2DFDB#80CBC4#4DB6AC#26A69A#009688#00897B#00796B#00695C#004D40#A7FFEB#64FFDA#1DE9B6#00BFA5", green = "#E8F5E9#C8E6C9#A5D6A7#81C784#66BB6A#4CAF50#43A047#388E3C#2E7D32#1B5E20#B9F6CA#69F0AE#00E676#00C853", lightgreen = "#F1F8E9#DCEDC8#C5E1A5#AED581#9CCC65#8BC34A#7CB342#689F38#558B2F#33691E#CCFF90#B2FF59#76FF03#64DD17", lime = "#F9FBE7#F0F4C3#E6EE9C#DCE775#D4E157#CDDC39#C0CA33#AFB42B#9E9D24#827717#F4FF81#EEFF41#C6FF00#AEEA00", yellow = "#FFFDE7#FFF9C4#FFF59D#FFF176#FFEE58#FFEB3B#FDD835#FBC02D#F9A825#F57F17#FFFF8D#FFFF00#FFEA00#FFD600", amber = "#FFF8E1#FFECB3#FFE082#FFD54F#FFCA28#FFC107#FFB300#FFA000#FF8F00#FF6F00#FFE57F#FFD740#FFC400#FFAB00", orange = "#FFF3E0#FFE0B2#FFCC80#FFB74D#FFA726#FF9800#FB8C00#F57C00#EF6C00#E65100#FFD180#FFAB40#FF9100#FF6D00", deeporange = "#FBE9E7#FFCCBC#FFAB91#FF8A65#FF7043#FF5722#F4511E#E64A19#D84315#BF360C#FF9E80#FF6E40#FF3D00#DD2C00", brown = "#EFEBE9#D7CCC8#BCAAA4#A1887F#8D6E63#795548#6D4C41#5D4037#4E342E#3E2723", grey = "#FAFAFA#F5F5F5#EEEEEE#E0E0E0#BDBDBD#9E9E9E#757575#616161#424242#212121", bluegrey = "#ECEFF1#CFD8DC#B0BEC5#90A4AE#78909C#607D8B#546E7A#455A64#37474F#263238"; /*\ * Snap.mui [ property ] ** * Contain Material UI colours. | Snap().rect(0, 0, 10, 10).attr({fill: Snap.mui.deeppurple, stroke: Snap.mui.amber[600]}); # For colour reference: https://www.materialui.co. \*/ Snap.mui = {}; /*\ * Snap.flat [ property ] ** * Contain Flat UI colours. | Snap().rect(0, 0, 10, 10).attr({fill: Snap.flat.carrot, stroke: Snap.flat.wetasphalt}); # For colour reference: https://www.materialui.co. \*/ Snap.flat = {}; function saveColor(colors) { colors = colors.split(/(?=#)/); var color = new String(colors[5]); color[50] = colors[0]; color[100] = colors[1]; color[200] = colors[2]; color[300] = colors[3]; color[400] = colors[4]; color[500] = colors[5]; color[600] = colors[6]; color[700] = colors[7]; color[800] = colors[8]; color[900] = colors[9]; if (colors[10]) { color.A100 = colors[10]; color.A200 = colors[11]; color.A400 = colors[12]; color.A700 = colors[13]; } return color; } Snap.mui.red = saveColor(red); Snap.mui.pink = saveColor(pink); Snap.mui.purple = saveColor(purple); Snap.mui.deeppurple = saveColor(deeppurple); Snap.mui.indigo = saveColor(indigo); Snap.mui.blue = saveColor(blue); Snap.mui.lightblue = saveColor(lightblue); Snap.mui.cyan = saveColor(cyan); Snap.mui.teal = saveColor(teal); Snap.mui.green = saveColor(green); Snap.mui.lightgreen = saveColor(lightgreen); Snap.mui.lime = saveColor(lime); Snap.mui.yellow = saveColor(yellow); Snap.mui.amber = saveColor(amber); Snap.mui.orange = saveColor(orange); Snap.mui.deeporange = saveColor(deeporange); Snap.mui.brown = saveColor(brown); Snap.mui.grey = saveColor(grey); Snap.mui.bluegrey = saveColor(bluegrey); Snap.flat.turquoise = "#1abc9c"; Snap.flat.greensea = "#16a085"; Snap.flat.sunflower = "#f1c40f"; Snap.flat.orange = "#f39c12"; Snap.flat.emerland = "#2ecc71"; Snap.flat.nephritis = "#27ae60"; Snap.flat.carrot = "#e67e22"; Snap.flat.pumpkin = "#d35400"; Snap.flat.peterriver = "#3498db"; Snap.flat.belizehole = "#2980b9"; Snap.flat.alizarin = "#e74c3c"; Snap.flat.pomegranate = "#c0392b"; Snap.flat.amethyst = "#9b59b6"; Snap.flat.wisteria = "#8e44ad"; Snap.flat.clouds = "#ecf0f1"; Snap.flat.silver = "#bdc3c7"; Snap.flat.wetasphalt = "#34495e"; Snap.flat.midnightblue = "#2c3e50"; Snap.flat.concrete = "#95a5a6"; Snap.flat.asbestos = "#7f8c8d"; /*\ * Snap.importMUIColors [ method ] ** * Imports Material UI colours into global object. | Snap.importMUIColors(); | Snap().rect(0, 0, 10, 10).attr({fill: deeppurple, stroke: amber[600]}); # For colour reference: https://www.materialui.co. \*/ Snap.importMUIColors = function () { for (var color in Snap.mui) { if (Snap.mui.hasOwnProperty(color)) { window[color] = Snap.mui[color]; } } }; }); ================================================ FILE: src/copy.js ================================================ // Snap.svg @VERSION // // Copyright (c) 2013 – 2017 Adobe Systems Incorporated. All rights reserved. // // 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. // // build: @DATE ================================================ FILE: src/element.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var elproto = Element.prototype, is = Snap.is, Str = String, unit2px = Snap._unit2px, $ = Snap._.$, make = Snap._.make, getSomeDefs = Snap._.getSomeDefs, has = "hasOwnProperty", wrap = Snap._.wrap; /*\ * Element.getBBox [ method ] ** * Returns the bounding box descriptor for the given element ** = (object) bounding box descriptor: o { o cx: (number) x of the center, o cy: (number) x of the center, o h: (number) height, o height: (number) height, o path: (string) path command for the box, o r0: (number) radius of a circle that fully encloses the box, o r1: (number) radius of the smallest circle that can be enclosed, o r2: (number) radius of the largest circle that can be enclosed, o vb: (string) box as a viewbox command, o w: (number) width, o width: (number) width, o x2: (number) x of the right side, o x: (number) x of the left side, o y2: (number) y of the bottom edge, o y: (number) y of the top edge o } \*/ elproto.getBBox = function (isWithoutTransform) { if (this.type == "tspan") { return Snap._.box(this.node.getClientRects().item(0)); } if (!Snap.Matrix || !Snap.path) { return this.node.getBBox(); } var el = this, m = new Snap.Matrix; if (el.removed) { return Snap._.box(); } while (el.type == "use") { if (!isWithoutTransform) { m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0)); } if (el.original) { el = el.original; } else { var href = el.attr("xlink:href"); el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1)); } } var _ = el._, pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt; try { if (isWithoutTransform) { _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox()); return Snap._.box(_.bboxwt); } else { el.realPath = pathfinder(el); el.matrix = el.transform().localMatrix; _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix))); return Snap._.box(_.bbox); } } catch (e) { // Firefox doesn’t give you bbox of hidden element return Snap._.box(); } }; var propString = function () { return this.string; }; function extractTransform(el, tstr) { if (tstr == null) { var doReturn = true; if (el.type == "linearGradient" || el.type == "radialGradient") { tstr = el.node.getAttribute("gradientTransform"); } else if (el.type == "pattern") { tstr = el.node.getAttribute("patternTransform"); } else { tstr = el.node.getAttribute("transform"); } if (!tstr) { return new Snap.Matrix; } tstr = Snap._.svgTransform2string(tstr); } else { if (!Snap._.rgTransform.test(tstr)) { tstr = Snap._.svgTransform2string(tstr); } else { tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || ""); } if (is(tstr, "array")) { tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr); } el._.transform = tstr; } var m = Snap._.transform2matrix(tstr, el.getBBox(1)); if (doReturn) { return m; } else { el.matrix = m; } } /*\ * Element.transform [ method ] ** * Gets or sets transformation of the element ** - tstr (string) transform string in Snap or SVG format = (Element) the current element * or = (object) transformation descriptor: o { o string (string) transform string, o globalMatrix (Matrix) matrix of all transformations applied to element or its parents, o localMatrix (Matrix) matrix of transformations applied only to the element, o diffMatrix (Matrix) matrix of difference between global and local transformations, o global (string) global transformation as string, o local (string) local transformation as string, o toString (function) returns `string` property o } \*/ elproto.transform = function (tstr) { var _ = this._; if (tstr == null) { var papa = this, global = new Snap.Matrix(this.node.getCTM()), local = extractTransform(this), ms = [local], m = new Snap.Matrix, i, localString = local.toTransformString(), string = Str(local) == Str(this.matrix) ? Str(_.transform) : localString; while (papa.type != "svg" && (papa = papa.parent())) { ms.push(extractTransform(papa)); } i = ms.length; while (i--) { m.add(ms[i]); } return { string: string, globalMatrix: global, totalMatrix: m, localMatrix: local, diffMatrix: global.clone().add(local.invert()), global: global.toTransformString(), total: m.toTransformString(), local: localString, toString: propString }; } if (tstr instanceof Snap.Matrix) { this.matrix = tstr; this._.transform = tstr.toTransformString(); } else { extractTransform(this, tstr); } if (this.node) { if (this.type == "linearGradient" || this.type == "radialGradient") { $(this.node, {gradientTransform: this.matrix}); } else if (this.type == "pattern") { $(this.node, {patternTransform: this.matrix}); } else { $(this.node, {transform: this.matrix}); } } return this; }; /*\ * Element.parent [ method ] ** * Returns the element's parent ** = (Element) the parent element \*/ elproto.parent = function () { return wrap(this.node.parentNode); }; /*\ * Element.append [ method ] ** * Appends the given element to current one ** - el (Element|Set) element to append = (Element) the parent element \*/ /*\ * Element.add [ method ] ** * See @Element.append \*/ elproto.append = elproto.add = function (el) { if (el) { if (el.type == "set") { var it = this; el.forEach(function (el) { it.add(el); }); return this; } el = wrap(el); this.node.appendChild(el.node); el.paper = this.paper; } return this; }; /*\ * Element.appendTo [ method ] ** * Appends the current element to the given one ** - el (Element) parent element to append to = (Element) the child element \*/ elproto.appendTo = function (el) { if (el) { el = wrap(el); el.append(this); } return this; }; /*\ * Element.prepend [ method ] ** * Prepends the given element to the current one ** - el (Element) element to prepend = (Element) the parent element \*/ elproto.prepend = function (el) { if (el) { if (el.type == "set") { var it = this, first; el.forEach(function (el) { if (first) { first.after(el); } else { it.prepend(el); } first = el; }); return this; } el = wrap(el); var parent = el.parent(); this.node.insertBefore(el.node, this.node.firstChild); this.add && this.add(); el.paper = this.paper; this.parent() && this.parent().add(); parent && parent.add(); } return this; }; /*\ * Element.prependTo [ method ] ** * Prepends the current element to the given one ** - el (Element) parent element to prepend to = (Element) the child element \*/ elproto.prependTo = function (el) { el = wrap(el); el.prepend(this); return this; }; /*\ * Element.before [ method ] ** * Inserts given element before the current one ** - el (Element) element to insert = (Element) the parent element \*/ elproto.before = function (el) { if (el.type == "set") { var it = this; el.forEach(function (el) { var parent = el.parent(); it.node.parentNode.insertBefore(el.node, it.node); parent && parent.add(); }); this.parent().add(); return this; } el = wrap(el); var parent = el.parent(); this.node.parentNode.insertBefore(el.node, this.node); this.parent() && this.parent().add(); parent && parent.add(); el.paper = this.paper; return this; }; /*\ * Element.after [ method ] ** * Inserts given element after the current one ** - el (Element) element to insert = (Element) the parent element \*/ elproto.after = function (el) { el = wrap(el); var parent = el.parent(); if (this.node.nextSibling) { this.node.parentNode.insertBefore(el.node, this.node.nextSibling); } else { this.node.parentNode.appendChild(el.node); } this.parent() && this.parent().add(); parent && parent.add(); el.paper = this.paper; return this; }; /*\ * Element.insertBefore [ method ] ** * Inserts the element after the given one ** - el (Element) element next to whom insert to = (Element) the parent element \*/ elproto.insertBefore = function (el) { el = wrap(el); var parent = this.parent(); el.node.parentNode.insertBefore(this.node, el.node); this.paper = el.paper; parent && parent.add(); el.parent() && el.parent().add(); return this; }; /*\ * Element.insertAfter [ method ] ** * Inserts the element after the given one ** - el (Element) element next to whom insert to = (Element) the parent element \*/ elproto.insertAfter = function (el) { el = wrap(el); var parent = this.parent(); el.node.parentNode.insertBefore(this.node, el.node.nextSibling); this.paper = el.paper; parent && parent.add(); el.parent() && el.parent().add(); return this; }; /*\ * Element.remove [ method ] ** * Removes element from the DOM = (Element) the detached element \*/ elproto.remove = function () { var parent = this.parent(); this.node.parentNode && this.node.parentNode.removeChild(this.node); delete this.paper; this.removed = true; parent && parent.add(); return this; }; /*\ * Element.select [ method ] ** * Gathers the nested @Element matching the given set of CSS selectors ** - query (string) CSS selector = (Element) result of query selection \*/ elproto.select = function (query) { return wrap(this.node.querySelector(query)); }; /*\ * Element.selectAll [ method ] ** * Gathers nested @Element objects matching the given set of CSS selectors ** - query (string) CSS selector = (Set|array) result of query selection \*/ elproto.selectAll = function (query) { var nodelist = this.node.querySelectorAll(query), set = (Snap.set || Array)(); for (var i = 0; i < nodelist.length; i++) { set.push(wrap(nodelist[i])); } return set; }; /*\ * Element.asPX [ method ] ** * Returns given attribute of the element as a `px` value (not %, em, etc.) ** - attr (string) attribute name - value (string) #optional attribute value = (Element) result of query selection \*/ elproto.asPX = function (attr, value) { if (value == null) { value = this.attr(attr); } return +unit2px(this, attr, value); }; // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned instantiates. It's a part of SVG with which ordinary web developers may be least familiar. /*\ * Element.use [ method ] ** * Creates a `` element linked to the current element ** = (Element) the `` element \*/ elproto.use = function () { var use, id = this.node.id; if (!id) { id = this.id; $(this.node, { id: id }); } if (this.type == "linearGradient" || this.type == "radialGradient" || this.type == "pattern") { use = make(this.type, this.node.parentNode); } else { use = make("use", this.node.parentNode); } $(use.node, { "xlink:href": "#" + id }); use.original = this; return use; }; function fixids(el) { var els = el.selectAll("*"), it, url = /^\s*url\(("|'|)(.*)\1\)\s*$/, ids = [], uses = {}; function urltest(it, name) { var val = $(it.node, name); val = val && val.match(url); val = val && val[2]; if (val && val.charAt() == "#") { val = val.substring(1); } else { return; } if (val) { uses[val] = (uses[val] || []).concat(function (id) { var attr = {}; attr[name] = Snap.url(id); $(it.node, attr); }); } } function linktest(it) { var val = $(it.node, "xlink:href"); if (val && val.charAt() == "#") { val = val.substring(1); } else { return; } if (val) { uses[val] = (uses[val] || []).concat(function (id) { it.attr("xlink:href", "#" + id); }); } } for (var i = 0, ii = els.length; i < ii; i++) { it = els[i]; urltest(it, "fill"); urltest(it, "stroke"); urltest(it, "filter"); urltest(it, "mask"); urltest(it, "clip-path"); linktest(it); var oldid = $(it.node, "id"); if (oldid) { $(it.node, {id: it.id}); ids.push({ old: oldid, id: it.id }); } } for (i = 0, ii = ids.length; i < ii; i++) { var fs = uses[ids[i].old]; if (fs) { for (var j = 0, jj = fs.length; j < jj; j++) { fs[j](ids[i].id); } } } } /*\ * Element.clone [ method ] ** * Creates a clone of the element and inserts it after the element ** = (Element) the clone \*/ elproto.clone = function () { var clone = wrap(this.node.cloneNode(true)); if ($(clone.node, "id")) { $(clone.node, {id: clone.id}); } fixids(clone); clone.insertAfter(this); return clone; }; /*\ * Element.toDefs [ method ] ** * Moves element to the shared `` area ** = (Element) the element \*/ elproto.toDefs = function () { var defs = getSomeDefs(this); defs.appendChild(this.node); return this; }; /*\ * Element.toPattern [ method ] ** * Creates a `` element from the current element ** * To create a pattern you have to specify the pattern rect: - x (string|number) - y (string|number) - width (string|number) - height (string|number) = (Element) the `` element * You can use pattern later on as an argument for `fill` attribute: | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ | fill: "none", | stroke: "#bada55", | strokeWidth: 5 | }).pattern(0, 0, 10, 10), | c = paper.circle(200, 200, 100); | c.attr({ | fill: p | }); \*/ elproto.pattern = elproto.toPattern = function (x, y, width, height) { var p = make("pattern", getSomeDefs(this)); if (x == null) { x = this.getBBox(); } if (is(x, "object") && "x" in x) { y = x.y; width = x.width; height = x.height; x = x.x; } $(p.node, { x: x, y: y, width: width, height: height, patternUnits: "userSpaceOnUse", id: p.id, viewBox: [x, y, width, height].join(" ") }); p.node.appendChild(this.node); return p; }; // SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path. // SIERRA Element.marker(): I suggest the method should accept default reference point values. Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where? Couldn't they also be assigned default values? /*\ * Element.marker [ method ] ** * Creates a `` element from the current element ** * To create a marker you have to specify the bounding rect and reference point: - x (number) - y (number) - width (number) - height (number) - refX (number) - refY (number) = (Element) the `` element * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end. \*/ // TODO add usage for markers elproto.marker = function (x, y, width, height, refX, refY) { var p = make("marker", getSomeDefs(this)); if (x == null) { x = this.getBBox(); } if (is(x, "object") && "x" in x) { y = x.y; width = x.width; height = x.height; refX = x.refX || x.cx; refY = x.refY || x.cy; x = x.x; } $(p.node, { viewBox: [x, y, width, height].join(" "), markerWidth: width, markerHeight: height, orient: "auto", refX: refX || 0, refY: refY || 0, id: p.id }); p.node.appendChild(this.node); return p; }; var eldata = {}; /*\ * Element.data [ method ] ** * Adds or retrieves given value associated with given key. (Don’t confuse * with `data-` attributes) * * See also @Element.removeData - key (string) key to store data - value (any) #optional value to store = (object) @Element * or, if value is not specified: = (any) value > Usage | for (var i = 0, i < 5, i++) { | paper.circle(10 + 15 * i, 10, 10) | .attr({fill: "#000"}) | .data("i", i) | .click(function () { | alert(this.data("i")); | }); | } \*/ elproto.data = function (key, value) { var data = eldata[this.id] = eldata[this.id] || {}; if (arguments.length == 0){ eve("snap.data.get." + this.id, this, data, null); return data; } if (arguments.length == 1) { if (Snap.is(key, "object")) { for (var i in key) if (key[has](i)) { this.data(i, key[i]); } return this; } eve("snap.data.get." + this.id, this, data[key], key); return data[key]; } data[key] = value; eve("snap.data.set." + this.id, this, value, key); return this; }; /*\ * Element.removeData [ method ] ** * Removes value associated with an element by given key. * If key is not provided, removes all the data of the element. - key (string) #optional key = (object) @Element \*/ elproto.removeData = function (key) { if (key == null) { eldata[this.id] = {}; } else { eldata[this.id] && delete eldata[this.id][key]; } return this; }; /*\ * Element.outerSVG [ method ] ** * Returns SVG code for the element, equivalent to HTML's `outerHTML`. * * See also @Element.innerSVG = (string) SVG code for the element \*/ /*\ * Element.toString [ method ] ** * See @Element.outerSVG \*/ elproto.outerSVG = elproto.toString = toString(1); /*\ * Element.innerSVG [ method ] ** * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML` = (string) SVG code for the element \*/ elproto.innerSVG = toString(); function toString(type) { return function () { var res = type ? "<" + this.type : "", attr = this.node.attributes, chld = this.node.childNodes; if (type) { for (var i = 0, ii = attr.length; i < ii; i++) { res += " " + attr[i].name + '="' + attr[i].value.replace(/"/g, '\\"') + '"'; } } if (chld.length) { type && (res += ">"); for (i = 0, ii = chld.length; i < ii; i++) { if (chld[i].nodeType == 3) { res += chld[i].nodeValue; } else if (chld[i].nodeType == 1) { res += wrap(chld[i]).toString(); } } type && (res += ""); } else { type && (res += "/>"); } return res; }; } elproto.toDataURL = function () { if (window && window.btoa) { var bb = this.getBBox(), svg = Snap.format('{contents}', { x: +bb.x.toFixed(3), y: +bb.y.toFixed(3), width: +bb.width.toFixed(3), height: +bb.height.toFixed(3), contents: this.outerSVG() }); return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg))); } }; /*\ * Fragment.select [ method ] ** * See @Element.select \*/ Fragment.prototype.select = elproto.select; /*\ * Fragment.selectAll [ method ] ** * See @Element.selectAll \*/ Fragment.prototype.selectAll = elproto.selectAll; }); ================================================ FILE: src/equal.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var names = {}, reUnit = /[%a-z]+$/i, Str = String; names.stroke = names.fill = "colour"; function getEmpty(item) { var l = item[0]; switch (l.toLowerCase()) { case "t": return [l, 0, 0]; case "m": return [l, 1, 0, 0, 1, 0, 0]; case "r": if (item.length == 4) { return [l, 0, item[2], item[3]]; } else { return [l, 0]; } case "s": if (item.length == 5) { return [l, 1, 1, item[3], item[4]]; } else if (item.length == 3) { return [l, 1, 1]; } else { return [l, 1]; } } } function equaliseTransform(t1, t2, getBBox) { t1 = t1 || new Snap.Matrix; t2 = t2 || new Snap.Matrix; t1 = Snap.parseTransformString(t1.toTransformString()) || []; t2 = Snap.parseTransformString(t2.toTransformString()) || []; var maxlength = Math.max(t1.length, t2.length), from = [], to = [], i = 0, j, jj, tt1, tt2; for (; i < maxlength; i++) { tt1 = t1[i] || getEmpty(t2[i]); tt2 = t2[i] || getEmpty(tt1); if (tt1[0] != tt2[0] || tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3]) || tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]) ) { t1 = Snap._.transform2matrix(t1, getBBox()); t2 = Snap._.transform2matrix(t2, getBBox()); from = [["m", t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]]; to = [["m", t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]]; break; } from[i] = []; to[i] = []; for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) { j in tt1 && (from[i][j] = tt1[j]); j in tt2 && (to[i][j] = tt2[j]); } } return { from: path2array(from), to: path2array(to), f: getPath(from) }; } function getNumber(val) { return val; } function getUnit(unit) { return function (val) { return +val.toFixed(3) + unit; }; } function getViewBox(val) { return val.join(" "); } function getColour(clr) { return Snap.rgb(clr[0], clr[1], clr[2], clr[3]); } function getPath(path) { var k = 0, i, ii, j, jj, out, a, b = []; for (i = 0, ii = path.length; i < ii; i++) { out = "["; a = ['"' + path[i][0] + '"']; for (j = 1, jj = path[i].length; j < jj; j++) { a[j] = "val[" + k++ + "]"; } out += a + "]"; b[i] = out; } return Function("val", "return Snap.path.toString.call([" + b + "])"); } function path2array(path) { var out = []; for (var i = 0, ii = path.length; i < ii; i++) { for (var j = 1, jj = path[i].length; j < jj; j++) { out.push(path[i][j]); } } return out; } function isNumeric(obj) { return isFinite(obj); } function arrayEqual(arr1, arr2) { if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) { return false; } return arr1.toString() == arr2.toString(); } Element.prototype.equal = function (name, b) { return eve("snap.util.equal", this, name, b).firstDefined(); }; eve.on("snap.util.equal", function (name, b) { var A, B, a = Str(this.attr(name) || ""), el = this; if (names[name] == "colour") { A = Snap.color(a); B = Snap.color(b); return { from: [A.r, A.g, A.b, A.opacity], to: [B.r, B.g, B.b, B.opacity], f: getColour }; } if (name == "viewBox") { A = this.attr(name).vb.split(" ").map(Number); B = b.split(" ").map(Number); return { from: A, to: B, f: getViewBox }; } if (name == "transform" || name == "gradientTransform" || name == "patternTransform") { if (typeof b == "string") { b = Str(b).replace(/\.{3}|\u2026/g, a); } a = this.matrix; if (!Snap._.rgTransform.test(b)) { b = Snap._.transform2matrix(Snap._.svgTransform2string(b), this.getBBox()); } else { b = Snap._.transform2matrix(b, this.getBBox()); } return equaliseTransform(a, b, function () { return el.getBBox(1); }); } if (name == "d" || name == "path") { A = Snap.path.toCubic(a, b); return { from: path2array(A[0]), to: path2array(A[1]), f: getPath(A[0]) }; } if (name == "points") { A = Str(a).split(Snap._.separator); B = Str(b).split(Snap._.separator); return { from: A, to: B, f: function (val) { return val; } }; } if (isNumeric(a) && isNumeric(b)) { return { from: parseFloat(a), to: parseFloat(b), f: getNumber }; } var aUnit = a.match(reUnit), bUnit = Str(b).match(reUnit); if (aUnit && arrayEqual(aUnit, bUnit)) { return { from: parseFloat(a), to: parseFloat(b), f: getUnit(aUnit) }; } else { return { from: this.asPX(name), to: this.asPX(name, b), f: getNumber }; } }); }); ================================================ FILE: src/filter.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var elproto = Element.prototype, pproto = Paper.prototype, rgurl = /^\s*url\((.+)\)/, Str = String, $ = Snap._.$; Snap.filter = {}; /*\ * Paper.filter [ method ] ** * Creates a `` element ** - filstr (string) SVG fragment of filter provided as a string = (object) @Element * Note: It is recommended to use filters embedded into the page inside an empty SVG element. > Usage | var f = paper.filter(''), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ pproto.filter = function (filstr) { var paper = this; if (paper.type != "svg") { paper = paper.paper; } var f = Snap.parse(Str(filstr)), id = Snap._.id(), width = paper.node.offsetWidth, height = paper.node.offsetHeight, filter = $("filter"); $(filter, { id: id, filterUnits: "userSpaceOnUse" }); filter.appendChild(f.node); paper.defs.appendChild(filter); return new Element(filter); }; eve.on("snap.util.getattr.filter", function () { eve.stop(); var p = $(this.node, "filter"); if (p) { var match = Str(p).match(rgurl); return match && Snap.select(match[1]); } }); eve.on("snap.util.attr.filter", function (value) { if (value instanceof Element && value.type == "filter") { eve.stop(); var id = value.node.id; if (!id) { $(value.node, {id: value.id}); id = value.id; } $(this.node, { filter: Snap.url(id) }); } if (!value || value == "none") { eve.stop(); this.node.removeAttribute("filter"); } }); /*\ * Snap.filter.blur [ method ] ** * Returns an SVG markup string for the blur filter ** - x (number) amount of horizontal blur, in pixels - y (number) #optional amount of vertical blur, in pixels = (string) filter representation > Usage | var f = paper.filter(Snap.filter.blur(5, 10)), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ Snap.filter.blur = function (x, y) { if (x == null) { x = 2; } var def = y == null ? x : [x, y]; return Snap.format('\', { def: def }); }; Snap.filter.blur.toString = function () { return this(); }; /*\ * Snap.filter.shadow [ method ] ** * Returns an SVG markup string for the shadow filter ** - dx (number) #optional horizontal shift of the shadow, in pixels - dy (number) #optional vertical shift of the shadow, in pixels - blur (number) #optional amount of blur - color (string) #optional color of the shadow - opacity (number) #optional `0..1` opacity of the shadow * or - dx (number) #optional horizontal shift of the shadow, in pixels - dy (number) #optional vertical shift of the shadow, in pixels - color (string) #optional color of the shadow - opacity (number) #optional `0..1` opacity of the shadow * which makes blur default to `4`. Or - dx (number) #optional horizontal shift of the shadow, in pixels - dy (number) #optional vertical shift of the shadow, in pixels - opacity (number) #optional `0..1` opacity of the shadow = (string) filter representation > Usage | var f = paper.filter(Snap.filter.shadow(0, 2, .3)), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ Snap.filter.shadow = function (dx, dy, blur, color, opacity) { if (opacity == null) { if (color == null) { opacity = blur; blur = 4; color = "#000"; } else { opacity = color; color = blur; blur = 4; } } if (blur == null) { blur = 4; } if (opacity == null) { opacity = 1; } if (dx == null) { dx = 0; dy = 2; } if (dy == null) { dy = dx; } color = Snap.color(color); return Snap.format('', { color: color, dx: dx, dy: dy, blur: blur, opacity: opacity }); }; Snap.filter.shadow.toString = function () { return this(); }; /*\ * Snap.filter.grayscale [ method ] ** * Returns an SVG markup string for the grayscale filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.grayscale = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { a: 0.2126 + 0.7874 * (1 - amount), b: 0.7152 - 0.7152 * (1 - amount), c: 0.0722 - 0.0722 * (1 - amount), d: 0.2126 - 0.2126 * (1 - amount), e: 0.7152 + 0.2848 * (1 - amount), f: 0.0722 - 0.0722 * (1 - amount), g: 0.2126 - 0.2126 * (1 - amount), h: 0.0722 + 0.9278 * (1 - amount) }); }; Snap.filter.grayscale.toString = function () { return this(); }; /*\ * Snap.filter.sepia [ method ] ** * Returns an SVG markup string for the sepia filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.sepia = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { a: 0.393 + 0.607 * (1 - amount), b: 0.769 - 0.769 * (1 - amount), c: 0.189 - 0.189 * (1 - amount), d: 0.349 - 0.349 * (1 - amount), e: 0.686 + 0.314 * (1 - amount), f: 0.168 - 0.168 * (1 - amount), g: 0.272 - 0.272 * (1 - amount), h: 0.534 - 0.534 * (1 - amount), i: 0.131 + 0.869 * (1 - amount) }); }; Snap.filter.sepia.toString = function () { return this(); }; /*\ * Snap.filter.saturate [ method ] ** * Returns an SVG markup string for the saturate filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.saturate = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { amount: 1 - amount }); }; Snap.filter.saturate.toString = function () { return this(); }; /*\ * Snap.filter.hueRotate [ method ] ** * Returns an SVG markup string for the hue-rotate filter ** - angle (number) angle of rotation = (string) filter representation \*/ Snap.filter.hueRotate = function (angle) { angle = angle || 0; return Snap.format('', { angle: angle }); }; Snap.filter.hueRotate.toString = function () { return this(); }; /*\ * Snap.filter.invert [ method ] ** * Returns an SVG markup string for the invert filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.invert = function (amount) { if (amount == null) { amount = 1; } // return Snap.format('', { amount: amount, amount2: 1 - amount }); }; Snap.filter.invert.toString = function () { return this(); }; /*\ * Snap.filter.brightness [ method ] ** * Returns an SVG markup string for the brightness filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.brightness = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { amount: amount }); }; Snap.filter.brightness.toString = function () { return this(); }; /*\ * Snap.filter.contrast [ method ] ** * Returns an SVG markup string for the contrast filter ** - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ Snap.filter.contrast = function (amount) { if (amount == null) { amount = 1; } return Snap.format('', { amount: amount, amount2: .5 - amount / 2 }); }; Snap.filter.contrast.toString = function () { return this(); }; }); ================================================ FILE: src/matrix.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var objectToString = Object.prototype.toString, Str = String, math = Math, E = ""; function Matrix(a, b, c, d, e, f) { if (b == null && objectToString.call(a) == "[object SVGMatrix]") { this.a = a.a; this.b = a.b; this.c = a.c; this.d = a.d; this.e = a.e; this.f = a.f; return; } if (a != null) { this.a = +a; this.b = +b; this.c = +c; this.d = +d; this.e = +e; this.f = +f; } else { this.a = 1; this.b = 0; this.c = 0; this.d = 1; this.e = 0; this.f = 0; } } (function (matrixproto) { /*\ * Matrix.add [ method ] ** * Adds the given matrix to existing one - a (number) - b (number) - c (number) - d (number) - e (number) - f (number) * or - matrix (object) @Matrix \*/ matrixproto.add = function (a, b, c, d, e, f) { if (a && a instanceof Matrix) { return this.add(a.a, a.b, a.c, a.d, a.e, a.f); } var aNew = a * this.a + b * this.c, bNew = a * this.b + b * this.d; this.e += e * this.a + f * this.c; this.f += e * this.b + f * this.d; this.c = c * this.a + d * this.c; this.d = c * this.b + d * this.d; this.a = aNew; this.b = bNew; return this; }; /*\ * Matrix.multLeft [ method ] ** * Multiplies a passed affine transform to the left: M * this. - a (number) - b (number) - c (number) - d (number) - e (number) - f (number) * or - matrix (object) @Matrix \*/ Matrix.prototype.multLeft = function (a, b, c, d, e, f) { if (a && a instanceof Matrix) { return this.multLeft(a.a, a.b, a.c, a.d, a.e, a.f); } var aNew = a * this.a + c * this.b, cNew = a * this.c + c * this.d, eNew = a * this.e + c * this.f + e; this.b = b * this.a + d * this.b; this.d = b * this.c + d * this.d; this.f = b * this.e + d * this.f + f; this.a = aNew; this.c = cNew; this.e = eNew; return this; }; /*\ * Matrix.invert [ method ] ** * Returns an inverted version of the matrix = (object) @Matrix \*/ matrixproto.invert = function () { var me = this, x = me.a * me.d - me.b * me.c; return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x); }; /*\ * Matrix.clone [ method ] ** * Returns a copy of the matrix = (object) @Matrix \*/ matrixproto.clone = function () { return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f); }; /*\ * Matrix.translate [ method ] ** * Translate the matrix - x (number) horizontal offset distance - y (number) vertical offset distance \*/ matrixproto.translate = function (x, y) { this.e += x * this.a + y * this.c; this.f += x * this.b + y * this.d; return this; }; /*\ * Matrix.scale [ method ] ** * Scales the matrix - x (number) amount to be scaled, with `1` resulting in no change - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.) - cx (number) #optional horizontal origin point from which to scale - cy (number) #optional vertical origin point from which to scale * Default cx, cy is the middle point of the element. \*/ matrixproto.scale = function (x, y, cx, cy) { y == null && (y = x); (cx || cy) && this.translate(cx, cy); this.a *= x; this.b *= x; this.c *= y; this.d *= y; (cx || cy) && this.translate(-cx, -cy); return this; }; /*\ * Matrix.rotate [ method ] ** * Rotates the matrix - a (number) angle of rotation, in degrees - x (number) horizontal origin point from which to rotate - y (number) vertical origin point from which to rotate \*/ matrixproto.rotate = function (a, x, y) { a = Snap.rad(a); x = x || 0; y = y || 0; var cos = +math.cos(a).toFixed(9), sin = +math.sin(a).toFixed(9); this.add(cos, sin, -sin, cos, x, y); return this.add(1, 0, 0, 1, -x, -y); }; /*\ * Matrix.skewX [ method ] ** * Skews the matrix along the x-axis - x (number) Angle to skew along the x-axis (in degrees). \*/ matrixproto.skewX = function (x) { return this.skew(x, 0); }; /*\ * Matrix.skewY [ method ] ** * Skews the matrix along the y-axis - y (number) Angle to skew along the y-axis (in degrees). \*/ matrixproto.skewY = function (y) { return this.skew(0, y); }; /*\ * Matrix.skew [ method ] ** * Skews the matrix - y (number) Angle to skew along the y-axis (in degrees). - x (number) Angle to skew along the x-axis (in degrees). \*/ matrixproto.skew = function (x, y) { x = x || 0; y = y || 0; x = Snap.rad(x); y = Snap.rad(y); var c = math.tan(x).toFixed(9); var b = math.tan(y).toFixed(9); return this.add(1, b, c, 1, 0, 0); }; /*\ * Matrix.x [ method ] ** * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y - x (number) - y (number) = (number) x \*/ matrixproto.x = function (x, y) { return x * this.a + y * this.c + this.e; }; /*\ * Matrix.y [ method ] ** * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x - x (number) - y (number) = (number) y \*/ matrixproto.y = function (x, y) { return x * this.b + y * this.d + this.f; }; matrixproto.get = function (i) { return +this[Str.fromCharCode(97 + i)].toFixed(4); }; matrixproto.toString = function () { return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")"; }; matrixproto.offset = function () { return [this.e.toFixed(4), this.f.toFixed(4)]; }; function norm(a) { return a[0] * a[0] + a[1] * a[1]; } function normalize(a) { var mag = math.sqrt(norm(a)); a[0] && (a[0] /= mag); a[1] && (a[1] /= mag); } /*\ * Matrix.determinant [ method ] ** * Finds determinant of the given matrix. = (number) determinant \*/ matrixproto.determinant = function () { return this.a * this.d - this.b * this.c; }; /*\ * Matrix.split [ method ] ** * Splits matrix into primitive transformations = (object) in format: o dx (number) translation by x o dy (number) translation by y o scalex (number) scale by x o scaley (number) scale by y o shear (number) shear o rotate (number) rotation in deg o isSimple (boolean) could it be represented via simple transformations \*/ matrixproto.split = function () { var out = {}; // translation out.dx = this.e; out.dy = this.f; // scale and shear var row = [[this.a, this.b], [this.c, this.d]]; out.scalex = math.sqrt(norm(row[0])); normalize(row[0]); out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1]; row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear]; out.scaley = math.sqrt(norm(row[1])); normalize(row[1]); out.shear /= out.scaley; if (this.determinant() < 0) { out.scalex = -out.scalex; } // rotation var sin = row[0][1], cos = row[1][1]; if (cos < 0) { out.rotate = Snap.deg(math.acos(cos)); if (sin < 0) { out.rotate = 360 - out.rotate; } } else { out.rotate = Snap.deg(math.asin(sin)); } out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate); out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate; out.noRotation = !+out.shear.toFixed(9) && !out.rotate; return out; }; /*\ * Matrix.toTransformString [ method ] ** * Returns transform string that represents given matrix = (string) transform string \*/ matrixproto.toTransformString = function (shorter) { var s = shorter || this.split(); if (!+s.shear.toFixed(9)) { s.scalex = +s.scalex.toFixed(4); s.scaley = +s.scaley.toFixed(4); s.rotate = +s.rotate.toFixed(4); return (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) + (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E) + (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E); } else { return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)]; } }; })(Matrix.prototype); /*\ * Snap.Matrix [ method ] ** * Matrix constructor, extend on your own risk. * To create matrices use @Snap.matrix. \*/ Snap.Matrix = Matrix; /*\ * Snap.matrix [ method ] ** * Utility method ** * Returns a matrix based on the given parameters - a (number) - b (number) - c (number) - d (number) - e (number) - f (number) * or - svgMatrix (SVGMatrix) = (object) @Matrix \*/ Snap.matrix = function (a, b, c, d, e, f) { return new Matrix(a, b, c, d, e, f); }; }); ================================================ FILE: src/mina.js ================================================ // Copyright (c) 2017 Adobe Systems Incorporated. All rights reserved. // // 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. var mina = (function (eve) { var animations = {}, requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { setTimeout(callback, 16, new Date().getTime()); return true; }, requestID, isArray = Array.isArray || function (a) { return a instanceof Array || Object.prototype.toString.call(a) == "[object Array]"; }, idgen = 0, idprefix = "M" + (+new Date).toString(36), ID = function () { return idprefix + (idgen++).toString(36); }, diff = function (a, b, A, B) { if (isArray(a)) { res = []; for (var i = 0, ii = a.length; i < ii; i++) { res[i] = diff(a[i], b, A[i], B); } return res; } var dif = (A - a) / (B - b); return function (bb) { return a + dif * (bb - b); }; }, timer = Date.now || function () { return +new Date; }, sta = function (val) { var a = this; if (val == null) { return a.s; } var ds = a.s - val; a.b += a.dur * ds; a.B += a.dur * ds; a.s = val; }, speed = function (val) { var a = this; if (val == null) { return a.spd; } a.spd = val; }, duration = function (val) { var a = this; if (val == null) { return a.dur; } a.s = a.s * val / a.dur; a.dur = val; }, stopit = function () { var a = this; delete animations[a.id]; a.update(); eve("mina.stop." + a.id, a); }, pause = function () { var a = this; if (a.pdif) { return; } delete animations[a.id]; a.update(); a.pdif = a.get() - a.b; }, resume = function () { var a = this; if (!a.pdif) { return; } a.b = a.get() - a.pdif; delete a.pdif; animations[a.id] = a; frame(); }, update = function () { var a = this, res; if (isArray(a.start)) { res = []; for (var j = 0, jj = a.start.length; j < jj; j++) { res[j] = +a.start[j] + (a.end[j] - a.start[j]) * a.easing(a.s); } } else { res = +a.start + (a.end - a.start) * a.easing(a.s); } a.set(res); }, frame = function (timeStamp) { // Manual invokation? if (!timeStamp) { // Frame loop stopped? if (!requestID) { // Start frame loop... requestID = requestAnimFrame(frame); } return; } var len = 0; for (var i in animations) if (animations.hasOwnProperty(i)) { var a = animations[i], b = a.get(), res; len++; a.s = (b - a.b) / (a.dur / a.spd); if (a.s >= 1) { delete animations[i]; a.s = 1; len--; (function (a) { setTimeout(function () { eve("mina.finish." + a.id, a); }); }(a)); } a.update(); } requestID = len ? requestAnimFrame(frame) : false; }, /*\ * mina [ method ] ** * Generic animation of numbers ** - a (number) start _slave_ number - A (number) end _slave_ number - b (number) start _master_ number (start time in general case) - B (number) end _master_ number (end time in general case) - get (function) getter of _master_ number (see @mina.time) - set (function) setter of _slave_ number - easing (function) #optional easing function, default is @mina.linear = (object) animation descriptor o { o id (string) animation id, o start (number) start _slave_ number, o end (number) end _slave_ number, o b (number) start _master_ number, o s (number) animation status (0..1), o dur (number) animation duration, o spd (number) animation speed, o get (function) getter of _master_ number (see @mina.time), o set (function) setter of _slave_ number, o easing (function) easing function, default is @mina.linear, o status (function) status getter/setter, o speed (function) speed getter/setter, o duration (function) duration getter/setter, o stop (function) animation stopper o pause (function) pauses the animation o resume (function) resumes the animation o update (function) calles setter with the right value of the animation o } \*/ mina = function (a, A, b, B, get, set, easing) { var anim = { id: ID(), start: a, end: A, b: b, s: 0, dur: B - b, spd: 1, get: get, set: set, easing: easing || mina.linear, status: sta, speed: speed, duration: duration, stop: stopit, pause: pause, resume: resume, update: update }; animations[anim.id] = anim; var len = 0, i; for (i in animations) if (animations.hasOwnProperty(i)) { len++; if (len == 2) { break; } } len == 1 && frame(); return anim; }; /*\ * mina.time [ method ] ** * Returns the current time. Equivalent to: | function () { | return (new Date).getTime(); | } \*/ mina.time = timer; /*\ * mina.getById [ method ] ** * Returns an animation by its id - id (string) animation's id = (object) See @mina \*/ mina.getById = function (id) { return animations[id] || null; }; /*\ * mina.linear [ method ] ** * Default linear easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.linear = function (n) { return n; }; /*\ * mina.easeout [ method ] ** * Easeout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easeout = function (n) { return Math.pow(n, 1.7); }; /*\ * mina.easein [ method ] ** * Easein easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easein = function (n) { return Math.pow(n, .48); }; /*\ * mina.easeinout [ method ] ** * Easeinout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easeinout = function (n) { if (n == 1) { return 1; } if (n == 0) { return 0; } var q = .48 - n / 1.04, Q = Math.sqrt(.1734 + q * q), x = Q - q, X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1), y = -Q - q, Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1), t = X + Y + .5; return (1 - t) * 3 * t * t + t * t * t; }; /*\ * mina.backin [ method ] ** * Backin easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.backin = function (n) { if (n == 1) { return 1; } var s = 1.70158; return n * n * ((s + 1) * n - s); }; /*\ * mina.backout [ method ] ** * Backout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.backout = function (n) { if (n == 0) { return 0; } n = n - 1; var s = 1.70158; return n * n * ((s + 1) * n + s) + 1; }; /*\ * mina.elastic [ method ] ** * Elastic easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.elastic = function (n) { if (n == !!n) { return n; } return Math.pow(2, -10 * n) * Math.sin((n - .075) * (2 * Math.PI) / .3) + 1; }; /*\ * mina.bounce [ method ] ** * Bounce easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.bounce = function (n) { var s = 7.5625, p = 2.75, l; if (n < 1 / p) { l = s * n * n; } else { if (n < 2 / p) { n -= 1.5 / p; l = s * n * n + .75; } else { if (n < 2.5 / p) { n -= 2.25 / p; l = s * n * n + .9375; } else { n -= 2.625 / p; l = s * n * n + .984375; } } } return l; }; window.mina = mina; return mina; })(typeof eve == "undefined" ? function () {} : eve); ================================================ FILE: src/mouse.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var elproto = Element.prototype, has = "hasOwnProperty", supportsTouch = (('ontouchstart' in window) || window.TouchEvent || window.DocumentTouch && document instanceof DocumentTouch), events = [ "click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "touchstart", "touchmove", "touchend", "touchcancel" ], touchMap = { mousedown: "touchstart", mousemove: "touchmove", mouseup: "touchend" }, getScroll = function (xy, el) { var name = xy == "y" ? "scrollTop" : "scrollLeft", doc = el && el.node ? el.node.ownerDocument : glob.doc; return doc[name in doc.documentElement ? "documentElement" : "body"][name]; }, preventDefault = function () { this.returnValue = false; }, preventTouch = function () { return this.originalEvent.preventDefault(); }, stopPropagation = function () { this.cancelBubble = true; }, stopTouch = function () { return this.originalEvent.stopPropagation(); }, addEvent = function (obj, type, fn, element) { var realName = supportsTouch && touchMap[type] ? touchMap[type] : type, f = function (e) { var scrollY = getScroll("y", element), scrollX = getScroll("x", element); if (supportsTouch && touchMap[has](type)) { for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) { if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) { var olde = e; e = e.targetTouches[i]; e.originalEvent = olde; e.preventDefault = preventTouch; e.stopPropagation = stopTouch; break; } } } var x = e.clientX + scrollX, y = e.clientY + scrollY; return fn.call(element, e, x, y); }; if (type !== realName) { obj.addEventListener(type, f, false); } obj.addEventListener(realName, f, false); return function () { if (type !== realName) { obj.removeEventListener(type, f, false); } obj.removeEventListener(realName, f, false); return true; }; }, drag = [], dragMove = function (e) { var x = e.clientX, y = e.clientY, scrollY = getScroll("y"), scrollX = getScroll("x"), dragi, j = drag.length; while (j--) { dragi = drag[j]; if (supportsTouch) { var i = e.touches && e.touches.length, touch; while (i--) { touch = e.touches[i]; if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) { x = touch.clientX; y = touch.clientY; (e.originalEvent ? e.originalEvent : e).preventDefault(); break; } } } else { e.preventDefault(); } var node = dragi.el.node, o, next = node.nextSibling, parent = node.parentNode, display = node.style.display; // glob.win.opera && parent.removeChild(node); // node.style.display = "none"; // o = dragi.el.paper.getElementByPoint(x, y); // node.style.display = display; // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node)); // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o); x += scrollX; y += scrollY; eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e); } }, dragUp = function (e) { Snap.unmousemove(dragMove).unmouseup(dragUp); var i = drag.length, dragi; while (i--) { dragi = drag[i]; dragi.el._drag = {}; eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e); eve.off("snap.drag.*." + dragi.el.id); } drag = []; }; /*\ * Element.click [ method ] ** * Adds a click event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unclick [ method ] ** * Removes a click event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.dblclick [ method ] ** * Adds a double click event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.undblclick [ method ] ** * Removes a double click event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mousedown [ method ] ** * Adds a mousedown event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmousedown [ method ] ** * Removes a mousedown event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mousemove [ method ] ** * Adds a mousemove event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmousemove [ method ] ** * Removes a mousemove event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mouseout [ method ] ** * Adds a mouseout event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmouseout [ method ] ** * Removes a mouseout event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mouseover [ method ] ** * Adds a mouseover event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmouseover [ method ] ** * Removes a mouseover event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.mouseup [ method ] ** * Adds a mouseup event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.unmouseup [ method ] ** * Removes a mouseup event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchstart [ method ] ** * Adds a touchstart event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchstart [ method ] ** * Removes a touchstart event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchmove [ method ] ** * Adds a touchmove event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchmove [ method ] ** * Removes a touchmove event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchend [ method ] ** * Adds a touchend event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchend [ method ] ** * Removes a touchend event handler from the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.touchcancel [ method ] ** * Adds a touchcancel event handler to the element - handler (function) handler for the event = (object) @Element \*/ /*\ * Element.untouchcancel [ method ] ** * Removes a touchcancel event handler from the element - handler (function) handler for the event = (object) @Element \*/ for (var i = events.length; i--;) { (function (eventName) { Snap[eventName] = elproto[eventName] = function (fn, scope) { if (Snap.is(fn, "function")) { this.events = this.events || []; this.events.push({ name: eventName, f: fn, unbind: addEvent(this.node || document, eventName, fn, scope || this) }); } else { for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) { try { this.events[i].f.call(this); } catch (e) {} } } return this; }; Snap["un" + eventName] = elproto["un" + eventName] = function (fn) { var events = this.events || [], l = events.length; while (l--) if (events[l].name == eventName && (events[l].f == fn || !fn)) { events[l].unbind(); events.splice(l, 1); !events.length && delete this.events; return this; } return this; }; })(events[i]); } /*\ * Element.hover [ method ] ** * Adds hover event handlers to the element - f_in (function) handler for hover in - f_out (function) handler for hover out - icontext (object) #optional context for hover in handler - ocontext (object) #optional context for hover out handler = (object) @Element \*/ elproto.hover = function (f_in, f_out, scope_in, scope_out) { return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in); }; /*\ * Element.unhover [ method ] ** * Removes hover event handlers from the element - f_in (function) handler for hover in - f_out (function) handler for hover out = (object) @Element \*/ elproto.unhover = function (f_in, f_out) { return this.unmouseover(f_in).unmouseout(f_out); }; var draggable = []; // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture. // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from? // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason. // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start. on start, drag.end. on end and drag.move. on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID? /*\ * Element.drag [ method ] ** * Adds event handlers for an element's drag gesture ** - onmove (function) handler for moving - onstart (function) handler for drag start - onend (function) handler for drag end - mcontext (object) #optional context for moving handler - scontext (object) #optional context for drag start handler - econtext (object) #optional context for drag end handler * Additionaly following `drag` events are triggered: `drag.start.` on start, * `drag.end.` on end and `drag.move.` on every move. When element is dragged over another element * `drag.over.` fires as well. * * Start event and start handler are called in specified context or in context of the element with following parameters: o x (number) x position of the mouse o y (number) y position of the mouse o event (object) DOM event object * Move event and move handler are called in specified context or in context of the element with following parameters: o dx (number) shift by x from the start point o dy (number) shift by y from the start point o x (number) x position of the mouse o y (number) y position of the mouse o event (object) DOM event object * End event and end handler are called in specified context or in context of the element with following parameters: o event (object) DOM event object = (object) @Element \*/ elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) { var el = this; if (!arguments.length) { var origTransform; return el.drag(function (dx, dy) { this.attr({ transform: origTransform + (origTransform ? "T" : "t") + [dx, dy] }); }, function () { origTransform = this.transform().local; }); } function start(e, x, y) { (e.originalEvent || e).preventDefault(); el._drag.x = x; el._drag.y = y; el._drag.id = e.identifier; !drag.length && Snap.mousemove(dragMove).mouseup(dragUp); drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope}); onstart && eve.on("snap.drag.start." + el.id, onstart); onmove && eve.on("snap.drag.move." + el.id, onmove); onend && eve.on("snap.drag.end." + el.id, onend); eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e); } function init(e, x, y) { eve("snap.draginit." + el.id, el, e, x, y); } eve.on("snap.draginit." + el.id, start); el._drag = {}; draggable.push({el: el, start: start, init: init}); el.mousedown(init); return el; }; /* * Element.onDragOver [ method ] ** * Shortcut to assign event handler for `drag.over.` event, where `id` is the element's `id` (see @Element.id) - f (function) handler for event, first argument would be the element you are dragging over \*/ // elproto.onDragOver = function (f) { // f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id); // }; /*\ * Element.undrag [ method ] ** * Removes all drag event handlers from the given element \*/ elproto.undrag = function () { var i = draggable.length; while (i--) if (draggable[i].el == this) { this.unmousedown(draggable[i].init); draggable.splice(i, 1); eve.unbind("snap.drag.*." + this.id); eve.unbind("snap.draginit." + this.id); } !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp); return this; }; }); ================================================ FILE: src/paper.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { var proto = Paper.prototype, is = Snap.is; /*\ * Paper.rect [ method ] * * Draws a rectangle ** - x (number) x coordinate of the top left corner - y (number) y coordinate of the top left corner - width (number) width - height (number) height - rx (number) #optional horizontal radius for rounded corners, default is 0 - ry (number) #optional vertical radius for rounded corners, default is rx or 0 = (object) the `rect` element ** > Usage | // regular rectangle | var c = paper.rect(10, 10, 50, 50); | // rectangle with rounded corners | var c = paper.rect(40, 40, 50, 50, 10); \*/ proto.rect = function (x, y, w, h, rx, ry) { var attr; if (ry == null) { ry = rx; } if (is(x, "object") && x == "[object Object]") { attr = x; } else if (x != null) { attr = { x: x, y: y, width: w, height: h }; if (rx != null) { attr.rx = rx; attr.ry = ry; } } return this.el("rect", attr); }; /*\ * Paper.circle [ method ] ** * Draws a circle ** - x (number) x coordinate of the centre - y (number) y coordinate of the centre - r (number) radius = (object) the `circle` element ** > Usage | var c = paper.circle(50, 50, 40); \*/ proto.circle = function (cx, cy, r) { var attr; if (is(cx, "object") && cx == "[object Object]") { attr = cx; } else if (cx != null) { attr = { cx: cx, cy: cy, r: r }; } return this.el("circle", attr); }; var preload = (function () { function onerror() { this.parentNode.removeChild(this); } return function (src, f) { var img = glob.doc.createElement("img"), body = glob.doc.body; img.style.cssText = "position:absolute;left:-9999em;top:-9999em"; img.onload = function () { f.call(img); img.onload = img.onerror = null; body.removeChild(img); }; img.onerror = onerror; body.appendChild(img); img.src = src; }; }()); /*\ * Paper.image [ method ] ** * Places an image on the surface ** - src (string) URI of the source image - x (number) x offset position - y (number) y offset position - width (number) width of the image - height (number) height of the image = (object) the `image` element * or = (object) Snap element object with type `image` ** > Usage | var c = paper.image("apple.png", 10, 10, 80, 80); \*/ proto.image = function (src, x, y, width, height) { var el = this.el("image"); if (is(src, "object") && "src" in src) { el.attr(src); } else if (src != null) { var set = { "xlink:href": src, preserveAspectRatio: "none" }; if (x != null && y != null) { set.x = x; set.y = y; } if (width != null && height != null) { set.width = width; set.height = height; } else { preload(src, function () { Snap._.$(el.node, { width: this.offsetWidth, height: this.offsetHeight }); }); } Snap._.$(el.node, set); } return el; }; /*\ * Paper.ellipse [ method ] ** * Draws an ellipse ** - x (number) x coordinate of the centre - y (number) y coordinate of the centre - rx (number) horizontal radius - ry (number) vertical radius = (object) the `ellipse` element ** > Usage | var c = paper.ellipse(50, 50, 40, 20); \*/ proto.ellipse = function (cx, cy, rx, ry) { var attr; if (is(cx, "object") && cx == "[object Object]") { attr = cx; } else if (cx != null) { attr ={ cx: cx, cy: cy, rx: rx, ry: ry }; } return this.el("ellipse", attr); }; // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier. /*\ * Paper.path [ method ] ** * Creates a `` element using the given string as the path's definition - pathString (string) #optional path string in SVG format * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example: | "M10,20L30,40" * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates. * #

    Here is short list of commands available, for more details see SVG path string format or article about path strings at MDN.

    # # # # # # # # # # # #
    CommandNameParameters
    Mmoveto(x y)+
    Zclosepath(none)
    Llineto(x y)+
    Hhorizontal linetox+
    Vvertical linetoy+
    Ccurveto(x1 y1 x2 y2 x y)+
    Ssmooth curveto(x2 y2 x y)+
    Qquadratic Bézier curveto(x1 y1 x y)+
    Tsmooth quadratic Bézier curveto(x y)+
    Aelliptical arc(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
    RCatmull-Rom curveto*x1 y1 (x y)+
    * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier. * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point. > Usage | var c = paper.path("M10 10L90 90"); | // draw a diagonal line: | // move to 10,10, line to 90,90 \*/ proto.path = function (d) { var attr; if (is(d, "object") && !is(d, "array")) { attr = d; } else if (d) { attr = {d: d}; } return this.el("path", attr); }; /*\ * Paper.g [ method ] ** * Creates a group element ** - varargs (…) #optional elements to nest within the group = (object) the `g` element ** > Usage | var c1 = paper.circle(), | c2 = paper.rect(), | g = paper.g(c2, c1); // note that the order of elements is different * or | var c1 = paper.circle(), | c2 = paper.rect(), | g = paper.g(); | g.add(c2, c1); \*/ /*\ * Paper.group [ method ] ** * See @Paper.g \*/ proto.group = proto.g = function (first) { var attr, el = this.el("g"); if (arguments.length == 1 && first && !first.type) { el.attr(first); } else if (arguments.length) { el.add(Array.prototype.slice.call(arguments, 0)); } return el; }; /*\ * Paper.svg [ method ] ** * Creates a nested SVG element. - x (number) @optional X of the element - y (number) @optional Y of the element - width (number) @optional width of the element - height (number) @optional height of the element - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height ** = (object) the `svg` element ** \*/ proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) { var attrs = {}; if (is(x, "object") && y == null) { attrs = x; } else { if (x != null) { attrs.x = x; } if (y != null) { attrs.y = y; } if (width != null) { attrs.width = width; } if (height != null) { attrs.height = height; } if (vbx != null && vby != null && vbw != null && vbh != null) { attrs.viewBox = [vbx, vby, vbw, vbh]; } } return this.el("svg", attrs); }; /*\ * Paper.mask [ method ] ** * Equivalent in behaviour to @Paper.g, except it’s a mask. ** = (object) the `mask` element ** \*/ proto.mask = function (first) { var attr, el = this.el("mask"); if (arguments.length == 1 && first && !first.type) { el.attr(first); } else if (arguments.length) { el.add(Array.prototype.slice.call(arguments, 0)); } return el; }; /*\ * Paper.ptrn [ method ] ** * Equivalent in behaviour to @Paper.g, except it’s a pattern. - x (number) @optional X of the element - y (number) @optional Y of the element - width (number) @optional width of the element - height (number) @optional height of the element - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height ** = (object) the `pattern` element ** \*/ proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) { if (is(x, "object")) { var attr = x; } else { attr = {patternUnits: "userSpaceOnUse"}; if (x) { attr.x = x; } if (y) { attr.y = y; } if (width != null) { attr.width = width; } if (height != null) { attr.height = height; } if (vx != null && vy != null && vw != null && vh != null) { attr.viewBox = [vx, vy, vw, vh]; } else { attr.viewBox = [x || 0, y || 0, width || 0, height || 0]; } } return this.el("pattern", attr); }; /*\ * Paper.use [ method ] ** * Creates a element. - id (string) @optional id of element to link * or - id (Element) @optional element to link ** = (object) the `use` element ** \*/ proto.use = function (id) { if (id != null) { if (id instanceof Element) { if (!id.attr("id")) { id.attr({id: Snap._.id(id)}); } id = id.attr("id"); } if (String(id).charAt() == "#") { id = id.substring(1); } return this.el("use", {"xlink:href": "#" + id}); } else { return Element.prototype.use.call(this); } }; /*\ * Paper.symbol [ method ] ** * Creates a element. - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height = (object) the `symbol` element ** \*/ proto.symbol = function (vx, vy, vw, vh) { var attr = {}; if (vx != null && vy != null && vw != null && vh != null) { attr.viewBox = [vx, vy, vw, vh]; } return this.el("symbol", attr); }; /*\ * Paper.text [ method ] ** * Draws a text string ** - x (number) x coordinate position - y (number) y coordinate position - text (string|array) The text string to draw or array of strings to nest within separate `` elements = (object) the `text` element ** > Usage | var t1 = paper.text(50, 50, "Snap"); | var t2 = paper.text(50, 50, ["S","n","a","p"]); | // Text path usage | t1.attr({textpath: "M10,10L100,100"}); | // or | var pth = paper.path("M10,10L100,100"); | t1.attr({textpath: pth}); \*/ proto.text = function (x, y, text) { var attr = {}; if (is(x, "object")) { attr = x; } else if (x != null) { attr = { x: x, y: y, text: text || "" }; } return this.el("text", attr); }; /*\ * Paper.line [ method ] ** * Draws a line ** - x1 (number) x coordinate position of the start - y1 (number) y coordinate position of the start - x2 (number) x coordinate position of the end - y2 (number) y coordinate position of the end = (object) the `line` element ** > Usage | var t1 = paper.line(50, 50, 100, 100); \*/ proto.line = function (x1, y1, x2, y2) { var attr = {}; if (is(x1, "object")) { attr = x1; } else if (x1 != null) { attr = { x1: x1, x2: x2, y1: y1, y2: y2 }; } return this.el("line", attr); }; /*\ * Paper.polyline [ method ] ** * Draws a polyline ** - points (array) array of points * or - varargs (…) points = (object) the `polyline` element ** > Usage | var p1 = paper.polyline([10, 10, 100, 100]); | var p2 = paper.polyline(10, 10, 100, 100); \*/ proto.polyline = function (points) { if (arguments.length > 1) { points = Array.prototype.slice.call(arguments, 0); } var attr = {}; if (is(points, "object") && !is(points, "array")) { attr = points; } else if (points != null) { attr = {points: points}; } return this.el("polyline", attr); }; /*\ * Paper.polygon [ method ] ** * Draws a polygon. See @Paper.polyline \*/ proto.polygon = function (points) { if (arguments.length > 1) { points = Array.prototype.slice.call(arguments, 0); } var attr = {}; if (is(points, "object") && !is(points, "array")) { attr = points; } else if (points != null) { attr = {points: points}; } return this.el("polygon", attr); }; // gradients (function () { var $ = Snap._.$; // gradients' helpers /*\ * Element.stops [ method ] ** * Only for gradients! * Returns array of gradient stops elements. = (array) the stops array. \*/ function Gstops() { return this.selectAll("stop"); } /*\ * Element.addStop [ method ] ** * Only for gradients! * Adds another stop to the gradient. - color (string) stops color - offset (number) stops offset 0..100 = (object) gradient element \*/ function GaddStop(color, offset) { var stop = $("stop"), attr = { offset: +offset + "%" }; color = Snap.color(color); attr["stop-color"] = color.hex; if (color.opacity < 1) { attr["stop-opacity"] = color.opacity; } $(stop, attr); var stops = this.stops(), inserted; for (var i = 0; i < stops.length; i++) { var stopOffset = parseFloat(stops[i].attr("offset")); if (stopOffset > offset) { this.node.insertBefore(stop, stops[i].node); inserted = true; break; } } if (!inserted) { this.node.appendChild(stop); } return this; } function GgetBBox() { if (this.type == "linearGradient") { var x1 = $(this.node, "x1") || 0, x2 = $(this.node, "x2") || 1, y1 = $(this.node, "y1") || 0, y2 = $(this.node, "y2") || 0; return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)); } else { var cx = this.node.cx || .5, cy = this.node.cy || .5, r = this.node.r || 0; return Snap._.box(cx - r, cy - r, r * 2, r * 2); } } /*\ * Element.setStops [ method ] ** * Only for gradients! * Updates stops of the gradient based on passed gradient descriptor. See @Ppaer.gradient - str (string) gradient descriptor part after `()`. = (object) gradient element | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); | g.setStops("#fff-#000-#f00-#fc0"); \*/ function GsetStops(str) { var grad = str, stops = this.stops(); if (typeof str == "string") { grad = eve("snap.util.grad.parse", null, "l(0,0,0,1)" + str).firstDefined().stops; } if (!Snap.is(grad, "array")) { return; } for (var i = 0; i < stops.length; i++) { if (grad[i]) { var color = Snap.color(grad[i].color), attr = {"offset": grad[i].offset + "%"}; attr["stop-color"] = color.hex; if (color.opacity < 1) { attr["stop-opacity"] = color.opacity; } stops[i].attr(attr); } else { stops[i].remove(); } } for (i = stops.length; i < grad.length; i++) { this.addStop(grad[i].color, grad[i].offset); } return this; } function gradient(defs, str) { var grad = eve("snap.util.grad.parse", null, str).firstDefined(), el; if (!grad) { return null; } grad.params.unshift(defs); if (grad.type.toLowerCase() == "l") { el = gradientLinear.apply(0, grad.params); } else { el = gradientRadial.apply(0, grad.params); } if (grad.type != grad.type.toLowerCase()) { $(el.node, { gradientUnits: "userSpaceOnUse" }); } var stops = grad.stops, len = stops.length; for (var i = 0; i < len; i++) { var stop = stops[i]; el.addStop(stop.color, stop.offset); } return el; } function gradientLinear(defs, x1, y1, x2, y2) { var el = Snap._.make("linearGradient", defs); el.stops = Gstops; el.addStop = GaddStop; el.getBBox = GgetBBox; el.setStops = GsetStops; if (x1 != null) { $(el.node, { x1: x1, y1: y1, x2: x2, y2: y2 }); } return el; } function gradientRadial(defs, cx, cy, r, fx, fy) { var el = Snap._.make("radialGradient", defs); el.stops = Gstops; el.addStop = GaddStop; el.getBBox = GgetBBox; if (cx != null) { $(el.node, { cx: cx, cy: cy, r: r }); } if (fx != null && fy != null) { $(el.node, { fx: fx, fy: fy }); } return el; } /*\ * Paper.gradient [ method ] ** * Creates a gradient element ** - gradient (string) gradient descriptor > Gradient Descriptor * The gradient descriptor is an expression formatted as * follows: `()`. The `` can be * either linear or radial. The uppercase `L` or `R` letters * indicate absolute coordinates offset from the SVG surface. * Lowercase `l` or `r` letters indicate coordinates * calculated relative to the element to which the gradient is * applied. Coordinates specify a linear gradient vector as * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`, * `r` and optional `fx`, `fy` specifying a focal point away * from the center of the circle. Specify `` as a list * of dash-separated CSS color values. Each color may be * followed by a custom offset value, separated with a colon * character. > Examples * Linear gradient, relative from top-left corner to bottom-right * corner, from black through red to white: | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); * Linear gradient, absolute from (0, 0) to (100, 100), from black * through red at 25% to white: | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff"); * Radial gradient, relative from the center of the element with radius * half the width, from black to white: | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff"); * To apply the gradient: | paper.circle(50, 50, 40).attr({ | fill: g | }); = (object) the `gradient` element \*/ proto.gradient = function (str) { return gradient(this.defs, str); }; proto.gradientLinear = function (x1, y1, x2, y2) { return gradientLinear(this.defs, x1, y1, x2, y2); }; proto.gradientRadial = function (cx, cy, r, fx, fy) { return gradientRadial(this.defs, cx, cy, r, fx, fy); }; /*\ * Paper.toString [ method ] ** * Returns SVG code for the @Paper = (string) SVG code for the @Paper \*/ proto.toString = function () { var doc = this.node.ownerDocument, f = doc.createDocumentFragment(), d = doc.createElement("div"), svg = this.node.cloneNode(true), res; f.appendChild(d); d.appendChild(svg); Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"}); res = d.innerHTML; f.removeChild(f.firstChild); return res; }; /*\ * Paper.toDataURL [ method ] ** * Returns SVG code for the @Paper as Data URI string. = (string) Data URI string \*/ proto.toDataURL = function () { if (window && window.btoa) { return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this))); } }; /*\ * Paper.clear [ method ] ** * Removes all child nodes of the paper, except . \*/ proto.clear = function () { var node = this.node.firstChild, next; while (node) { next = node.nextSibling; if (node.tagName != "defs") { node.parentNode.removeChild(node); } else { proto.clear.call({node: node}); } node = next; } }; }()); }); ================================================ FILE: src/path.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var elproto = Element.prototype, is = Snap.is, clone = Snap._.clone, has = "hasOwnProperty", p2s = /,?([a-z]),?/gi, toFloat = parseFloat, math = Math, PI = math.PI, mmin = math.min, mmax = math.max, pow = math.pow, abs = math.abs; function paths(ps) { var p = paths.ps = paths.ps || {}; if (p[ps]) { p[ps].sleep = 100; } else { p[ps] = { sleep: 100 }; } setTimeout(function () { for (var key in p) if (p[has](key) && key != ps) { p[key].sleep--; !p[key].sleep && delete p[key]; } }); return p[ps]; } function box(x, y, width, height) { if (x == null) { x = y = width = height = 0; } if (y == null) { y = x.y; width = x.width; height = x.height; x = x.x; } return { x: x, y: y, width: width, w: width, height: height, h: height, x2: x + width, y2: y + height, cx: x + width / 2, cy: y + height / 2, r1: math.min(width, height) / 2, r2: math.max(width, height) / 2, r0: math.sqrt(width * width + height * height) / 2, path: rectPath(x, y, width, height), vb: [x, y, width, height].join(" ") }; } function toString() { return this.join(",").replace(p2s, "$1"); } function pathClone(pathArray) { var res = clone(pathArray); res.toString = toString; return res; } function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) { if (length == null) { return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y); } else { return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length)); } } function getLengthFactory(istotal, subpath) { function O(val) { return +(+val).toFixed(3); } return Snap._.cacher(function (path, length, onlystart) { if (path instanceof Element) { path = path.attr("d"); } path = path2curve(path); var x, y, p, l, sp = "", subpaths = {}, point, len = 0; for (var 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 (subpath && !subpaths.start) { point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len); sp += [ "C" + O(point.start.x), O(point.start.y), O(point.m.x), O(point.m.y), O(point.x), O(point.y) ]; if (onlystart) {return sp;} subpaths.start = sp; sp = [ "M" + O(point.x), O(point.y) + "C" + O(point.n.x), O(point.n.y), O(point.end.x), O(point.end.y), O(p[5]), O(p[6]) ].join(); len += l; x = +p[5]; y = +p[6]; continue; } if (!istotal && !subpath) { point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len); return point; } } len += l; x = +p[5]; y = +p[6]; } sp += p.shift() + p; } subpaths.end = sp; point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1); return point; }, null, Snap._.clone); } var getTotalLength = getLengthFactory(1), getPointAtLength = getLengthFactory(), getSubpathsAtLength = getLengthFactory(0, 1); function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { var t1 = 1 - t, t13 = pow(t1, 3), t12 = pow(t1, 2), t2 = t * t, t3 = t2 * t, x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y, mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), ax = t1 * p1x + t * c1x, ay = t1 * p1y + t * c1y, cx = t1 * c2x + t * p2x, cy = t1 * c2y + t * p2y, alpha = 90 - math.atan2(mx - nx, my - ny) * 180 / PI; // (mx > nx || my < ny) && (alpha += 180); return { x: x, y: y, m: {x: mx, y: my}, n: {x: nx, y: ny}, start: {x: ax, y: ay}, end: {x: cx, y: cy}, alpha: alpha }; } function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { if (!Snap.is(p1x, "array")) { p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y]; } var bbox = curveDim.apply(null, p1x); return box( bbox.min.x, bbox.min.y, bbox.max.x - bbox.min.x, bbox.max.y - bbox.min.y ); } function isPointInsideBBox(bbox, x, y) { return x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height; } function isBBoxIntersect(bbox1, bbox2) { bbox1 = box(bbox1); bbox2 = box(bbox2); return isPointInsideBBox(bbox2, bbox1.x, bbox1.y) || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y) || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2) || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2) || isPointInsideBBox(bbox1, bbox2.x, bbox2.y) || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y) || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2) || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2) || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x) && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y); } function base3(t, p1, p2, p3, p4) { var 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 bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { if (z == null) { z = 1; } z = z > 1 ? 1 : z < 0 ? 0 : z; var z2 = z / 2, n = 12, Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816], 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], sum = 0; for (var i = 0; i < n; i++) { var 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 getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { return; } var t = 1, step = t / 2, t2 = t - step, l, e = .01; l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); while (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 intersect(x1, y1, x2, y2, x3, y3, x4, y4) { if ( mmax(x1, x2) < mmin(x3, x4) || mmin(x1, x2) > mmax(x3, x4) || mmax(y1, y2) < mmin(y3, y4) || mmin(y1, y2) > mmax(y3, y4) ) { return; } var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); if (!denominator) { return; } var px = nx / denominator, py = ny / denominator, px2 = +px.toFixed(2), py2 = +py.toFixed(2); if ( px2 < +mmin(x1, x2).toFixed(2) || px2 > +mmax(x1, x2).toFixed(2) || px2 < +mmin(x3, x4).toFixed(2) || px2 > +mmax(x3, x4).toFixed(2) || py2 < +mmin(y1, y2).toFixed(2) || py2 > +mmax(y1, y2).toFixed(2) || py2 < +mmin(y3, y4).toFixed(2) || py2 > +mmax(y3, y4).toFixed(2) ) { return; } return {x: px, y: py}; } function inter(bez1, bez2) { return interHelper(bez1, bez2); } function interCount(bez1, bez2) { return interHelper(bez1, bez2, 1); } function interHelper(bez1, bez2, justCount) { var bbox1 = bezierBBox(bez1), bbox2 = bezierBBox(bez2); if (!isBBoxIntersect(bbox1, bbox2)) { return justCount ? 0 : []; } var l1 = bezlen.apply(0, bez1), l2 = bezlen.apply(0, bez2), n1 = ~~(l1 / 8), n2 = ~~(l2 / 8), dots1 = [], dots2 = [], xy = {}, res = justCount ? 0 : []; for (var i = 0; i < n1 + 1; i++) { var p = findDotsAtSegment.apply(0, bez1.concat(i / n1)); dots1.push({x: p.x, y: p.y, t: i / n1}); } for (i = 0; i < n2 + 1; i++) { p = findDotsAtSegment.apply(0, bez2.concat(i / n2)); dots2.push({x: p.x, y: p.y, t: i / n2}); } for (i = 0; i < n1; i++) { for (var j = 0; j < n2; j++) { var di = dots1[i], di1 = dots1[i + 1], dj = dots2[j], dj1 = dots2[j + 1], ci = abs(di1.x - di.x) < .001 ? "y" : "x", cj = abs(dj1.x - dj.x) < .001 ? "y" : "x", is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y); if (is) { if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) { continue; } xy[is.x.toFixed(4)] = is.y.toFixed(4); var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t); if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) { if (justCount) { res++; } else { res.push({ x: is.x, y: is.y, t1: t1, t2: t2 }); } } } } } return res; } function pathIntersection(path1, path2) { return interPathHelper(path1, path2); } function pathIntersectionNumber(path1, path2) { return interPathHelper(path1, path2, 1); } function interPathHelper(path1, path2, justCount) { path1 = path2curve(path1); path2 = path2curve(path2); var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2, res = justCount ? 0 : []; for (var i = 0, ii = path1.length; i < ii; i++) { var pi = path1[i]; if (pi[0] == "M") { x1 = x1m = pi[1]; y1 = y1m = pi[2]; } else { if (pi[0] == "C") { bez1 = [x1, y1].concat(pi.slice(1)); x1 = bez1[6]; y1 = bez1[7]; } else { bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m]; x1 = x1m; y1 = y1m; } for (var j = 0, jj = path2.length; j < jj; j++) { var pj = path2[j]; if (pj[0] == "M") { x2 = x2m = pj[1]; y2 = y2m = pj[2]; } else { if (pj[0] == "C") { bez2 = [x2, y2].concat(pj.slice(1)); x2 = bez2[6]; y2 = bez2[7]; } else { bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m]; x2 = x2m; y2 = y2m; } var intr = interHelper(bez1, bez2, justCount); if (justCount) { res += intr; } else { for (var k = 0, kk = intr.length; k < kk; k++) { intr[k].segment1 = i; intr[k].segment2 = j; intr[k].bez1 = bez1; intr[k].bez2 = bez2; } res = res.concat(intr); } } } } } return res; } function isPointInsidePath(path, x, y) { var bbox = pathBBox(path); return isPointInsideBBox(bbox, x, y) && interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1; } function pathBBox(path) { var pth = paths(path); if (pth.bbox) { return clone(pth.bbox); } if (!path) { return box(); } path = path2curve(path); var x = 0, y = 0, X = [], Y = [], p; for (var i = 0, ii = path.length; i < ii; i++) { p = path[i]; if (p[0] == "M") { x = p[1]; y = p[2]; X.push(x); Y.push(y); } else { var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]); X = X.concat(dim.min.x, dim.max.x); Y = Y.concat(dim.min.y, dim.max.y); x = p[5]; y = p[6]; } } var xmin = mmin.apply(0, X), ymin = mmin.apply(0, Y), xmax = mmax.apply(0, X), ymax = mmax.apply(0, Y), bb = box(xmin, ymin, xmax - xmin, ymax - ymin); pth.bbox = clone(bb); return bb; } function rectPath(x, y, w, h, r) { if (r) { return [ ["M", +x + +r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"] ]; } var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]]; res.toString = toString; return res; } function ellipsePath(x, y, rx, ry, a) { if (a == null && ry == null) { ry = rx; } x = +x; y = +y; rx = +rx; ry = +ry; if (a != null) { var rad = Math.PI / 180, x1 = x + rx * Math.cos(-ry * rad), x2 = x + rx * Math.cos(-a * rad), y1 = y + rx * Math.sin(-ry * rad), y2 = y + rx * Math.sin(-a * rad), res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]]; } else { res = [ ["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"] ]; } res.toString = toString; return res; } var unit2px = Snap._unit2px, getPath = { path: function (el) { return el.attr("path"); }, circle: function (el) { var attr = unit2px(el); return ellipsePath(attr.cx, attr.cy, attr.r); }, ellipse: function (el) { var attr = unit2px(el); return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry); }, rect: function (el) { var attr = unit2px(el); return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry); }, image: function (el) { var attr = unit2px(el); return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height); }, line: function (el) { return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")]; }, polyline: function (el) { return "M" + el.attr("points"); }, polygon: function (el) { return "M" + el.attr("points") + "z"; }, deflt: function (el) { var bbox = el.node.getBBox(); return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); } }; function pathToRelative(pathArray) { var pth = paths(pathArray), lowerCase = String.prototype.toLowerCase; if (pth.rel) { return pathClone(pth.rel); } if (!Snap.is(pathArray, "array") || !Snap.is(pathArray && pathArray[0], "array")) { pathArray = Snap.parsePathString(pathArray); } var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0; if (pathArray[0][0] == "M") { x = pathArray[0][1]; y = pathArray[0][2]; mx = x; my = y; start++; res.push(["M", x, y]); } for (var i = start, ii = pathArray.length; i < ii; i++) { var r = res[i] = [], pa = pathArray[i]; if (pa[0] != lowerCase.call(pa[0])) { r[0] = lowerCase.call(pa[0]); 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).toFixed(3); r[7] = +(pa[7] - y).toFixed(3); break; case "v": r[1] = +(pa[1] - y).toFixed(3); break; case "m": mx = pa[1]; my = pa[2]; default: for (var j = 1, jj = pa.length; j < jj; j++) { r[j] = +(pa[j] - (j % 2 ? x : y)).toFixed(3); } } } else { r = res[i] = []; if (pa[0] == "m") { mx = pa[1] + x; my = pa[2] + y; } for (var k = 0, kk = pa.length; k < kk; k++) { res[i][k] = pa[k]; } } var len = res[i].length; switch (res[i][0]) { case "z": x = mx; y = my; break; case "h": x += +res[i][len - 1]; break; case "v": y += +res[i][len - 1]; break; default: x += +res[i][len - 2]; y += +res[i][len - 1]; } } res.toString = toString; pth.rel = pathClone(res); return res; } function pathToAbsolute(pathArray) { var pth = paths(pathArray); if (pth.abs) { return pathClone(pth.abs); } if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption pathArray = Snap.parsePathString(pathArray); } if (!pathArray || !pathArray.length) { return [["M", 0, 0]]; } var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0, pa0; if (pathArray[0][0] == "M") { x = +pathArray[0][1]; y = +pathArray[0][2]; mx = x; my = y; start++; res[0] = ["M", x, y]; } var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z"; for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { res.push(r = []); pa = pathArray[i]; pa0 = pa[0]; if (pa0 != pa0.toUpperCase()) { r[0] = pa0.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": var dots = [x, y].concat(pa.slice(1)); for (var 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 "O": res.pop(); dots = ellipsePath(x, y, pa[1], pa[2]); dots.push(dots[0]); res = res.concat(dots); break; case "U": res.pop(); res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])); r = ["U"].concat(res[res.length - 1].slice(-2)); break; case "M": mx = +pa[1] + x; my = +pa[2] + y; default: for (j = 1, jj = pa.length; j < jj; j++) { r[j] = +pa[j] + (j % 2 ? x : y); } } } else if (pa0 == "R") { dots = [x, y].concat(pa.slice(1)); res.pop(); res = res.concat(catmullRom2bezier(dots, crz)); r = ["R"].concat(pa.slice(-2)); } else if (pa0 == "O") { res.pop(); dots = ellipsePath(x, y, pa[1], pa[2]); dots.push(dots[0]); res = res.concat(dots); } else if (pa0 == "U") { res.pop(); res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])); r = ["U"].concat(res[res.length - 1].slice(-2)); } else { for (var k = 0, kk = pa.length; k < kk; k++) { r[k] = pa[k]; } } pa0 = pa0.toUpperCase(); if (pa0 != "O") { 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]; default: x = r[r.length - 2]; y = r[r.length - 1]; } } } res.toString = toString; pth.abs = pathClone(res); return res; } function l2c(x1, y1, x2, y2) { return [x1, y1, x2, y2, x2, y2]; } function q2c(x1, y1, ax, ay, x2, y2) { var _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 a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { // for more information of where this math came from visit: // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes var _120 = PI * 120 / 180, rad = PI / 180 * (+angle || 0), res = [], xy, rotate = Snap._.cacher(function (x, y, rad) { var X = x * math.cos(rad) - y * math.sin(rad), Y = x * math.sin(rad) + y * math.cos(rad); return {x: X, y: Y}; }); if (!rx || !ry) { return [x1, y1, x2, y2, x2, y2]; } if (!recursive) { xy = rotate(x1, y1, -rad); x1 = xy.x; y1 = xy.y; xy = rotate(x2, y2, -rad); x2 = xy.x; y2 = xy.y; var cos = math.cos(PI / 180 * angle), sin = math.sin(PI / 180 * angle), x = (x1 - x2) / 2, y = (y1 - y2) / 2; var h = x * x / (rx * rx) + y * y / (ry * ry); if (h > 1) { h = math.sqrt(h); rx = h * rx; ry = h * ry; } var rx2 = rx * rx, ry2 = ry * ry, k = (large_arc_flag == sweep_flag ? -1 : 1) * math.sqrt(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; f1 < 0 && (f1 = PI * 2 + f1); 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]; } var df = f2 - f1; if (abs(df) > _120) { var 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; var 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(","); var newres = []; for (var 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; } } function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { var t1 = 1 - t; return { x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x, y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y }; } // Returns bounding box of cubic bezier curve. // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html // Original version: NISHIO Hirokazu // Modifications: https://github.com/timo22345 function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) { var tvalues = [], bounds = [[], []], a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (var i = 0; i < 2; ++i) { if (i == 0) { b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; } else { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (abs(a) < 1e-12) { if (abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; sqrtb2ac = math.sqrt(b2ac); if (b2ac < 0) { continue; } t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var x, y, j = tvalues.length, jlen = j, mt; while (j--) { t = tvalues[j]; mt = 1 - t; bounds[0][j] = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3; bounds[1][j] = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3; } bounds[0][jlen] = x0; bounds[1][jlen] = y0; bounds[0][jlen + 1] = x3; bounds[1][jlen + 1] = y3; bounds[0].length = bounds[1].length = jlen + 2; return { min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])}, max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])} }; } function path2curve(path, path2) { var pth = !path2 && paths(path); if (!path2 && pth.curve) { return pathClone(pth.curve); } var p = pathToAbsolute(path), p2 = path2 && pathToAbsolute(path2), attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, processPath = function (path, d, pcom) { var nx, ny; if (!path) { return ["C", d.x, d.y, d.x, d.y, d.x, d.y]; } !(path[0] in {T: 1, Q: 1}) && (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") { // In "S" case we have to take into account, if the previous command is C/S. nx = d.x * 2 - d.bx; // And reflect the previous ny = d.y * 2 - d.by; // command's control point relative to the current point. } else { // or some else or nothing nx = d.x; ny = d.y; } path = ["C", nx, ny].concat(path.slice(1)); break; case "T": if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T. d.qx = d.x * 2 - d.qx; // And make a reflection similar d.qy = d.y * 2 - d.qy; // to case "S". } else { // or something else or nothing 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 = function (pp, i) { if (pp[i].length > 7) { pp[i].shift(); var pi = pp[i]; while (pi.length) { pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved p2 && (pcoms2[i] = "A"); // the same as above pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6))); } pp.splice(i, 1); ii = mmax(p.length, p2 && p2.length || 0); } }, fixM = function (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 = mmax(p.length, p2 && p2.length || 0); } }, pcoms1 = [], // path commands of original path p pcoms2 = [], // path commands of original path p2 pfirst = "", // temporary holder for original path command pcom = ""; // holder for previous path command of original path for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) { p[i] && (pfirst = p[i][0]); // save current path command if (pfirst != "C") // C is not saved yet, because it may be result of conversion { pcoms1[i] = pfirst; // Save current path command i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom } p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command // which may produce multiple C:s // so we have to make sure that C is also C in original path fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1 if (p2) { // the same procedures is done to p2 p2[i] && (pfirst = p2[i][0]); if (pfirst != "C") { pcoms2[i] = pfirst; 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); var 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 = toFloat(seg[seglen - 4]) || attrs.x; attrs.by = toFloat(seg[seglen - 3]) || attrs.y; attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x); attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y); attrs2.x = p2 && seg2[seg2len - 2]; attrs2.y = p2 && seg2[seg2len - 1]; } if (!p2) { pth.curve = pathClone(p); } return p2 ? [p, p2] : p; } function mapPath(path, matrix) { if (!matrix) { return path; } var x, y, i, j, ii, jj, pathi; path = path2curve(path); for (i = 0, ii = path.length; i < ii; i++) { pathi = path[i]; for (j = 1, jj = pathi.length; j < jj; j += 2) { x = matrix.x(pathi[j], pathi[j + 1]); y = matrix.y(pathi[j], pathi[j + 1]); pathi[j] = x; pathi[j + 1] = y; } } return path; } // http://schepers.cc/getting-to-the-point function catmullRom2bezier(crp, z) { var d = []; for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { var 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; } // export Snap.path = paths; /*\ * Snap.path.getTotalLength [ method ] ** * Returns the length of the given path in pixels ** - path (string) SVG path string ** = (number) length \*/ Snap.path.getTotalLength = getTotalLength; /*\ * Snap.path.getPointAtLength [ method ] ** * Returns the coordinates of the point located at the given length along the given path ** - path (string) SVG path string - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps ** = (object) representation of the point: o { o x: (number) x coordinate, o y: (number) y coordinate, o alpha: (number) angle of derivative o } \*/ Snap.path.getPointAtLength = getPointAtLength; /*\ * Snap.path.getSubpath [ method ] ** * Returns the subpath of a given path between given start and end lengths ** - path (string) SVG path string - from (number) length, in pixels, from the start of the path to the start of the segment - to (number) length, in pixels, from the start of the path to the end of the segment ** = (string) path string definition for the segment \*/ Snap.path.getSubpath = function (path, from, to) { if (this.getTotalLength(path) - to < 1e-6) { return getSubpathsAtLength(path, from).end; } var a = getSubpathsAtLength(path, to, 1); return from ? getSubpathsAtLength(a, from).end : a; }; /*\ * Element.getTotalLength [ method ] ** * Returns the length of the path in pixels (only works for `path` elements) = (number) length \*/ elproto.getTotalLength = function () { if (this.node.getTotalLength) { return this.node.getTotalLength(); } }; // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length? /*\ * Element.getPointAtLength [ method ] ** * Returns coordinates of the point located at the given length on the given path (only works for `path` elements) ** - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps ** = (object) representation of the point: o { o x: (number) x coordinate, o y: (number) y coordinate, o alpha: (number) angle of derivative o } \*/ elproto.getPointAtLength = function (length) { return getPointAtLength(this.attr("d"), length); }; // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear. /*\ * Element.getSubpath [ method ] ** * Returns subpath of a given element from given start and end lengths (only works for `path` elements) ** - from (number) length, in pixels, from the start of the path to the start of the segment - to (number) length, in pixels, from the start of the path to the end of the segment ** = (string) path string definition for the segment \*/ elproto.getSubpath = function (from, to) { return Snap.path.getSubpath(this.attr("d"), from, to); }; Snap._.box = box; /*\ * Snap.path.findDotsAtSegment [ method ] ** * Utility method ** * Finds dot coordinates on the given cubic beziér curve at the given t - p1x (number) x of the first point of the curve - p1y (number) y of the first point of the curve - c1x (number) x of the first anchor of the curve - c1y (number) y of the first anchor of the curve - c2x (number) x of the second anchor of the curve - c2y (number) y of the second anchor of the curve - p2x (number) x of the second point of the curve - p2y (number) y of the second point of the curve - t (number) position on the curve (0..1) = (object) point information in format: o { o x: (number) x coordinate of the point, o y: (number) y coordinate of the point, o m: { o x: (number) x coordinate of the left anchor, o y: (number) y coordinate of the left anchor o }, o n: { o x: (number) x coordinate of the right anchor, o y: (number) y coordinate of the right anchor o }, o start: { o x: (number) x coordinate of the start of the curve, o y: (number) y coordinate of the start of the curve o }, o end: { o x: (number) x coordinate of the end of the curve, o y: (number) y coordinate of the end of the curve o }, o alpha: (number) angle of the curve derivative at the point o } \*/ Snap.path.findDotsAtSegment = findDotsAtSegment; /*\ * Snap.path.bezierBBox [ method ] ** * Utility method ** * Returns the bounding box of a given cubic beziér curve - p1x (number) x of the first point of the curve - p1y (number) y of the first point of the curve - c1x (number) x of the first anchor of the curve - c1y (number) y of the first anchor of the curve - c2x (number) x of the second anchor of the curve - c2y (number) y of the second anchor of the curve - p2x (number) x of the second point of the curve - p2y (number) y of the second point of the curve * or - bez (array) array of six points for beziér curve = (object) bounding box o { o x: (number) x coordinate of the left top point of the box, o y: (number) y coordinate of the left top point of the box, o x2: (number) x coordinate of the right bottom point of the box, o y2: (number) y coordinate of the right bottom point of the box, o width: (number) width of the box, o height: (number) height of the box o } \*/ Snap.path.bezierBBox = bezierBBox; /*\ * Snap.path.isPointInsideBBox [ method ] ** * Utility method ** * Returns `true` if given point is inside bounding box - bbox (string) bounding box - x (string) x coordinate of the point - y (string) y coordinate of the point = (boolean) `true` if point is inside \*/ Snap.path.isPointInsideBBox = isPointInsideBBox; Snap.closest = function (x, y, X, Y) { var r = 100, b = box(x - r / 2, y - r / 2, r, r), inside = [], getter = X[0].hasOwnProperty("x") ? function (i) { return { x: X[i].x, y: X[i].y }; } : function (i) { return { x: X[i], y: Y[i] }; }, found = 0; while (r <= 1e6 && !found) { for (var i = 0, ii = X.length; i < ii; i++) { var xy = getter(i); if (isPointInsideBBox(b, xy.x, xy.y)) { found++; inside.push(xy); break; } } if (!found) { r *= 2; b = box(x - r / 2, y - r / 2, r, r) } } if (r == 1e6) { return; } var len = Infinity, res; for (i = 0, ii = inside.length; i < ii; i++) { var l = Snap.len(x, y, inside[i].x, inside[i].y); if (len > l) { len = l; inside[i].len = l; res = inside[i]; } } return res; }; /*\ * Snap.path.isBBoxIntersect [ method ] ** * Utility method ** * Returns `true` if two bounding boxes intersect - bbox1 (string) first bounding box - bbox2 (string) second bounding box = (boolean) `true` if bounding boxes intersect \*/ Snap.path.isBBoxIntersect = isBBoxIntersect; /*\ * Snap.path.intersection [ method ] ** * Utility method ** * Finds intersections of two paths - path1 (string) path string - path2 (string) path string = (array) dots of intersection o [ o { o x: (number) x coordinate of the point, o y: (number) y coordinate of the point, o t1: (number) t value for segment of path1, o t2: (number) t value for segment of path2, o segment1: (number) order number for segment of path1, o segment2: (number) order number for segment of path2, o bez1: (array) eight coordinates representing beziér curve for the segment of path1, o bez2: (array) eight coordinates representing beziér curve for the segment of path2 o } o ] \*/ Snap.path.intersection = pathIntersection; Snap.path.intersectionNumber = pathIntersectionNumber; /*\ * Snap.path.isPointInside [ method ] ** * Utility method ** * Returns `true` if given point is inside a given closed path. * * Note: fill mode doesn’t affect the result of this method. - path (string) path string - x (number) x of the point - y (number) y of the point = (boolean) `true` if point is inside the path \*/ Snap.path.isPointInside = isPointInsidePath; /*\ * Snap.path.getBBox [ method ] ** * Utility method ** * Returns the bounding box of a given path - path (string) path string = (object) bounding box o { o x: (number) x coordinate of the left top point of the box, o y: (number) y coordinate of the left top point of the box, o x2: (number) x coordinate of the right bottom point of the box, o y2: (number) y coordinate of the right bottom point of the box, o width: (number) width of the box, o height: (number) height of the box o } \*/ Snap.path.getBBox = pathBBox; Snap.path.get = getPath; /*\ * Snap.path.toRelative [ method ] ** * Utility method ** * Converts path coordinates into relative values - path (string) path string = (array) path string \*/ Snap.path.toRelative = pathToRelative; /*\ * Snap.path.toAbsolute [ method ] ** * Utility method ** * Converts path coordinates into absolute values - path (string) path string = (array) path string \*/ Snap.path.toAbsolute = pathToAbsolute; /*\ * Snap.path.toCubic [ method ] ** * Utility method ** * Converts path to a new path where all segments are cubic beziér curves - pathString (string|array) path string or array of segments = (array) array of segments \*/ Snap.path.toCubic = path2curve; /*\ * Snap.path.map [ method ] ** * Transform the path string with the given matrix - path (string) path string - matrix (object) see @Matrix = (string) transformed path string \*/ Snap.path.map = mapPath; Snap.path.toString = toString; Snap.path.clone = pathClone; }); ================================================ FILE: src/set.js ================================================ // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // 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. Snap.plugin(function (Snap, Element, Paper, glob) { var mmax = Math.max, mmin = Math.min; // Set var Set = function (items) { this.items = []; this.bindings = {}; this.length = 0; this.type = "set"; if (items) { for (var i = 0, ii = items.length; i < ii; i++) { if (items[i]) { this[this.items.length] = this.items[this.items.length] = items[i]; this.length++; } } } }, setproto = Set.prototype; /*\ * Set.push [ method ] ** * Adds each argument to the current set = (object) original element \*/ setproto.push = function () { var item, len; for (var i = 0, ii = arguments.length; i < ii; i++) { item = arguments[i]; if (item) { len = this.items.length; this[len] = this.items[len] = item; this.length++; } } return this; }; /*\ * Set.pop [ method ] ** * Removes last element and returns it = (object) element \*/ setproto.pop = function () { this.length && delete this[this.length--]; return this.items.pop(); }; /*\ * Set.forEach [ method ] ** * Executes given function for each element in the set * * If the function returns `false`, the loop stops running. ** - callback (function) function to run - thisArg (object) context object for the callback = (object) Set object \*/ setproto.forEach = function (callback, thisArg) { for (var i = 0, ii = this.items.length; i < ii; i++) { if (callback.call(thisArg, this.items[i], i) === false) { return this; } } return this; }; /*\ * Set.animate [ method ] ** * Animates each element in set in sync. * ** - attrs (object) key-value pairs of destination attributes - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends * or - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]` > Usage | // animate all elements in set to radius 10 | set.animate({r: 10}, 500, mina.easein); | // or | // animate first element to radius 10, but second to radius 20 and in different time | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]); = (Element) the current element \*/ setproto.animate = function (attrs, ms, easing, callback) { if (typeof easing == "function" && !easing.length) { callback = easing; easing = mina.linear; } if (attrs instanceof Snap._.Animation) { callback = attrs.callback; easing = attrs.easing; ms = easing.dur; attrs = attrs.attr; } var args = arguments; if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) { var each = true; } var begin, handler = function () { if (begin) { this.b = begin; } else { begin = this.b; } }, cb = 0, set = this, callbacker = callback && function () { if (++cb == set.length) { callback.call(this); } }; return this.forEach(function (el, i) { eve.once("snap.animcreated." + el.id, handler); if (each) { args[i] && el.animate.apply(el, args[i]); } else { el.animate(attrs, ms, easing, callbacker); } }); }; /*\ * Set.remove [ method ] ** * Removes all children of the set. * = (object) Set object \*/ setproto.remove = function () { while (this.length) { this.pop().remove(); } return this; }; /*\ * Set.bind [ method ] ** * Specifies how to handle a specific attribute when applied * to a set. * ** - attr (string) attribute name - callback (function) function to run * or - attr (string) attribute name - element (Element) specific element in the set to apply the attribute to * or - attr (string) attribute name - element (Element) specific element in the set to apply the attribute to - eattr (string) attribute on the element to bind the attribute to = (object) Set object \*/ setproto.bind = function (attr, a, b) { var data = {}; if (typeof a == "function") { this.bindings[attr] = a; } else { var aname = b || attr; this.bindings[attr] = function (v) { data[aname] = v; a.attr(data); }; } return this; }; /*\ * Set.attr [ method ] ** * Equivalent of @Element.attr. = (object) Set object \*/ setproto.attr = function (value) { var unbound = {}; for (var k in value) { if (this.bindings[k]) { this.bindings[k](value[k]); } else { unbound[k] = value[k]; } } for (var i = 0, ii = this.items.length; i < ii; i++) { this.items[i].attr(unbound); } return this; }; /*\ * Set.clear [ method ] ** * Removes all elements from the set \*/ setproto.clear = function () { while (this.length) { this.pop(); } }; /*\ * Set.splice [ method ] ** * Removes range of elements from the set ** - index (number) position of the deletion - count (number) number of element to remove - insertion… (object) #optional elements to insert = (object) set elements that were deleted \*/ setproto.splice = function (index, count, insertion) { index = index < 0 ? mmax(this.length + index, 0) : index; count = mmax(0, mmin(this.length - index, count)); var tail = [], todel = [], args = [], i; for (i = 2; i < arguments.length; i++) { args.push(arguments[i]); } for (i = 0; i < count; i++) { todel.push(this[index + i]); } for (; i < this.length - index; i++) { tail.push(this[index + i]); } var arglen = args.length; for (i = 0; i < arglen + tail.length; i++) { this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen]; } i = this.items.length = this.length -= count - arglen; while (this[i]) { delete this[i++]; } return new Set(todel); }; /*\ * Set.exclude [ method ] ** * Removes given element from the set ** - element (object) element to remove = (boolean) `true` if object was found and removed from the set \*/ setproto.exclude = function (el) { for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) { this.splice(i, 1); return true; } return false; }; /*\ * Set.insertAfter [ method ] ** * Inserts set elements after given element. ** - element (object) set will be inserted after this element = (object) Set object \*/ setproto.insertAfter = function (el) { var i = this.items.length; while (i--) { this.items[i].insertAfter(el); } return this; }; /*\ * Set.getBBox [ method ] ** * Union of all bboxes of the set. See @Element.getBBox. = (object) bounding box descriptor. See @Element.getBBox. \*/ setproto.getBBox = function () { var x = [], y = [], x2 = [], y2 = []; for (var i = this.items.length; i--;) if (!this.items[i].removed) { var box = this.items[i].getBBox(); x.push(box.x); y.push(box.y); x2.push(box.x + box.width); y2.push(box.y + box.height); } x = mmin.apply(0, x); y = mmin.apply(0, y); x2 = mmax.apply(0, x2); y2 = mmax.apply(0, y2); return { x: x, y: y, x2: x2, y2: y2, width: x2 - x, height: y2 - y, cx: x + (x2 - x) / 2, cy: y + (y2 - y) / 2 }; }; /*\ * Set.insertAfter [ method ] ** * Creates a clone of the set. ** = (object) New Set object \*/ setproto.clone = function (s) { s = new Set; for (var i = 0, ii = this.items.length; i < ii; i++) { s.push(this.items[i].clone()); } return s; }; setproto.toString = function () { return "Snap\u2018s set"; }; setproto.type = "set"; // export /*\ * Snap.Set [ property ] ** * Set constructor. \*/ Snap.Set = Set; /*\ * Snap.set [ method ] ** * Creates a set and fills it with list of arguments. ** = (object) New Set object | var r = paper.rect(0, 0, 10, 10), | s1 = Snap.set(), // empty set | s2 = Snap.set(r, paper.circle(100, 100, 20)); // prefilled set \*/ Snap.set = function () { var set = new Set; if (arguments.length) { set.push.apply(set, Array.prototype.slice.call(arguments, 0)); } return set; }; }); ================================================ FILE: src/svg.js ================================================ // Copyright (c) 2013 - 2017 Adobe Systems Incorporated. All rights reserved. // // 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. var Snap = (function(root) { Snap.version = "0.5.1"; /*\ * Snap [ method ] ** * Creates a drawing surface or wraps existing SVG element. ** - width (number|string) width of surface - height (number|string) height of surface * or - DOM (SVGElement) element to be wrapped into Snap structure * or - array (array) array of elements (will return set of elements) * or - query (string) CSS query selector = (object) @Element \*/ function Snap(w, h) { if (w) { if (w.nodeType) { return wrap(w); } if (is(w, "array") && Snap.set) { return Snap.set.apply(Snap, w); } if (w instanceof Element) { return w; } if (h == null) { try { w = glob.doc.querySelector(String(w)); return wrap(w); } catch (e) { return null; } } } w = w == null ? "100%" : w; h = h == null ? "100%" : h; return new Paper(w, h); } Snap.toString = function () { return "Snap v" + this.version; }; Snap._ = {}; var glob = { win: root.window, doc: root.window.document }; Snap._.glob = glob; var has = "hasOwnProperty", Str = String, toFloat = parseFloat, toInt = parseInt, math = Math, mmax = math.max, mmin = math.min, abs = math.abs, pow = math.pow, PI = math.PI, round = math.round, E = "", S = " ", objectToString = Object.prototype.toString, ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i, colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i, bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, separator = Snap._.separator = /[,\s]+/, whitespace = /[\s]/g, commaSpaces = /[\s]*,[\s]*/, hsrg = {hs: 1, rg: 1}, pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\s]*,?[\s]*/ig, idgen = 0, idprefix = "S" + (+new Date).toString(36), ID = function (el) { return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36); }, xlink = "http://www.w3.org/1999/xlink", xmlns = "http://www.w3.org/2000/svg", hub = {}, /*\ * Snap.url [ method ] ** * Wraps path into `"url('')"`. - value (string) path = (string) wrapped path \*/ URL = Snap.url = function (url) { return "url('#" + url + "')"; }; function $(el, attr) { if (attr) { if (el == "#text") { el = glob.doc.createTextNode(attr.text || attr["#text"] || ""); } if (el == "#comment") { el = glob.doc.createComment(attr.text || attr["#text"] || ""); } if (typeof el == "string") { el = $(el); } if (typeof attr == "string") { if (el.nodeType == 1) { if (attr.substring(0, 6) == "xlink:") { return el.getAttributeNS(xlink, attr.substring(6)); } if (attr.substring(0, 4) == "xml:") { return el.getAttributeNS(xmlns, attr.substring(4)); } return el.getAttribute(attr); } else if (attr == "text") { return el.nodeValue; } else { return null; } } if (el.nodeType == 1) { for (var key in attr) if (attr[has](key)) { var val = Str(attr[key]); if (val) { if (key.substring(0, 6) == "xlink:") { el.setAttributeNS(xlink, key.substring(6), val); } else if (key.substring(0, 4) == "xml:") { el.setAttributeNS(xmlns, key.substring(4), val); } else { el.setAttribute(key, val); } } else { el.removeAttribute(key); } } } else if ("text" in attr) { el.nodeValue = attr.text; } } else { el = glob.doc.createElementNS(xmlns, el); } return el; } Snap._.$ = $; Snap._.id = ID; function getAttrs(el) { var attrs = el.attributes, name, out = {}; for (var i = 0; i < attrs.length; i++) { if (attrs[i].namespaceURI == xlink) { name = "xlink:"; } else { name = ""; } name += attrs[i].name; out[name] = attrs[i].textContent; } return out; } function is(o, type) { type = Str.prototype.toLowerCase.call(type); if (type == "finite") { return isFinite(o); } if (type == "array" && (o instanceof Array || Array.isArray && Array.isArray(o))) { return true; } return type == "null" && o === null || type == typeof o && o !== null || type == "object" && o === Object(o) || objectToString.call(o).slice(8, -1).toLowerCase() == type; } /*\ * Snap.format [ method ] ** * Replaces construction of type `{}` to the corresponding argument ** - token (string) string to format - json (object) object which properties are used as a replacement = (string) formatted string > Usage | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z" | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", { | x: 10, | y: 20, | dim: { | width: 40, | height: 50, | "negative width": -40 | } | })); \*/ Snap.format = (function () { var tokenRegex = /\{([^\}]+)\}/g, objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties replacer = function (all, key, obj) { var res = obj; key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) { name = name || quotedName; if (res) { if (name in res) { res = res[name]; } typeof res == "function" && isFunc && (res = res()); } }); res = (res == null || res == obj ? all : res) + ""; return res; }; return function (str, obj) { return Str(str).replace(tokenRegex, function (all, key) { return replacer(all, key, obj); }); }; })(); function clone(obj) { if (typeof obj == "function" || Object(obj) !== obj) { return obj; } var res = new obj.constructor; for (var key in obj) if (obj[has](key)) { res[key] = clone(obj[key]); } return res; } Snap._.clone = clone; function repush(array, item) { for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) { return array.push(array.splice(i, 1)[0]); } } function cacher(f, scope, postprocessor) { function newf() { var arg = Array.prototype.slice.call(arguments, 0), args = arg.join("\u2400"), cache = newf.cache = newf.cache || {}, count = newf.count = newf.count || []; if (cache[has](args)) { repush(count, args); return postprocessor ? postprocessor(cache[args]) : cache[args]; } count.length >= 1e3 && delete cache[count.shift()]; count.push(args); cache[args] = f.apply(scope, arg); return postprocessor ? postprocessor(cache[args]) : cache[args]; } return newf; } Snap._.cacher = cacher; function angle(x1, y1, x2, y2, x3, y3) { if (x3 == null) { var x = x1 - x2, y = y1 - y2; if (!x && !y) { return 0; } return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360; } else { return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3); } } function rad(deg) { return deg % 360 * PI / 180; } function deg(rad) { return rad * 180 / PI % 360; } function x_y() { return this.x + S + this.y; } function x_y_w_h() { return this.x + S + this.y + S + this.width + " \xd7 " + this.height; } /*\ * Snap.rad [ method ] ** * Transform angle to radians - deg (number) angle in degrees = (number) angle in radians \*/ Snap.rad = rad; /*\ * Snap.deg [ method ] ** * Transform angle to degrees - rad (number) angle in radians = (number) angle in degrees \*/ Snap.deg = deg; /*\ * Snap.sin [ method ] ** * Equivalent to `Math.sin()` only works with degrees, not radians. - angle (number) angle in degrees = (number) sin \*/ Snap.sin = function (angle) { return math.sin(Snap.rad(angle)); }; /*\ * Snap.tan [ method ] ** * Equivalent to `Math.tan()` only works with degrees, not radians. - angle (number) angle in degrees = (number) tan \*/ Snap.tan = function (angle) { return math.tan(Snap.rad(angle)); }; /*\ * Snap.cos [ method ] ** * Equivalent to `Math.cos()` only works with degrees, not radians. - angle (number) angle in degrees = (number) cos \*/ Snap.cos = function (angle) { return math.cos(Snap.rad(angle)); }; /*\ * Snap.asin [ method ] ** * Equivalent to `Math.asin()` only works with degrees, not radians. - num (number) value = (number) asin in degrees \*/ Snap.asin = function (num) { return Snap.deg(math.asin(num)); }; /*\ * Snap.acos [ method ] ** * Equivalent to `Math.acos()` only works with degrees, not radians. - num (number) value = (number) acos in degrees \*/ Snap.acos = function (num) { return Snap.deg(math.acos(num)); }; /*\ * Snap.atan [ method ] ** * Equivalent to `Math.atan()` only works with degrees, not radians. - num (number) value = (number) atan in degrees \*/ Snap.atan = function (num) { return Snap.deg(math.atan(num)); }; /*\ * Snap.atan2 [ method ] ** * Equivalent to `Math.atan2()` only works with degrees, not radians. - x (number) value - y (number) value = (number) atan2 in degrees \*/ Snap.atan2 = function (x, y) { return Snap.deg(math.atan2(x, y)); }; /*\ * Snap.angle [ method ] ** * Returns an angle between two or three points - x1 (number) x coord of first point - y1 (number) y coord of first point - x2 (number) x coord of second point - y2 (number) y coord of second point - x3 (number) #optional x coord of third point - y3 (number) #optional y coord of third point = (number) angle in degrees \*/ Snap.angle = angle; /*\ * Snap.len [ method ] ** * Returns distance between two points - x1 (number) x coord of first point - y1 (number) y coord of first point - x2 (number) x coord of second point - y2 (number) y coord of second point = (number) distance \*/ Snap.len = function (x1, y1, x2, y2) { return Math.sqrt(Snap.len2(x1, y1, x2, y2)); }; /*\ * Snap.len2 [ method ] ** * Returns squared distance between two points - x1 (number) x coord of first point - y1 (number) y coord of first point - x2 (number) x coord of second point - y2 (number) y coord of second point = (number) distance \*/ Snap.len2 = function (x1, y1, x2, y2) { return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); }; /*\ * Snap.closestPoint [ method ] ** * Returns closest point to a given one on a given path. - path (Element) path element - x (number) x coord of a point - y (number) y coord of a point = (object) in format { x (number) x coord of the point on the path y (number) y coord of the point on the path length (number) length of the path to the point distance (number) distance from the given point to the path } \*/ // Copied from http://bl.ocks.org/mbostock/8027637 Snap.closestPoint = function (path, x, y) { function distance2(p) { var dx = p.x - x, dy = p.y - y; return dx * dx + dy * dy; } var pathNode = path.node, pathLength = pathNode.getTotalLength(), precision = pathLength / pathNode.pathSegList.numberOfItems * .125, best, bestLength, bestDistance = Infinity; // linear scan for coarse approximation for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) { if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) { best = scan; bestLength = scanLength; bestDistance = scanDistance; } } // binary search for precise estimate precision *= .5; while (precision > .5) { var before, after, beforeLength, afterLength, beforeDistance, afterDistance; if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) { best = before; bestLength = beforeLength; bestDistance = beforeDistance; } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) { best = after; bestLength = afterLength; bestDistance = afterDistance; } else { precision *= .5; } } best = { x: best.x, y: best.y, length: bestLength, distance: Math.sqrt(bestDistance) }; return best; } /*\ * Snap.is [ method ] ** * Handy replacement for the `typeof` operator - o (…) any object or primitive - type (string) name of the type, e.g., `string`, `function`, `number`, etc. = (boolean) `true` if given value is of given type \*/ Snap.is = is; /*\ * Snap.snapTo [ method ] ** * Snaps given value to given grid - values (array|number) given array of values or step of the grid - value (number) value to adjust - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`. = (number) adjusted value \*/ Snap.snapTo = function (values, value, tolerance) { tolerance = is(tolerance, "finite") ? tolerance : 10; if (is(values, "array")) { var i = values.length; while (i--) if (abs(values[i] - value) <= tolerance) { return values[i]; } } else { values = +values; var rem = value % values; if (rem < tolerance) { return value - rem; } if (rem > values - tolerance) { return value - rem + values; } } return value; }; // Colour /*\ * Snap.getRGB [ method ] ** * Parses color string as RGB object - color (string) color string in one of the following formats: #
      #
    • Color name (red, green, cornflowerblue, etc)
    • #
    • #••• — shortened HTML color: (#000, #fc0, etc.)
    • #
    • #•••••• — full length HTML color: (#000000, #bd2300)
    • #
    • rgb(•••, •••, •••) — red, green and blue channels values: (rgb(200, 100, 0))
    • #
    • rgba(•••, •••, •••, •••) — also with opacity
    • #
    • rgb(•••%, •••%, •••%) — same as above, but in %: (rgb(100%, 175%, 0%))
    • #
    • rgba(•••%, •••%, •••%, •••%) — also with opacity
    • #
    • hsb(•••, •••, •••) — hue, saturation and brightness values: (hsb(0.5, 0.25, 1))
    • #
    • hsba(•••, •••, •••, •••) — also with opacity
    • #
    • hsb(•••%, •••%, •••%) — same as above, but in %
    • #
    • hsba(•••%, •••%, •••%, •••%) — also with opacity
    • #
    • hsl(•••, •••, •••) — hue, saturation and luminosity values: (hsb(0.5, 0.25, 0.5))
    • #
    • hsla(•••, •••, •••, •••) — also with opacity
    • #
    • hsl(•••%, •••%, •••%) — same as above, but in %
    • #
    • hsla(•••%, •••%, •••%, •••%) — also with opacity
    • #
    * Note that `%` can be used any time: `rgb(20%, 255, 50%)`. = (object) RGB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #••••••, o error (boolean) true if string can't be parsed o } \*/ Snap.getRGB = cacher(function (colour) { if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) { return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; } if (colour == "none") { return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString}; } !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour)); if (!colour) { return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; } var res, red, green, blue, opacity, t, values, rgb = colour.match(colourRegExp); if (rgb) { if (rgb[2]) { blue = toInt(rgb[2].substring(5), 16); green = toInt(rgb[2].substring(3, 5), 16); red = toInt(rgb[2].substring(1, 3), 16); } if (rgb[3]) { blue = toInt((t = rgb[3].charAt(3)) + t, 16); green = toInt((t = rgb[3].charAt(2)) + t, 16); red = toInt((t = rgb[3].charAt(1)) + t, 16); } if (rgb[4]) { values = rgb[4].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red *= 2.55); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green *= 2.55); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue *= 2.55); rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); } if (rgb[5]) { values = rgb[5].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red /= 100); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green /= 100); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue /= 100); (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); return Snap.hsb2rgb(red, green, blue, opacity); } if (rgb[6]) { values = rgb[6].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red /= 100); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green /= 100); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue /= 100); (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); return Snap.hsl2rgb(red, green, blue, opacity); } red = mmin(math.round(red), 255); green = mmin(math.round(green), 255); blue = mmin(math.round(blue), 255); opacity = mmin(mmax(opacity, 0), 1); rgb = {r: red, g: green, b: blue, toString: rgbtoString}; rgb.hex = "#" + (16777216 | blue | green << 8 | red << 16).toString(16).slice(1); rgb.opacity = is(opacity, "finite") ? opacity : 1; return rgb; } return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; }, Snap); /*\ * Snap.hsb [ method ] ** * Converts HSB values to a hex representation of the color - h (number) hue - s (number) saturation - b (number) value or brightness = (string) hex representation of the color \*/ Snap.hsb = cacher(function (h, s, b) { return Snap.hsb2rgb(h, s, b).hex; }); /*\ * Snap.hsl [ method ] ** * Converts HSL values to a hex representation of the color - h (number) hue - s (number) saturation - l (number) luminosity = (string) hex representation of the color \*/ Snap.hsl = cacher(function (h, s, l) { return Snap.hsl2rgb(h, s, l).hex; }); /*\ * Snap.rgb [ method ] ** * Converts RGB values to a hex representation of the color - r (number) red - g (number) green - b (number) blue = (string) hex representation of the color \*/ Snap.rgb = cacher(function (r, g, b, o) { if (is(o, "finite")) { var round = math.round; return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")"; } return "#" + (16777216 | b | g << 8 | r << 16).toString(16).slice(1); }); var toHex = function (color) { var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0], red = "rgb(255, 0, 0)"; toHex = cacher(function (color) { if (color.toLowerCase() == "red") { return red; } i.style.color = red; i.style.color = color; var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color"); return out == red ? null : out; }); return toHex(color); }, hsbtoString = function () { return "hsb(" + [this.h, this.s, this.b] + ")"; }, hsltoString = function () { return "hsl(" + [this.h, this.s, this.l] + ")"; }, rgbtoString = function () { return this.opacity == 1 || this.opacity == null ? this.hex : "rgba(" + [this.r, this.g, this.b, this.opacity] + ")"; }, prepareRGB = function (r, g, b) { if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) { b = r.b; g = r.g; r = r.r; } if (g == null && is(r, string)) { var clr = Snap.getRGB(r); r = clr.r; g = clr.g; b = clr.b; } if (r > 1 || g > 1 || b > 1) { r /= 255; g /= 255; b /= 255; } return [r, g, b]; }, packageRGB = function (r, g, b, o) { r = math.round(r * 255); g = math.round(g * 255); b = math.round(b * 255); var rgb = { r: r, g: g, b: b, opacity: is(o, "finite") ? o : 1, hex: Snap.rgb(r, g, b), toString: rgbtoString }; is(o, "finite") && (rgb.opacity = o); return rgb; }; /*\ * Snap.color [ method ] ** * Parses the color string and returns an object featuring the color's component values - clr (string) color string in one of the supported formats (see @Snap.getRGB) = (object) Combined RGB/HSB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #••••••, o error (boolean) `true` if string can't be parsed, o h (number) hue, o s (number) saturation, o v (number) value (brightness), o l (number) lightness o } \*/ Snap.color = function (clr) { var rgb; if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) { rgb = Snap.hsb2rgb(clr); clr.r = rgb.r; clr.g = rgb.g; clr.b = rgb.b; clr.opacity = 1; clr.hex = rgb.hex; } else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) { rgb = Snap.hsl2rgb(clr); clr.r = rgb.r; clr.g = rgb.g; clr.b = rgb.b; clr.opacity = 1; clr.hex = rgb.hex; } else { if (is(clr, "string")) { clr = Snap.getRGB(clr); } if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) { rgb = Snap.rgb2hsl(clr); clr.h = rgb.h; clr.s = rgb.s; clr.l = rgb.l; rgb = Snap.rgb2hsb(clr); clr.v = rgb.b; } else { clr = {hex: "none"}; clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1; clr.error = 1; } } clr.toString = rgbtoString; return clr; }; /*\ * Snap.hsb2rgb [ method ] ** * Converts HSB values to an RGB object - h (number) hue - s (number) saturation - v (number) value or brightness = (object) RGB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #•••••• o } \*/ Snap.hsb2rgb = function (h, s, v, o) { if (is(h, "object") && "h" in h && "s" in h && "b" in h) { v = h.b; s = h.s; o = h.o; h = h.h; } h *= 360; var R, G, B, X, C; h = h % 360 / 60; C = v * s; X = C * (1 - abs(h % 2 - 1)); R = G = B = v - C; h = ~~h; R += [C, X, 0, 0, X, C][h]; G += [X, C, C, X, 0, 0][h]; B += [0, 0, X, C, C, X][h]; return packageRGB(R, G, B, o); }; /*\ * Snap.hsl2rgb [ method ] ** * Converts HSL values to an RGB object - h (number) hue - s (number) saturation - l (number) luminosity = (object) RGB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #•••••• o } \*/ Snap.hsl2rgb = function (h, s, l, o) { if (is(h, "object") && "h" in h && "s" in h && "l" in h) { l = h.l; s = h.s; h = h.h; } if (h > 1 || s > 1 || l > 1) { h /= 360; s /= 100; l /= 100; } h *= 360; var R, G, B, X, C; h = h % 360 / 60; C = 2 * s * (l < .5 ? l : 1 - l); X = C * (1 - abs(h % 2 - 1)); R = G = B = l - C / 2; h = ~~h; R += [C, X, 0, 0, X, C][h]; G += [X, C, C, X, 0, 0][h]; B += [0, 0, X, C, C, X][h]; return packageRGB(R, G, B, o); }; /*\ * Snap.rgb2hsb [ method ] ** * Converts RGB values to an HSB object - r (number) red - g (number) green - b (number) blue = (object) HSB object in the following format: o { o h (number) hue, o s (number) saturation, o b (number) brightness o } \*/ Snap.rgb2hsb = function (r, g, b) { b = prepareRGB(r, g, b); r = b[0]; g = b[1]; b = b[2]; var H, S, V, C; V = mmax(r, g, b); C = V - mmin(r, g, b); H = C == 0 ? null : V == r ? (g - b) / C : V == g ? (b - r) / C + 2 : (r - g) / C + 4; H = (H + 360) % 6 * 60 / 360; S = C == 0 ? 0 : C / V; return {h: H, s: S, b: V, toString: hsbtoString}; }; /*\ * Snap.rgb2hsl [ method ] ** * Converts RGB values to an HSL object - r (number) red - g (number) green - b (number) blue = (object) HSL object in the following format: o { o h (number) hue, o s (number) saturation, o l (number) luminosity o } \*/ Snap.rgb2hsl = function (r, g, b) { b = prepareRGB(r, g, b); r = b[0]; g = b[1]; b = b[2]; var H, S, L, M, m, C; M = mmax(r, g, b); m = mmin(r, g, b); C = M - m; H = C == 0 ? null : M == r ? (g - b) / C : M == g ? (b - r) / C + 2 : (r - g) / C + 4; H = (H + 360) % 6 * 60 / 360; L = (M + m) / 2; S = C == 0 ? 0 : L < .5 ? C / (2 * L) : C / (2 - 2 * L); return {h: H, s: S, l: L, toString: hsltoString}; }; // Transformations /*\ * Snap.parsePathString [ method ] ** * Utility method ** * Parses given path string into an array of arrays of path segments - pathString (string|array) path string or array of segments (in the last case it is returned straight away) = (array) array of segments \*/ Snap.parsePathString = function (pathString) { if (!pathString) { return null; } var pth = Snap.path(pathString); if (pth.arr) { return Snap.path.clone(pth.arr); } var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0}, data = []; if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption data = Snap.path.clone(pathString); } if (!data.length) { Str(pathString).replace(pathCommand, function (a, b, c) { var params = [], name = b.toLowerCase(); c.replace(pathValues, function (a, b) { b && params.push(+b); }); if (name == "m" && params.length > 2) { data.push([b].concat(params.splice(0, 2))); name = "l"; b = b == "m" ? "l" : "L"; } if (name == "o" && params.length == 1) { data.push([b, params[0]]); } if (name == "r") { data.push([b].concat(params)); } else while (params.length >= paramCounts[name]) { data.push([b].concat(params.splice(0, paramCounts[name]))); if (!paramCounts[name]) { break; } } }); } data.toString = Snap.path.toString; pth.arr = Snap.path.clone(data); return data; }; /*\ * Snap.parseTransformString [ method ] ** * Utility method ** * Parses given transform string into an array of transformations - TString (string|array) transform string or array of transformations (in the last case it is returned straight away) = (array) array of transformations \*/ var parseTransformString = Snap.parseTransformString = function (TString) { if (!TString) { return null; } var paramCounts = {r: 3, s: 4, t: 2, m: 6}, data = []; if (is(TString, "array") && is(TString[0], "array")) { // rough assumption data = Snap.path.clone(TString); } if (!data.length) { Str(TString).replace(tCommand, function (a, b, c) { var params = [], name = b.toLowerCase(); c.replace(pathValues, function (a, b) { b && params.push(+b); }); data.push([b].concat(params)); }); } data.toString = Snap.path.toString; return data; }; function svgTransform2string(tstr) { var res = []; tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) { params = params.split(/\s*,\s*|\s+/); if (name == "rotate" && params.length == 1) { params.push(0, 0); } if (name == "scale") { if (params.length > 2) { params = params.slice(0, 2); } else if (params.length == 2) { params.push(0, 0); } if (params.length == 1) { params.push(params[0], 0, 0); } } if (name == "skewX") { res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]); } else if (name == "skewY") { res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]); } else { res.push([name.charAt(0)].concat(params)); } return all; }); return res; } Snap._.svgTransform2string = svgTransform2string; Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i; function transform2matrix(tstr, bbox) { var tdata = parseTransformString(tstr), m = new Snap.Matrix; if (tdata) { for (var i = 0, ii = tdata.length; i < ii; i++) { var t = tdata[i], tlen = t.length, command = Str(t[0]).toLowerCase(), absolute = t[0] != command, inver = absolute ? m.invert() : 0, x1, y1, x2, y2, bb; if (command == "t" && tlen == 2){ m.translate(t[1], 0); } else if (command == "t" && tlen == 3) { if (absolute) { x1 = inver.x(0, 0); y1 = inver.y(0, 0); x2 = inver.x(t[1], t[2]); y2 = inver.y(t[1], t[2]); m.translate(x2 - x1, y2 - y1); } else { m.translate(t[1], t[2]); } } else if (command == "r") { if (tlen == 2) { bb = bb || bbox; m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2); } else if (tlen == 4) { if (absolute) { x2 = inver.x(t[2], t[3]); y2 = inver.y(t[2], t[3]); m.rotate(t[1], x2, y2); } else { m.rotate(t[1], t[2], t[3]); } } } else if (command == "s") { if (tlen == 2 || tlen == 3) { bb = bb || bbox; m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2); } else if (tlen == 4) { if (absolute) { x2 = inver.x(t[2], t[3]); y2 = inver.y(t[2], t[3]); m.scale(t[1], t[1], x2, y2); } else { m.scale(t[1], t[1], t[2], t[3]); } } else if (tlen == 5) { if (absolute) { x2 = inver.x(t[3], t[4]); y2 = inver.y(t[3], t[4]); m.scale(t[1], t[2], x2, y2); } else { m.scale(t[1], t[2], t[3], t[4]); } } } else if (command == "m" && tlen == 7) { m.add(t[1], t[2], t[3], t[4], t[5], t[6]); } } } return m; } Snap._.transform2matrix = transform2matrix; Snap._unit2px = unit2px; var contains = glob.doc.contains || glob.doc.compareDocumentPosition ? function (a, b) { var adown = a.nodeType == 9 ? a.documentElement : a, bup = b && b.parentNode; return a == bup || !!(bup && bup.nodeType == 1 && ( adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16 )); } : function (a, b) { if (b) { while (b) { b = b.parentNode; if (b == a) { return true; } } } return false; }; function getSomeDefs(el) { var p = el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || el.node.parentNode && wrap(el.node.parentNode) || Snap.select("svg") || Snap(0, 0), pdefs = p.select("defs"), defs = pdefs == null ? false : pdefs.node; if (!defs) { defs = make("defs", p.node).node; } return defs; } function getSomeSVG(el) { return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg"); } Snap._.getSomeDefs = getSomeDefs; Snap._.getSomeSVG = getSomeSVG; function unit2px(el, name, value) { var svg = getSomeSVG(el).node, out = {}, mgr = svg.querySelector(".svg---mgr"); if (!mgr) { mgr = $("rect"); $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"}); svg.appendChild(mgr); } function getW(val) { if (val == null) { return E; } if (val == +val) { return val; } $(mgr, {width: val}); try { return mgr.getBBox().width; } catch (e) { return 0; } } function getH(val) { if (val == null) { return E; } if (val == +val) { return val; } $(mgr, {height: val}); try { return mgr.getBBox().height; } catch (e) { return 0; } } function set(nam, f) { if (name == null) { out[nam] = f(el.attr(nam) || 0); } else if (nam == name) { out = f(value == null ? el.attr(nam) || 0 : value); } } switch (el.type) { case "rect": set("rx", getW); set("ry", getH); case "image": set("width", getW); set("height", getH); case "text": set("x", getW); set("y", getH); break; case "circle": set("cx", getW); set("cy", getH); set("r", getW); break; case "ellipse": set("cx", getW); set("cy", getH); set("rx", getW); set("ry", getH); break; case "line": set("x1", getW); set("x2", getW); set("y1", getH); set("y2", getH); break; case "marker": set("refX", getW); set("markerWidth", getW); set("refY", getH); set("markerHeight", getH); break; case "radialGradient": set("fx", getW); set("fy", getH); break; case "tspan": set("dx", getW); set("dy", getH); break; default: set(name, getW); } svg.removeChild(mgr); return out; } /*\ * Snap.select [ method ] ** * Wraps a DOM element specified by CSS selector as @Element - query (string) CSS selector of the element = (Element) the current element \*/ Snap.select = function (query) { query = Str(query).replace(/([^\\]):/g, "$1\\:"); return wrap(glob.doc.querySelector(query)); }; /*\ * Snap.selectAll [ method ] ** * Wraps DOM elements specified by CSS selector as set or array of @Element - query (string) CSS selector of the element = (Element) the current element \*/ Snap.selectAll = function (query) { var nodelist = glob.doc.querySelectorAll(query), set = (Snap.set || Array)(); for (var i = 0; i < nodelist.length; i++) { set.push(wrap(nodelist[i])); } return set; }; function add2group(list) { if (!is(list, "array")) { list = Array.prototype.slice.call(arguments, 0); } var i = 0, j = 0, node = this.node; while (this[i]) delete this[i++]; for (i = 0; i < list.length; i++) { if (list[i].type == "set") { list[i].forEach(function (el) { node.appendChild(el.node); }); } else { node.appendChild(list[i].node); } } var children = node.childNodes; for (i = 0; i < children.length; i++) { this[j++] = wrap(children[i]); } return this; } // Hub garbage collector every 10s setInterval(function () { for (var key in hub) if (hub[has](key)) { var el = hub[key], node = el.node; if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) { delete hub[key]; } } }, 1e4); function Element(el) { if (el.snap in hub) { return hub[el.snap]; } var svg; try { svg = el.ownerSVGElement; } catch(e) {} /*\ * Element.node [ property (object) ] ** * Gives you a reference to the DOM object, so you can assign event handlers or just mess around. > Usage | // draw a circle at coordinate 10,10 with radius of 10 | var c = paper.circle(10, 10, 10); | c.node.onclick = function () { | c.attr("fill", "red"); | }; \*/ this.node = el; if (svg) { this.paper = new Paper(svg); } /*\ * Element.type [ property (string) ] ** * SVG tag name of the given element. \*/ this.type = el.tagName || el.nodeName; var id = this.id = ID(this); this.anims = {}; this._ = { transform: [] }; el.snap = id; hub[id] = this; if (this.type == "g") { this.add = add2group; } if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) { for (var method in Paper.prototype) if (Paper.prototype[has](method)) { this[method] = Paper.prototype[method]; } } } /*\ * Element.attr [ method ] ** * Gets or sets given attributes of the element. ** - params (object) contains key-value pairs of attributes you want to set * or - param (string) name of the attribute = (Element) the current element * or = (string) value of attribute > Usage | el.attr({ | fill: "#fc0", | stroke: "#000", | strokeWidth: 2, // CamelCase... | "fill-opacity": 0.5, // or dash-separated names | width: "*=2" // prefixed values | }); | console.log(el.attr("fill")); // #fc0 * Prefixed values in format `"+=10"` supported. All four operations * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+` * and `-`: `"+=2em"`. \*/ Element.prototype.attr = function (params, value) { var el = this, node = el.node; if (!params) { if (node.nodeType != 1) { return { text: node.nodeValue }; } var attr = node.attributes, out = {}; for (var i = 0, ii = attr.length; i < ii; i++) { out[attr[i].nodeName] = attr[i].nodeValue; } return out; } if (is(params, "string")) { if (arguments.length > 1) { var json = {}; json[params] = value; params = json; } else { return eve("snap.util.getattr." + params, el).firstDefined(); } } for (var att in params) { if (params[has](att)) { eve("snap.util.attr." + att, el, params[att]); } } return el; }; /*\ * Snap.parse [ method ] ** * Parses SVG fragment and converts it into a @Fragment ** - svg (string) SVG string = (Fragment) the @Fragment \*/ Snap.parse = function (svg) { var f = glob.doc.createDocumentFragment(), full = true, div = glob.doc.createElement("div"); svg = Str(svg); if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) { svg = "" + svg + ""; full = false; } div.innerHTML = svg; svg = div.getElementsByTagName("svg")[0]; if (svg) { if (full) { f = svg; } else { while (svg.firstChild) { f.appendChild(svg.firstChild); } } } return new Fragment(f); }; function Fragment(frag) { this.node = frag; } /*\ * Snap.fragment [ method ] ** * Creates a DOM fragment from a given list of elements or strings ** - varargs (…) SVG string = (Fragment) the @Fragment \*/ Snap.fragment = function () { var args = Array.prototype.slice.call(arguments, 0), f = glob.doc.createDocumentFragment(); for (var i = 0, ii = args.length; i < ii; i++) { var item = args[i]; if (item.node && item.node.nodeType) { f.appendChild(item.node); } if (item.nodeType) { f.appendChild(item); } if (typeof item == "string") { f.appendChild(Snap.parse(item).node); } } return new Fragment(f); }; function make(name, parent) { var res = $(name); parent.appendChild(res); var el = wrap(res); return el; } function Paper(w, h) { var res, desc, defs, proto = Paper.prototype; if (w && w.tagName && w.tagName.toLowerCase() == "svg") { if (w.snap in hub) { return hub[w.snap]; } var doc = w.ownerDocument; res = new Element(w); desc = w.getElementsByTagName("desc")[0]; defs = w.getElementsByTagName("defs")[0]; if (!desc) { desc = $("desc"); desc.appendChild(doc.createTextNode("Created with Snap")); res.node.appendChild(desc); } if (!defs) { defs = $("defs"); res.node.appendChild(defs); } res.defs = defs; for (var key in proto) if (proto[has](key)) { res[key] = proto[key]; } res.paper = res.root = res; } else { res = make("svg", glob.doc.body); $(res.node, { height: h, version: 1.1, width: w, xmlns: xmlns }); } return res; } function wrap(dom) { if (!dom) { return dom; } if (dom instanceof Element || dom instanceof Fragment) { return dom; } if (dom.tagName && dom.tagName.toLowerCase() == "svg") { return new Paper(dom); } if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") { return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]); } return new Element(dom); } Snap._.make = make; Snap._.wrap = wrap; /*\ * Paper.el [ method ] ** * Creates an element on paper with a given name and no attributes ** - name (string) tag name - attr (object) attributes = (Element) the current element > Usage | var c = paper.circle(10, 10, 10); // is the same as... | var c = paper.el("circle").attr({ | cx: 10, | cy: 10, | r: 10 | }); | // and the same as | var c = paper.el("circle", { | cx: 10, | cy: 10, | r: 10 | }); \*/ Paper.prototype.el = function (name, attr) { var el = make(name, this.node); attr && el.attr(attr); return el; }; /*\ * Element.children [ method ] ** * Returns array of all the children of the element. = (array) array of Elements \*/ Element.prototype.children = function () { var out = [], ch = this.node.childNodes; for (var i = 0, ii = ch.length; i < ii; i++) { out[i] = Snap(ch[i]); } return out; }; function jsonFiller(root, o) { for (var i = 0, ii = root.length; i < ii; i++) { var item = { type: root[i].type, attr: root[i].attr() }, children = root[i].children(); o.push(item); if (children.length) { jsonFiller(children, item.childNodes = []); } } } /*\ * Element.toJSON [ method ] ** * Returns object representation of the given element and all its children. = (object) in format o { o type (string) this.type, o attr (object) attributes map, o childNodes (array) optional array of children in the same format o } \*/ Element.prototype.toJSON = function () { var out = []; jsonFiller([this], out); return out[0]; }; // default eve.on("snap.util.getattr", function () { var att = eve.nt(); att = att.substring(att.lastIndexOf(".") + 1); var css = att.replace(/[A-Z]/g, function (letter) { return "-" + letter.toLowerCase(); }); if (cssAttr[has](css)) { return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css); } else { return $(this.node, att); } }); var cssAttr = { "alignment-baseline": 0, "baseline-shift": 0, "clip": 0, "clip-path": 0, "clip-rule": 0, "color": 0, "color-interpolation": 0, "color-interpolation-filters": 0, "color-profile": 0, "color-rendering": 0, "cursor": 0, "direction": 0, "display": 0, "dominant-baseline": 0, "enable-background": 0, "fill": 0, "fill-opacity": 0, "fill-rule": 0, "filter": 0, "flood-color": 0, "flood-opacity": 0, "font": 0, "font-family": 0, "font-size": 0, "font-size-adjust": 0, "font-stretch": 0, "font-style": 0, "font-variant": 0, "font-weight": 0, "glyph-orientation-horizontal": 0, "glyph-orientation-vertical": 0, "image-rendering": 0, "kerning": 0, "letter-spacing": 0, "lighting-color": 0, "marker": 0, "marker-end": 0, "marker-mid": 0, "marker-start": 0, "mask": 0, "opacity": 0, "overflow": 0, "pointer-events": 0, "shape-rendering": 0, "stop-color": 0, "stop-opacity": 0, "stroke": 0, "stroke-dasharray": 0, "stroke-dashoffset": 0, "stroke-linecap": 0, "stroke-linejoin": 0, "stroke-miterlimit": 0, "stroke-opacity": 0, "stroke-width": 0, "text-anchor": 0, "text-decoration": 0, "text-rendering": 0, "unicode-bidi": 0, "visibility": 0, "word-spacing": 0, "writing-mode": 0 }; eve.on("snap.util.attr", function (value) { var att = eve.nt(), attr = {}; att = att.substring(att.lastIndexOf(".") + 1); attr[att] = value; var style = att.replace(/-(\w)/gi, function (all, letter) { return letter.toUpperCase(); }), css = att.replace(/[A-Z]/g, function (letter) { return "-" + letter.toLowerCase(); }); if (cssAttr[has](css)) { this.node.style[style] = value == null ? E : value; } else { $(this.node, attr); } }); (function (proto) {}(Paper.prototype)); // simple ajax /*\ * Snap.ajax [ method ] ** * Simple implementation of Ajax ** - url (string) URL - postData (object|string) data for post request - callback (function) callback - scope (object) #optional scope of callback * or - url (string) URL - callback (function) callback - scope (object) #optional scope of callback = (XMLHttpRequest) the XMLHttpRequest object, just in case \*/ Snap.ajax = function (url, postData, callback, scope){ var req = new XMLHttpRequest, id = ID(); if (req) { if (is(postData, "function")) { scope = callback; callback = postData; postData = null; } else if (is(postData, "object")) { var pd = []; for (var key in postData) if (postData.hasOwnProperty(key)) { pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key])); } postData = pd.join("&"); } req.open(postData ? "POST" : "GET", url, true); if (postData) { req.setRequestHeader("X-Requested-With", "XMLHttpRequest"); req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); } if (callback) { eve.once("snap.ajax." + id + ".0", callback); eve.once("snap.ajax." + id + ".200", callback); eve.once("snap.ajax." + id + ".304", callback); } req.onreadystatechange = function() { if (req.readyState != 4) return; eve("snap.ajax." + id + "." + req.status, scope, req); }; if (req.readyState == 4) { return req; } req.send(postData); return req; } }; /*\ * Snap.load [ method ] ** * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX) ** - url (string) URL - callback (function) callback - scope (object) #optional scope of callback \*/ Snap.load = function (url, callback, scope) { Snap.ajax(url, function (req) { var f = Snap.parse(req.responseText); scope ? callback.call(scope, f) : callback(f); }); }; var getOffset = function (elem) { var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement, clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop, left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft; return { y: top, x: left }; }; /*\ * Snap.getElementByPoint [ method ] ** * Returns you topmost element under given point. ** = (object) Snap element object - x (number) x coordinate from the top left corner of the window - y (number) y coordinate from the top left corner of the window > Usage | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); \*/ Snap.getElementByPoint = function (x, y) { var paper = this, svg = paper.canvas, target = glob.doc.elementFromPoint(x, y); if (glob.win.opera && target.tagName == "svg") { var so = getOffset(target), sr = target.createSVGRect(); sr.x = x - so.x; sr.y = y - so.y; sr.width = sr.height = 1; var hits = target.getIntersectionList(sr, null); if (hits.length) { target = hits[hits.length - 1]; } } if (!target) { return null; } return wrap(target); }; /*\ * Snap.plugin [ method ] ** * Let you write plugins. You pass in a function with five arguments, like this: | Snap.plugin(function (Snap, Element, Paper, global, Fragment) { | Snap.newmethod = function () {}; | Element.prototype.newmethod = function () {}; | Paper.prototype.newmethod = function () {}; | }); * Inside the function you have access to all main objects (and their * prototypes). This allow you to extend anything you want. ** - f (function) your plugin body \*/ Snap.plugin = function (f) { f(Snap, Element, Paper, glob, Fragment); }; glob.win.Snap = Snap; return Snap; }(window || this)); ================================================ FILE: template.dot ================================================ Snap.svg API Reference
    {{~it.out :item:index}}

    {{=item[0].name}}

    {{~item :line:i}} {{ if (i > 0) { }} {{ if (line.text) { }}

    {{=line.text.join("

    ")}}

    {{ } }} {{ if (line.attr) { }}

    Parameters

      {{~line.attr :attr:j}}
    1. {{=attr.name}} {{~attr.type :type:k}}{{=type}} {{~}} {{=attr.desc}}
    2. {{~}}
    {{ } }} {{ if (line.html) { }}{{=line.html}} {{ } }} {{ if (line.head) { }}

    {{=line.head}}

    {{ } }} {{ if (line.code) { }}
    {{=line.code.join("\n")}}
    {{ } }} {{ if (line.rtrn) { }}

    Returns: {{~line.rtrn.type :type:k}} {{=type}} {{~}} {{=line.rtrn.desc}}

    {{ } }} {{ if (line.json) { }}
      {{~line.json :a:j}} {{ if (a.key) { }}
    1. {{=a.key}} {{~a.type :type:k}}{{=type}} {{~}} {{=a.desc}}
    2. {{ } else if (a.start) { }}
    3. {{=a.start}}
        {{ } else if (a.end) { }}
    4. {{=a.end}}
    5. {{ } else { }}
    6. {{=a}}
    7. {{ } }} {{~}}
    {{ } }} {{ } }} {{~}}
    {{~}}
    ================================================ FILE: test/attradd.js ================================================ describe("Attributes += methods", function () { var s, r; beforeEach(function () { s = Snap(100, 100); r = s.rect(10, 10, 50, 50); }); afterEach(function () { s.remove(); }); it("+=10", function () { r.attr({x: "+=10"}); expect(r.node.getAttribute("x")).to.be("20"); }); it("-=10", function () { r.attr({x: "-=10"}); expect(r.node.getAttribute("x")).to.be("0"); }); it("*=2", function () { r.attr({x: "*=2"}); expect(r.node.getAttribute("x")).to.be("20"); }); it("/=2", function () { r.attr({x: "/=2"}); expect(r.node.getAttribute("x")).to.be("5"); }); it("+=1em", function () { var em = s.rect(0, 0, "1em", "1em"); em = em.getBBox().w; r.attr({x: "+=1em"}); expect(r.node.getAttribute("x")).to.eql(10 + em); }); it("-=.3em", function () { var em = s.rect(0, 0, "1em", "1em"); em = em.getBBox().w; r.attr({x: "-=.3em"}); expect((+r.node.getAttribute("x")).toFixed(2)).to.eql((10 - em * .3).toFixed(2)); }); }); ================================================ FILE: test/attrs.js ================================================ describe("Attributes", function () { var s, r; beforeEach(function () { s = Snap(100, 100); r = s.rect(10, 10, 50, 50); }); afterEach(function () { s.remove(); }); function colorTestProp(key) { var o = {}; return function () { o[key] = "#fc0"; r.attr(o); expect(r.node.getAttribute(key)).to.be("#ffcc00"); expect(r.attr(key)).to.be("rgb(255, 204, 0)"); o[key] = "rgb(255, 204, 0)"; r.attr(o); expect(r.node.getAttribute(key)).to.be("#ffcc00"); o[key] = "hsl(0.1333, 1, .5)"; r.attr(o); expect(r.node.getAttribute(key)).to.be("#ffcc00"); o[key] = "hsb(0.1333, 1, 1)"; r.attr(o); expect(r.node.getAttribute(key)).to.be("#ffcc00"); o[key] = "rgba(255, 204, 0, .5)"; r.attr(o); expect(r.node.getAttribute(key)).to.be("rgba(255,204,0,0.5)"); o[key] = "hsla(0.1333, 1, .5, .5)"; r.attr(o); expect(r.node.getAttribute(key)).to.be("rgba(255,204,0,0.5)"); o[key] = "hsba(0.1333, 1, 1, .5)"; r.attr(o); expect(r.node.getAttribute(key)).to.be("rgba(255,204,0,0.5)"); }; } function colorTestStyle(key) { var o = {}; return function () { function val() { return Snap.color(r.node.getAttribute(key)).hex; } o[key] = "#fc0"; r.attr(o); expect(val()).to.be("#ffcc00"); o[key] = "rgb(255, 204, 0)"; r.attr(o); expect(val()).to.be("#ffcc00"); o[key] = "hsl(0.1333, 1, .5)"; r.attr(o); expect(val()).to.be("#ffcc00"); o[key] = "hsb(0.1333, 1, 1)"; r.attr(o); expect(val()).to.be("#ffcc00"); o[key] = "rgba(255, 204, 0, .5)"; r.attr(o); expect(val()).to.be("#ffcc00"); o[key] = "hsla(0.1333, 1, .5, .5)"; r.attr(o); expect(val()).to.be("#ffcc00"); o[key] = "hsba(0.1333, 1, 1, .5)"; r.attr(o); expect(val()).to.be("#ffcc00"); }; } it("sets fill", colorTestProp("fill")); it("sets stroke", colorTestStyle("stroke")); it("circle core attributes", function () { var c = s.circle(10, 20, 30); expect(c.node.getAttribute("cx")).to.be("10"); expect(c.node.getAttribute("cy")).to.be("20"); expect(c.node.getAttribute("r")).to.be("30"); c.attr({ cx: 40, cy: 50, r: "5%" }); expect(c.node.getAttribute("cx")).to.be("40"); expect(c.node.getAttribute("cy")).to.be("50"); expect(c.node.getAttribute("r")).to.be("5%"); }); it("rect core attributes", function () { var c = s.rect(10, 20, 30, 40); expect(c.node.getAttribute("x")).to.be("10"); expect(c.node.getAttribute("y")).to.be("20"); expect(c.node.getAttribute("width")).to.be("30"); expect(c.node.getAttribute("height")).to.be("40"); c.attr({ x: 40, y: 50, width: "5%", height: "6%", r: 10 }); expect(c.node.getAttribute("x")).to.be("40"); expect(c.node.getAttribute("y")).to.be("50"); expect(c.node.getAttribute("width")).to.be("5%"); expect(c.node.getAttribute("height")).to.be("6%"); expect(c.node.getAttribute("rx")).to.be("10"); expect(c.node.getAttribute("ry")).to.be("10"); }); it("ellipse core attributes", function () { var c = s.ellipse(10, 20, 30, 40); expect(c.node.getAttribute("cx")).to.be("10"); expect(c.node.getAttribute("cy")).to.be("20"); expect(c.node.getAttribute("rx")).to.be("30"); expect(c.node.getAttribute("ry")).to.be("40"); c.attr({ cx: 40, cy: 50, rx: "5%", ry: "6%" }); expect(c.node.getAttribute("cx")).to.be("40"); expect(c.node.getAttribute("cy")).to.be("50"); expect(c.node.getAttribute("rx")).to.be("5%"); expect(c.node.getAttribute("ry")).to.be("6%"); }); it("path core attributes", function () { var c = s.path("M10,10 110,10"); expect(c.node.getAttribute("d")).to.be("M10,10 110,10"); c.attr({d: "M10,10 210,10"}); expect(c.node.getAttribute("d")).to.be("M10,10 210,10"); c.attr({path: "M10,10 310,10"}); expect(c.node.getAttribute("d")).to.be("M10,10 310,10"); }); it("text core attributes", function () { var c = s.text(10, 15, "testing"); expect(c.node.getAttribute("x")).to.be("10"); expect(c.node.getAttribute("y")).to.be("15"); expect(c.node.textContent).to.be("testing"); c.attr({ x: 20, y: 25, text: "texting" }); expect(c.node.getAttribute("x")).to.be("20"); expect(c.node.getAttribute("y")).to.be("25"); expect(c.node.textContent).to.be("texting"); }); it("line core attributes", function () { var c = s.line(10, 15, 110, 17); expect(c.node.getAttribute("x1")).to.be("10"); expect(c.node.getAttribute("y1")).to.be("15"); expect(c.node.getAttribute("x2")).to.be("110"); expect(c.node.getAttribute("y2")).to.be("17"); c.attr({ x1: 20, y1: 25, x2: 220, y2: 27 }); expect(c.node.getAttribute("x1")).to.be("20"); expect(c.node.getAttribute("y1")).to.be("25"); expect(c.node.getAttribute("x2")).to.be("220"); expect(c.node.getAttribute("y2")).to.be("27"); }); it("polyline core attributes", function () { var c = s.polyline(10, 15, 20, 25, 30, 35); expect(c.node.getAttribute("points")).to.be("10,15,20,25,30,35"); c.attr({ points: [20, 25, 30, 35, 40, 45] }); expect(c.node.getAttribute("points")).to.be("20,25,30,35,40,45"); }); it("polygon core attributes", function () { var c = s.polygon(10, 15, 20, 25, 30, 35); expect(c.node.getAttribute("points")).to.be("10,15,20,25,30,35"); c.attr({ points: [20, 25, 30, 35, 40, 45] }); expect(c.node.getAttribute("points")).to.be("20,25,30,35,40,45"); }); }); ================================================ FILE: test/class.js ================================================ describe("Class methods", function () { var s, r; beforeEach(function () { s = Snap(100, 100); r = s.rect(10, 10, 50, 50); }); afterEach(function () { s.remove(); }); it("elproto.addClass one", function() { r.addClass("one"); expect(r.node.className.baseVal).to.be("one"); }); it("elproto.addClass two", function() { r.addClass("one two"); expect(r.node.className.baseVal).to.be("one two"); }); it("elproto.addClass two (spacing)", function() { r.addClass("\tone two "); expect(r.node.className.baseVal).to.be("one two"); }); it("elproto.addClass three", function() { r.addClass("one two three"); expect(r.node.className.baseVal).to.be("one two three"); }); it("elproto.removeClass 1", function() { r.addClass("one two three"); r.removeClass("two"); expect(r.node.className.baseVal).to.be("one three"); }); it("elproto.removeClass 2", function() { r.addClass("one two three"); r.removeClass("two one"); expect(r.node.className.baseVal).to.be("three"); }); it("elproto.hasClass", function() { r.addClass("one two three"); expect(r.hasClass("two")).to.be(true); }); it("elproto.toggleClass 1", function() { r.addClass("one two three"); r.toggleClass("two one"); expect(r.node.className.baseVal).to.be("three"); }); it("elproto.toggleClass 2", function() { r.addClass("one three"); r.toggleClass("two one", false); expect(r.node.className.baseVal).to.be("three"); }); it("elproto.toggleClass 3", function() { r.addClass("one three"); r.toggleClass("two one", true); expect(r.node.className.baseVal).to.be("one three two"); }); }); ================================================ FILE: test/colors.js ================================================ describe("Colours", function () { it("parses hex colour", function () { expect(Snap.color("#fC0").hex).to.be("#ffcc00"); }); it("parses RGB", function () { var c = Snap.color("rgb(255, 204, 0)"); expect(c.hex).to.be("#ffcc00"); expect(c.r).to.be(255); expect(c.g).to.be(204); expect(c.b).to.be(0); }); it("parses RGB - %", function () { var c = Snap.color("rgb(100%, 80%, 0%)"); expect(c.hex).to.be("#ffcc00"); expect(c.r).to.be(255); expect(c.g).to.be(204); expect(c.b).to.be(0); }); it("parses HSL", function () { var c = Snap.color("hsl(0.1333, 1, .5)"); expect(c.hex).to.be("#ffcc00"); expect(c.h.toFixed(3)).to.be("0.133"); expect(c.s).to.be(1); expect(c.l).to.be(.5); }); it("parses HSL - %", function () { var c = Snap.color("hsl(13.33%, 100%, 50%)"); expect(c.hex).to.be("#ffcc00"); expect(c.h.toFixed(3)).to.be("0.133"); expect(c.s).to.be(1); expect(c.l).to.be(.5); }); it("parses HSB", function () { var c = Snap.color("hsb(0.1333, 1, 1)"); expect(c.hex).to.be("#ffcc00"); expect(c.h.toFixed(3)).to.be("0.133"); expect(c.s).to.be(1); expect(c.v).to.be(1); }); it("parses HSB - %", function () { var c = Snap.color("hsb(13.33%, 100%, 100%)"); expect(c.hex).to.be("#ffcc00"); expect(c.h.toFixed(3)).to.be("0.133"); expect(c.s).to.be(1); expect(c.v).to.be(1); }); it("parses RGBA", function () { var c = Snap.color("rgba(255, 204, 0, .75)"); expect(c.hex).to.be("#ffcc00"); expect(c.r).to.be(255); expect(c.g).to.be(204); expect(c.b).to.be(0); expect(c.opacity).to.be(.75); }); it("parses HSLA", function () { var c = Snap.color("hsla(0.1333, 1, .5, .5)"); expect(c.hex).to.be("#ffcc00"); expect(c.r).to.be(255); expect(c.g).to.be(204); expect(c.b).to.be(0); expect(c.opacity).to.be(.5); }); it("parses HSBA", function () { var c = Snap.color("hsba(0.1333, 1, 1, .5)"); expect(c.hex).to.be("#ffcc00"); expect(c.r).to.be(255); expect(c.g).to.be(204); expect(c.b).to.be(0); expect(c.opacity).to.be(.5); }); it("parses names", function () { var c = Snap.color("DodgerBlue"); expect(c.hex).to.be("#1e90ff"); c = Snap.color("FireBrick"); expect(c.hex).to.be("#b22222"); c = Snap.color("MintCream"); expect(c.hex).to.be("#f5fffa"); }); it("parses to RGB", function() { var expectRGB = function(str) { var c = Snap.getRGB(str); expect(c.hex).to.be("#ffcc00"); expect(c.r).to.be(255); expect(c.g).to.be(204); expect(c.b).to.be(0); expect(c.error).to.not.be(true); }; expectRGB("#fc0"); expectRGB("#ffcc00"); expectRGB("rgb(255, 204, 0)"); expectRGB("rgb(100%, 80%, 0%)"); expectRGB("hsb(0.1333, 1, 1)"); expectRGB("hsb(13.33%, 100%, 100%)"); expectRGB("hsl(0.1333, 1, .5)"); expectRGB("hsl(13.33%, 100%, 50%)"); expectRGB("rgba(255, 204, 0, .75)"); expectRGB("hsla(0.1333, 1, .5, .5)"); expectRGB("hsba(0.1333, 1, 1, .5)"); var c = Snap.getRGB("DodgerBlue"); expect(c.hex).to.be("#1e90ff"); expect(c.r).to.be(30); expect(c.g).to.be(144); expect(c.b).to.be(255); expect(c.error).to.not.be(true); c = Snap.getRGB("foobar"); expect(!!c.error).to.be(true); c = Snap.getRGB("#zzz"); expect(!!c.error).to.be(true); c = Snap.getRGB("rgb(255)"); expect(!!c.error).to.be(true); c = Snap.getRGB("hsl(0, 0, 0"); expect(!!c.error).to.be(true); c = Snap.getRGB("rab(0, 0, 0)"); expect(!!c.error).to.be(true); }); it("creates hsb", function() { var str = Snap.hsb(0.13333, 1, 1); expect(str).to.be("#ffcc00"); str = Snap.hsb(0, 0.5, 0.5); expect(str).to.be("#804040"); }); it("creates rgb from hsb", function() { var rgb = Snap.hsb2rgb(0.13333, 1, 1); expect(rgb.r).to.be(255); expect(rgb.g).to.be(204); expect(rgb.b).to.be(0); expect(rgb.hex).to.be("#ffcc00"); rgb = Snap.hsb2rgb(0, 0.5, 0.5); expect(rgb.r).to.be(128); expect(rgb.g).to.be(64); expect(rgb.b).to.be(64); expect(rgb.hex).to.be("#804040"); }); it("creates hsl", function() { var str = Snap.hsl(0.13333, 1, 0.5); expect(str).to.be("#ffcc00"); str = Snap.hsl(0, 1, 0.75); expect(str).to.be("#ff8080"); }); it("creates rgb from hsl", function() { var rgb = Snap.hsl2rgb(0.13333, 1, 0.5); expect(rgb.r).to.be(255); expect(rgb.g).to.be(204); expect(rgb.b).to.be(0); expect(rgb.hex).to.be("#ffcc00"); rgb = Snap.hsl2rgb(0, 1, 0.75); expect(rgb.r).to.be(255); expect(rgb.g).to.be(128); expect(rgb.b).to.be(128); expect(rgb.hex).to.be("#ff8080"); }); it("creates rgb", function() { var str = Snap.rgb(255, 204, 0); expect(str).to.be("#ffcc00"); str = Snap.rgb(64, 128, 255); expect(str).to.be("#4080ff"); }); it("creates hsb from rgb", function() { var hsb = Snap.rgb2hsb(255, 204, 0); expect(hsb.h.toFixed(3)).to.be("0.133"); expect(hsb.s).to.be(1); expect(hsb.b).to.be(1); hsb = Snap.rgb2hsb(128, 64, 64); expect(hsb.h).to.be(0); expect(hsb.s.toFixed(1)).to.be("0.5"); expect(hsb.b.toFixed(1)).to.be("0.5"); }); it("creates hsl from rgb", function() { var hsl = Snap.rgb2hsl(255, 204, 0); expect(hsl.h.toFixed(3)).to.be("0.133"); expect(hsl.s).to.be(1); expect(hsl.l).to.be(0.5); hsl = Snap.rgb2hsl(255, 128, 128); expect(hsl.h).to.be(0); expect(hsl.s).to.be(1); expect(hsl.l.toFixed(2)).to.be("0.75"); }); }); ================================================ FILE: test/element.js ================================================ describe("Element methods", function () { var s; beforeEach(function () { s = Snap(100, 100); }); afterEach(function () { s.remove(); }); /* DOM manipulation: - add/append - prepend - after - insertAfter - before - insertBefore - clone - parent - remove */ it("Element.add (for Element)", function () { var rect = s.rect(10, 20, 30, 40); var result = s.add(rect); expect(rect.node.parentNode).to.be(s.node); expect(s.node.lastChild).to.be(rect.node); expect(result).to.be(s); }); it("Element.add (for Set)", function () { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2); var result = s.add(set); expect(rect1.node.parentNode).to.be(s.node); expect(rect2.node.parentNode).to.be(s.node); expect(result).to.be(s); }); it("Element.append (for Element)", function () { var rect = s.rect(10, 20, 30, 40); var result = s.append(rect); expect(rect.node.parentNode).to.be(s.node); expect(s.node.lastChild).to.be(rect.node); expect(result).to.be(s); }); it("Element.appendTo (for Element)", function () { var rect = s.rect(10, 20, 30, 40); var result = rect.appendTo(s); expect(rect.node.parentNode).to.be(s.node); expect(s.node.lastChild).to.be(rect.node); expect(result).to.be(rect); }); it("Element.append (for Set)", function () { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2); var result = s.append(set); expect(rect1.node.parentNode).to.be(s.node); expect(rect2.node.parentNode).to.be(s.node); expect(result).to.be(s); }); it("Element.after", function() { var circle = s.circle(10, 20, 30); var rect = s.rect(10, 20, 30, 40); var result = circle.after(rect); expect(circle.node.nextSibling).to.be(rect.node); expect(result).to.be(circle); }); it("Element.prepend", function() { var rect = s.rect(10, 20, 30, 40); var circle = s.circle(10, 20, 30); var group = s.group(); s.append(group); var result = group.prepend(rect); expect(group.node.firstChild).to.be(rect.node); expect(result).to.be(group); result = group.prepend(circle); expect(group.node.firstChild).to.be(circle.node); expect(result).to.be(group); }); it("Element.prependTo", function() { var rect = s.rect(10, 20, 30, 40); var circle = s.circle(10, 20, 30); var group = s.group(); s.append(group); var result = rect.prependTo(group); expect(group.node.firstChild).to.be(rect.node); expect(result).to.be(rect); result = circle.prependTo(group); expect(group.node.firstChild).to.be(circle.node); expect(result).to.be(circle); }); it("Element.insertAfter", function() { var circle = s.circle(10, 20, 30); var rect = s.rect(10, 20, 30, 40); var result = rect.insertAfter(circle); expect(circle.node.nextSibling).to.be(rect.node); expect(result).to.be(rect); }); it("Element.before", function() { var circle = s.circle(10, 20, 30); var rect = s.rect(10, 20, 30, 40); var result = circle.before(rect); expect(circle.node.previousSibling).to.be(rect.node); expect(result).to.be(circle); }); it("Element.insertBefore", function() { var circle = s.circle(10, 20, 30); var rect = s.rect(10, 20, 30, 40); var result = rect.insertBefore(circle); expect(circle.node.previousSibling).to.be(rect.node); expect(result).to.be(rect); }); it("Element.clone", function() { var circle = s.circle(10, 20, 30); s.append(circle); var clone = circle.clone(); expect(circle.node).not.to.be(clone.node); expect(clone.node.getAttribute("cx")).to.be("10"); expect(clone.node.getAttribute("cy")).to.be("20"); expect(clone.node.getAttribute("r")).to.be("30"); }); it("Element.parent", function() { var circle = s.circle(10, 20, 30); s.append(circle); var parent = circle.parent(); expect(parent.node).to.be(s.node); }); it("Element.remove", function() { var rect = s.rect(10, 20, 30, 40); expect(rect.node.parentNode).to.be(s.node); var result = rect.remove(); expect(rect.node.parentNode).to.be(null); expect(result).to.be(rect); }); /* Set/get data: Element.attr() Element.data() Element.removeData() Element.asPX() Element.getBBox() Element.getPointAtLength() Element.getSubpath() Element.getTotalLength() Element.innerSVG() Element.toString() */ it("Element.attr - get", function() { var circle = s.circle(10, 20, 30); var r = circle.attr("r"); expect(r).to.be("30"); }); it("Element.attr - set", function() { var circle = s.circle(10, 20, 30); circle.attr({ cx: 1, cy: 2, r: 3 }); var cx = circle.node.getAttribute("cx"); var cy = circle.node.getAttribute("cy"); var r = circle.node.getAttribute("r"); expect(cx).to.be("1"); expect(cy).to.be("2"); expect(r).to.be("3"); }); it("Element.attr - set on group", function() { var group = s.group(); group.attr({'class': 'myclass'}); expect(group.node.getAttribute('class')).to.be('myclass'); }); it("Element.attr - textPath", function() { var txt = s.text(20, 20, "test"); txt.attr({textpath: "M10,20L100,100"}); expect(txt.node.firstChild.tagName).to.be("textPath"); }); it("Element.attr - textPath", function() { var txt = s.text(20, 20, "test"), pth = s.path("M10,20L100,100"); txt.attr({textpath: pth}); expect(txt.node.firstChild.tagName).to.be("textPath"); }); it("Element.data", function() { var circle = s.circle(10, 20, 30); circle.data("foo", "bar"); var data = circle.data("foo"); expect(data).to.be("bar"); var myObject = {}; circle.data("my-object", myObject); data = circle.data("my-object"); expect(data).to.be(myObject); }); it("Element.removeData - with key", function() { var circle = s.circle(10, 20, 30); var myObject = {}; circle.data("my-object", myObject); var data = circle.data("my-object"); expect(data).to.be(myObject); circle.removeData("my-object"); data = circle.data("my-object"); expect(data).to.be(undefined); }); it("Element.removeData - no key", function() { var circle = s.circle(10, 20, 30); var myObject = {}; var myNumber = 42; circle.data("my-object", myObject); circle.data("my-number", 42); expect(circle.data("my-object")).to.be(myObject); expect(circle.data("my-number")).to.be(myNumber); circle.removeData(); expect(circle.data("my-object")).to.be(undefined); expect(circle.data("my-number")).to.be(undefined); }); it("Element.asPX - from %", function() { s.attr({width: 200}); var rect = s.rect(0, 0, "100%", 10); var widthAsPx = rect.asPX("width"); expect(widthAsPx).to.be(200); }); it("Element.getBBox", function() { var rect = s.rect(10, 20, 30, 40), bbox = rect.getBBox(), line = s.line(10, 20, 40, 60), lbbx = line.getBBox(); expect(bbox.x).to.eql(10); expect(bbox.y).to.eql(20); expect(bbox.w).to.eql(30); expect(bbox.width).to.eql(30); expect(bbox.h).to.eql(40); expect(bbox.height).to.eql(40); expect(bbox.x2).to.eql(10 + 30); expect(bbox.cx).to.eql(10 + 30 / 2); expect(bbox.cy).to.eql(20 + 40 / 2); expect(bbox.y2).to.eql(20 + 40); expect(bbox).to.eql(lbbx); }); it("Element.getPointAtLength", function() { var path = s.path("M0,0 100,0"); expect(path.getPointAtLength(50)).to.eql({ x: 50, y: 0, m: { x: 25, y: 0 }, n: { x: 75, y: 0 }, start: { x: 0, y: 0 }, end: { x: 100, y: 0 }, alpha: 180 }); }); it("Element.getSubpath", function() { var path = s.path("M0,0 100,0"); expect(path.getSubpath(10, 90)).to.be("M9.995,0C29.153,0,70.839,0,90,0"); }); it("Element.getTotalLength", function() { var path = s.path("M0,0 100,0"); expect(+path.getTotalLength("M0,0 100,0").toFixed(2)).to.be(100); }); it("Element.innerSVG", function() { var group1 = s.group().attr({ 'class': 'group-one' }); var group2 = s.group().attr({ 'class': 'group-two' }); var group3 = s.group().attr({ 'class': 'group-three' }); var circle1 = s.circle(10, 20, 30).attr({ 'class': 'circle-one' }); var circle2 = s.circle(5, 10, 25).attr({ 'class': 'circle-two' }); group1.add(group2); group2.add(group3); group2.add(circle1); group3.add(circle2); var innerSVG = group1.innerSVG(); expect(innerSVG).to.match(/\w*\w*\w*/); }); it("Element.toString", function() { var group1 = s.group(); var circle1 = s.circle(10, 20, 30).attr({ 'class': 'circle-one' }); group1.add(circle1); var str = group1.toString(); expect(str).to.match(/\w*\w*<\/g>/); }); /* Misc: Element.select() Element.selectAll() Element.animate() Element.inAnim() Element.stop() Element.marker() Element.pattern() Element.use() Element.transform() Element.toDefs() */ it("Element.select", function() { var group1 = s.group(); var group2 = s.group(); var group3 = s.group(); var circle1 = s.circle(10, 20, 30).attr({ 'class': 'circle-one' }); var circle2 = s.circle(5, 10, 25).attr({ 'class': 'circle-two' }); group1.add(group2); group2.add(group3); group2.add(circle1); group3.add(circle2); var c1 = group1.select('.circle-one'); expect(circle1).to.be(c1); var c2 = group1.select('.circle-two'); expect(circle2).to.be(c2); }); it("Element.selectAll", function() { var group1 = s.group(); var group2 = s.group(); var group3 = s.group(); var circle1 = s.circle(10, 20, 30).attr({ 'class': 'circle-one' }); var circle2 = s.circle(5, 10, 25).attr({ 'class': 'circle-two' }); group1.add(group2); group2.add(group3); group2.add(circle1); group3.add(circle2); var circles = group1.selectAll('circle'); expect(circles.length).to.be(2); expect(circles).to.contain(circle1); expect(circles).to.contain(circle2); }); it("Element.animate", function(done) { var circle = s.circle(10, 20, 30); var result = circle.animate({r: 50}, 10); setTimeout(function() { var r = circle.attr("r"); expect(r).to.be("50"); done(); }, 50); expect(result).to.be(circle); }); it("Element.animate - with callback", function(done) { var circle = s.circle(10, 20, 30); var result = circle.animate({r: 50}, 10, function() { var r = circle.attr("r"); expect(r).to.be("50"); done(); }); expect(result).to.be(circle); }); it("Element.animate - with easing", function(done) { var circle = s.circle(10, 20, 30); var result = circle.animate({r: 50}, 10, mina.easein); setTimeout(function() { var r = circle.attr("r"); expect(r).to.be("50"); done(); }, 50); expect(result).to.be(circle); }); it("Element.animate - with callback & easing", function(done) { var circle = s.circle(10, 20, 30); var result = circle.animate({r: 50}, 10, mina.easeout, function() { var r = circle.attr("r"); expect(r).to.be("50"); done(); }); expect(result).to.be(circle); }); it("Element.inAnim", function(done) { var circle = s.circle(10, 20, 30); circle.animate({r: 50}, 100); circle.animate({cx: 100}, 100); setTimeout(function() { var inAnimArr = circle.inAnim(); expect(inAnimArr).to.be.an('array'); expect(inAnimArr.length).to.be(2); expect(inAnimArr[0].anim).to.be.an('object'); expect(inAnimArr[0].curStatus).to.be.a('number'); expect(inAnimArr[0].curStatus).to.be.within(0.01, 0.99); expect(inAnimArr[0].status).to.be.a('function'); expect(inAnimArr[0].stop).to.be.a('function'); done(); }, 50); }); it("Element.stop", function(done) { var circle = s.circle(10, 20, 30); circle.animate({r: 50}, 100); setTimeout(function() { var inAnimArr = circle.inAnim(); expect(inAnimArr.length).to.be(1); var result = circle.stop(); inAnimArr = circle.inAnim(); expect(inAnimArr.length).to.be(0); var r = circle.attr("r"); expect(r).to.be.lessThan(50); expect(result).to.be(circle); done(); }, 50); }); it("Element.marker", function() { var line = s.line(0, 0, 10, 10); var marker = line.marker(0, 0, 5, 5, 0, 0); expect(marker.node.nodeName).to.be("marker"); expect(marker.node.getAttribute("viewBox")).to.be("0 0 5 5"); expect(marker.node.getAttribute("markerWidth")).to.be("5"); expect(marker.node.getAttribute("markerHeight")).to.be("5"); expect(marker.node.getAttribute("refX")).to.be("0"); expect(marker.node.getAttribute("refY")).to.be("0"); }); it("Element.pattern", function() { var circle = s.circle(10, 20, 30); var pattern = circle.pattern(0, 0, 50, 50); expect(pattern.node.nodeName).to.be('pattern'); }); it("Element.transform", function() { var circle = s.circle(10, 20, 30); var result = circle.transform("translate(10,10)"); var matrix = { a: 1, b: 0, c: 0, d: 1, e: 10, f: 10 }; var transform = circle.transform(); expect(transform.string).to.be.a('string'); expect(transform.global).to.be.a('string'); expect(transform.globalMatrix).to.be.an('object'); expect(transform.globalMatrix).to.eql(matrix); expect(transform.local).to.be.a('string'); expect(transform.localMatrix).to.be.an('object'); expect(transform.localMatrix).to.eql(matrix); circle.transform("rotate(90)"); transform = circle.transform(); expect(transform.local).to.be("r90,0,0"); expect(result).to.be(circle); circle.transform("translate(10)"); matrix = { a: 1, b: 0, c: 0, d: 1, e: 10, f: 0 }; transform = circle.transform(); expect(transform.globalMatrix).to.eql(matrix); }); it("Element.use", function() { var circle = s.circle(10, 20, 30); var use = circle.use(); expect(use.node.nodeName).to.be('use'); }); it("Element.toDefs", function() { var circle = s.circle(10, 20, 30); var result = circle.toDefs(); expect(circle.node.parentElement.nodeName).to.be('defs'); expect(result).to.be(circle); }); /* Event binding & unbinding: Element.click() Element.dblclick() Element.mousedown() Element.mousemove() Element.mouseout() Element.mouseover() Element.mouseup() Element.touchcancel() Element.touchend() Element.touchmove() Element.touchstart() Element.unclick() Element.undblclick() Element.unmousedown() Element.unmousemove() Element.unmouseout() Element.unmouseover() Element.unmouseup() Element.untouchcancel() Element.untouchend() Element.untouchmove() Element.untouchstart() Element.drag() Element.undrag() Element.onDragOver() Element.hover() Element.unhover() */ // Helper function to simulate event triggering var triggerEvent = function(savageEl, eventType) { var event = document.createEvent("HTMLEvents"); event.initEvent(eventType, true, true); savageEl.node.dispatchEvent(event); }; // Generate tests for all standard DOM events (function() { var elementEvents = [ "click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "touchcancel", "touchend", "touchmove", "touchstart" ]; var makeEventTest = function(eventName) { return function() { // Add event, trigger event, remove event, trigger again var circle = s.circle(10, 20, 30); var n = 0; var fn = function() { n++; }; var result1 = circle[eventName](fn); expect(n).to.be(0); triggerEvent(circle, eventName); expect(n).to.be(1); var result2 = circle["un" + eventName](fn); triggerEvent(circle, eventName); expect(n).to.be(1); expect(result1).to.be(circle); expect(result2).to.be(circle); }; }; for (var i = 0; i < elementEvents.length; i++) { var eventName = elementEvents[i]; var testName = "Element." + eventName + ", Element.un" + eventName; var testFunc = makeEventTest(eventName); it(testName, testFunc); } }()); it("Element.drag, Element.undrag - no contexts", function() { var circle = s.circle(10, 20, 30); var moved = 0; var started = 0; var ended = 0; var result1 = circle.drag(function(dx, dy, x, y, event) { moved++; expect(dx).to.be.a('number'); expect(dy).to.be.a('number'); expect(x).to.be.a('number'); expect(y).to.be.a('number'); expect(event).to.be.an('object'); }, function(x, y, event) { started++; expect(x).to.be.a('number'); expect(y).to.be.a('number'); expect(event).to.be.an('object'); }, function(event) { ended++; expect(event).to.be.an('object'); }); expect(started).to.be(0); triggerEvent(circle, 'mousedown'); expect(started).to.be(1); expect(moved).to.be(0); triggerEvent(circle, 'mousemove'); expect(moved).to.be(1); expect(ended).to.be(0); triggerEvent(circle, 'mouseup'); expect(ended).to.be(1); expect(result1).to.be(circle); var result2 = circle.undrag(); triggerEvent(circle, 'mousedown'); expect(started).to.be(1); triggerEvent(circle, 'mousemove'); expect(moved).to.be(1); triggerEvent(circle, 'mouseup'); expect(ended).to.be(1); // expect(result2).to.be(circle); // TODO: Make undrag return element }); it("Element.drag - with contexts", function() { var circle = s.circle(10, 20, 30); var moved = 0; var started = 0; var ended = 0; var result = circle.drag(function(dx, dy, x, y, event) { moved++; expect(dx).to.be.a('number'); expect(dy).to.be.a('number'); expect(x).to.be.a('number'); expect(y).to.be.a('number'); expect(event).to.be.an('object'); expect(this.moveContext).to.be(true); }, function(x, y, event) { started++; expect(x).to.be.a('number'); expect(y).to.be.a('number'); expect(event).to.be.an('object'); expect(this.startContext).to.be(true); }, function(event) { ended++; expect(event).to.be.an('object'); expect(this.endContext).to.be(true); }, {moveContext: true}, {startContext: true}, {endContext: true}); expect(started).to.be(0); triggerEvent(circle, 'mousedown'); expect(started).to.be(1); expect(moved).to.be(0); triggerEvent(circle, 'mousemove'); expect(moved).to.be(1); expect(ended).to.be(0); triggerEvent(circle, 'mouseup'); expect(ended).to.be(1); expect(result).to.be(circle); var result2 = circle.undrag(); triggerEvent(circle, 'mousedown'); expect(started).to.be(1); triggerEvent(circle, 'mousemove'); expect(moved).to.be(1); triggerEvent(circle, 'mouseup'); expect(ended).to.be(1); // expect(result2).to.be(circle); // TODO: Make undrag return element }); it("Element.hover, Element.unhover - no contexts", function() { var circle = s.circle(10, 20, 30); var eventIn = 0; var eventOut = 0; var result1 = circle.hover(function() { eventIn++; }, function() { eventOut++; }); expect(eventIn).to.be(0); triggerEvent(circle, 'mouseover'); expect(eventIn).to.be(1); expect(eventOut).to.be(0); triggerEvent(circle, 'mouseout'); expect(eventOut).to.be(1); expect(result1).to.be(circle); var result2 = circle.unhover(); triggerEvent(circle, 'mouseover'); expect(eventIn).to.be(1); triggerEvent(circle, 'mouseout'); expect(eventOut).to.be(1); expect(result2).to.be(circle); }); it("Element.hover, Element.unhover - with contexts", function() { var circle = s.circle(10, 20, 30); var eventIn = 0; var eventOut = 0; var result1 = circle.hover(function() { eventIn++; expect(this.inContext).to.be(true); }, function() { eventOut++; expect(this.outContext).to.be(true); }, {inContext: true}, {outContext: true}); expect(eventIn).to.be(0); triggerEvent(circle, 'mouseover'); expect(eventIn).to.be(1); expect(eventOut).to.be(0); triggerEvent(circle, 'mouseout'); expect(eventOut).to.be(1); expect(result1).to.be(circle); var result2 = circle.unhover(); triggerEvent(circle, 'mouseover'); expect(eventIn).to.be(1); triggerEvent(circle, 'mouseout'); expect(eventOut).to.be(1); expect(result2).to.be(circle); }); it("Snap.getElementByPoint", function() { var rect = s.rect(10, 10, 30, 30).attr({id: "id"}); var res1 = Snap.getElementByPoint(15, 15); var res2 = Snap.getElementByPoint(45, 45); expect(res1.node.id).to.be("id"); expect(res2.node.id).to.not.be("id"); }); }); ================================================ FILE: test/filter.js ================================================ describe("Filter methods", function () { it("Snap.filter.blur", function() { var str = Snap.filter.blur(3); expect(str).to.be(''); str = Snap.filter.blur(0.123, 8); expect(str).to.be(''); }); it("Snap.filter.brightness", function() { var str = Snap.filter.brightness(0.3); expect(str).to.be(''); str = Snap.filter.brightness(1); expect(str).to.be(''); }); it("Snap.filter.contrast", function() { var str = Snap.filter.contrast(0.1); expect(str).to.be(''); str = Snap.filter.contrast(3); expect(str).to.be(''); }); it("Snap.filter.grayscale", function() { var str = Snap.filter.grayscale(0.5); expect(str).to.be(''); str = Snap.filter.grayscale(1); expect(str).to.be(''); }); it("Snap.filter.hueRotate", function() { var str = Snap.filter.hueRotate(180); expect(str).to.be(''); str = Snap.filter.hueRotate(90); expect(str).to.be(''); }); it("Snap.filter.hueRotate", function() { var str = Snap.filter.hueRotate(180); expect(str).to.be(''); str = Snap.filter.hueRotate(90); expect(str).to.be(''); }); it("Snap.filter.invert", function() { var str = Snap.filter.invert(0.6); expect(str).to.be(''); str = Snap.filter.invert(1); expect(str).to.be(''); }); it("Snap.filter.saturate", function() { var str = Snap.filter.saturate(0.3); expect(str).to.be(''); str = Snap.filter.saturate(1); expect(str).to.be(''); }); it("Snap.filter.sepia", function() { var str = Snap.filter.sepia(0.3); expect(str).to.be(''); str = Snap.filter.sepia(1); expect(str).to.be(''); }); it("Snap.filter.shadow - dx & dy", function() { var str = Snap.filter.shadow(5, 5); expect(str).to.be(''); str = Snap.filter.shadow(-1, 3); expect(str).to.be(''); }); it("Snap.filter.shadow - dx & dy, opacity", function() { var str = Snap.filter.shadow(5, 5, 5); expect(str).to.be(''); str = Snap.filter.shadow(-1, 3, 10); expect(str).to.be(''); }); it("Snap.filter.shadow - dx & dy, color, opacity", function() { var str = Snap.filter.shadow(5, 5, '#F00', 1); expect(str).to.be(''); str = Snap.filter.shadow(-1, 3, 'hsla(128deg, 50%, 50%, 0.8)', .5); expect(str).to.be(''); }); it("Snap.filter.shadow - dx & dy, blur, color, opacity", function() { var str = Snap.filter.shadow(5, 5, 5, '#F00', 1); expect(str).to.be(''); str = Snap.filter.shadow(-1, 3, 10, 'hsla(128deg, 50%, 50%, 0.8)', .5); expect(str).to.be(''); }); }); ================================================ FILE: test/gradients.js ================================================ describe("Gradients", function () { var s, r; beforeEach(function () { s = Snap(100, 100); r = s.rect(0, 0, 100, 100); }); afterEach(function () { s.remove(); }); function getGrad(el) { if (!el) { el = r; } var id = el.node.getAttribute("fill"); id = Snap.deurl(id); return s.select(id); } it("creates simple gradient", function () { r.attr({fill: "l(0,0,1,0)#fff-#000"}); var g = getGrad(); expect(g).to.not.be(null); expect(g.stops().length).to.be(2); expect(g.stops()[0].attr("stop-color")).to.be("rgb(255, 255, 255)"); }); it("creates radial gradient", function () { r.attr({fill: "r()#fff-#000"}); var g = getGrad(); expect(g).to.not.be(null); expect(g.stops().length).to.be(2); expect(g.stops()[0].attr("stop-color")).to.be("rgb(255, 255, 255)"); }); it("returns gradient for .attr(\"fill\") call", function () { r.attr({fill: "l(0,0,1,0)#fff-#fc0:20-#000"}); var g = getGrad(), g2 = r.attr("fill"); expect(g).to.be(g2); }); it("creates complex gradient", function () { r.attr({fill: "l(0,0,1,0)#fff-#fc0:20-#000"}); var g = getGrad(); expect(g).to.not.be(null); expect(g.stops().length).to.be(3); expect(g.stops()[0].attr("stop-color")).to.be("rgb(255, 255, 255)"); expect(g.stops()[1].attr("offset")).to.be("20%"); }); it("updates simple gradient", function () { r.attr({fill: "l(0,0,1,0)#fff-#000"}); var g = getGrad(); expect(g).to.not.be(null); expect(g.stops().length).to.be(2); expect(g.stops()[0].attr("stop-color")).to.be("rgb(255, 255, 255)"); g.setStops("#000-#fff"); expect(g.stops().length).to.be(2); expect(g.stops()[0].attr("stop-color")).to.be("rgb(0, 0, 0)"); g.setStops("#000-red-#fff"); expect(g.stops().length).to.be(3); expect(g.stops()[1].attr("stop-color")).to.be("rgb(255, 0, 0)"); }); it("adds stops to the gradient", function () { r.attr({fill: "l(0,0,1,0)#fff-#000"}); var g = getGrad(); expect(g).to.not.be(null); expect(g.stops().length).to.be(2); expect(g.stops()[0].attr("stop-color")).to.be("rgb(255, 255, 255)"); g.addStop("red", 20); expect(g.stops().length).to.be(3); expect(g.stops()[1].attr("stop-color")).to.be("rgb(255, 0, 0)"); expect(g.stops()[1].attr("offset")).to.be("20%"); }); }); ================================================ FILE: test/matrix.js ================================================ describe("Matrix methods", function () { it("Matrix.add - matrix", function() { var matrix1 = new Snap.Matrix(1, 0, 0, 1, 5, 5); var matrix2 = new Snap.Matrix(1, 0, 0, 1, 10, 10); var result = matrix1.add(matrix2); expect(result).to.eql({ a: 1, b: 0, c: 0, d: 1, e: 15, f: 15 }); // add two 90 degree rotations var matrix3 = new Snap.Matrix(0, 1, -1, 0, 0, 0); var matrix4 = new Snap.Matrix(0, 1, -1, 0, 0, 0); result = matrix3.add(matrix4); expect(result).to.eql({ a: -1, b: 0, c: 0, d: -1, e: 0, f: 0 }); }); it("Matrix.add - numbers", function() { var matrix1 = new Snap.Matrix(1, 0, 0, 1, 5, 5); var result = matrix1.add(1, 0, 0, 1, 10, 10); expect(result).to.eql({ a: 1, b: 0, c: 0, d: 1, e: 15, f: 15 }); }); it("Matrix.clone", function() { var matrix1 = new Snap.Matrix(1, 2, 3, 4, 5, 6); var clone = matrix1.clone(); expect(clone).to.not.be(matrix1); expect(clone).to.eql({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }); }); it("Matrix.invert", function() { var matrix1 = new Snap.Matrix(1, 2, 3, 4, 5, 6); var inverse = matrix1.invert(); expect(inverse).to.eql({ a: -2, b: 1, c: 1.5, d: -0.5, e: 1, f: -2 }); }); it("Matrix.rotate", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 0, 0); matrix.rotate(45, 0, 0); expect(+matrix.a.toFixed(3)).to.be(0.707); expect(+matrix.b.toFixed(3)).to.be(0.707); expect(+matrix.c.toFixed(3)).to.be(-0.707); expect(+matrix.d.toFixed(3)).to.be(0.707); expect(+matrix.e.toFixed(3)).to.be(0); expect(+matrix.f.toFixed(3)).to.be(0); }); it("Matrix.scale - x", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); matrix.scale(2); expect(matrix).to.eql({ a: 2, b: 0, c: 0, d: 2, e: 20, f: 30 }); matrix.scale(0.5); expect(matrix).to.eql({ a: 1, b: 0, c: 0, d: 1, e: 20, f: 30 }); }); it("Matrix.scale - x, y", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); matrix.scale(2, 3); expect(matrix).to.eql({ a: 2, b: 0, c: 0, d: 3, e: 20, f: 30 }); matrix.scale(0.5, 1); expect(matrix).to.eql({ a: 1, b: 0, c: 0, d: 3, e: 20, f: 30 }); }); it("Matrix.scale - x, y, cx, cy", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); matrix.scale(2, 3, 5, -5); expect(matrix).to.eql({ a: 2, b: 0, c: 0, d: 3, e: 15, f: 40 }); }); it("Matrix.split", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 0, 0); var result = matrix.split(); expect(result.dx).to.be(0); expect(result.dy).to.be(0); expect(result.scalex).to.be(1); expect(result.scaley).to.be(1); expect(result.shear).to.be(0); expect(result.rotate).to.be(0); expect(result.isSimple).to.be(true); matrix = new Snap.Matrix(1.5, 0, 0, 0.5, 20, 25); result = matrix.split(); expect(result.dx).to.be(20); expect(result.dy).to.be(25); expect(result.scalex).to.be(1.5); expect(result.scaley).to.be(0.5); expect(result.shear).to.be(0); expect(result.rotate).to.be(0); expect(result.isSimple).to.be(true); }); it("Matrix.toTransformString", function() { var matrix = new Snap.Matrix(1.5, 0, 0, 0.5, 20, 25); var str = matrix.toTransformString(); var s = Snap(10, 10); var rect = s.rect(0, 0, 10, 20); rect.transform(str); var transform = rect.transform(); expect(transform.localMatrix).to.eql({ a: 1.5, b: 0, c: 0, d: 0.5, e: 20, f: 25 }); }); it("Matrix.translate", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); matrix.translate(10, -10); expect(matrix).to.eql({ a: 1, b: 0, c: 0, d: 1, e: 30, f: 20 }); matrix.translate(-1, -2); expect(matrix).to.eql({ a: 1, b: 0, c: 0, d: 1, e: 29, f: 18 }); }); it("Matrix.skewX", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); matrix.skewX(45); expect(matrix).to.eql({ a: 1, b: 0, c: 1, d: 1, e: 20, f: 30 }); }); it("Matrix.skewY", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); matrix.skewY(45); expect(matrix).to.eql({ a: 1, b: 1, c: 0, d: 1, e: 20, f: 30 }); }); it("Matrix.skew", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); matrix.skew(45, -45); expect(matrix).to.eql({ a: 1, b: -1, c: 1, d: 1, e: 20, f: 30 }); }); it("Matrix.x", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); var result = matrix.x(10, -10); expect(result).to.be(30); }); it("Matrix.y", function() { var matrix = new Snap.Matrix(1, 0, 0, 1, 20, 30); var result = matrix.y(10, -10); expect(result).to.be(20); }); }); ================================================ FILE: test/mina.js ================================================ describe("Mina methods", function () { var s; beforeEach(function () { s = Snap(100, 100); }); afterEach(function () { s.remove(); }); var validateDescriptor = function(obj) { expect(obj).to.be.an('object'); expect(obj.id).to.be.a('string'); expect(obj.start).to.be.a('number'); expect(obj.end).to.be.a('number'); expect(obj.b).to.be.a('number'); expect(obj.s).to.be.a('number'); expect(obj.dur).to.be.a('number'); expect(obj.spd).to.be.a('number'); expect(obj.get).to.be.a('function'); expect(obj.set).to.be.a('function'); expect(obj.easing).to.be.a('function'); expect(obj.status).to.be.a('function'); expect(obj.speed).to.be.a('function'); expect(obj.duration).to.be.a('function'); expect(obj.stop).to.be.a('function'); }; it("mina", function() { var n; var animDescriptor = mina(10, 20, 0, 1000, function(newN) { n = newN; }, function() {}); validateDescriptor(animDescriptor); expect(animDescriptor.start).to.be(10); expect(animDescriptor.end).to.be(20); expect(animDescriptor.b).to.be(0); expect(animDescriptor.s).to.be(0); expect(animDescriptor.dur).to.be(1000); expect(animDescriptor.easing).to.be(mina.linear); }); it("mina.getById", function() { var anim1 = mina(10, 20, 0, 1000, function() {}, function() {}); var anim2 = mina(10, 20, 0, 1000, function() {}, function() {}); expect(mina.getById(anim1.id)).to.be(anim1); expect(mina.getById(anim2.id)).to.be(anim2); }); it("mina.time", function() { var now = (new Date).getTime(); expect(mina.time()).to.be(now); }); it("mina.backin", function() { expect(mina.backin(0)).to.be(0); expect(mina.backin(1)).to.be(1); }); it("mina.backout", function() { expect(mina.backout(0)).to.be(0); expect(mina.backout(1)).to.be(1); }); it("mina.bounce", function() { expect(mina.bounce(0)).to.be(0); expect(mina.bounce(1)).to.be(1); }); it("mina.easein", function() { expect(mina.easein(0)).to.be(0); expect(mina.easein(1)).to.be(1); }); it("mina.easeinout", function() { expect(mina.easeinout(0)).to.be(0); expect(mina.easeinout(1)).to.be(1); }); it("mina.easeout", function() { expect(mina.easeout(0)).to.be(0); expect(mina.easeout(1)).to.be(1); }); it("mina.elastic", function() { expect(mina.elastic(0)).to.be(0); expect(mina.elastic(1)).to.be(1); }); it("mina.linear", function() { expect(mina.linear(0)).to.be(0); expect(mina.linear(0.2)).to.be(0.2); expect(mina.linear(0.7)).to.be(0.7); expect(mina.linear(1)).to.be(1); }); }); ================================================ FILE: test/paper.js ================================================ describe("Check for Paper Creation", function () { it("creates simple paper 20 × 10", function () { var s = Snap(20, 10); var S = s.node; expect(S).to.not.be(null); expect(S.getAttribute("width")).to.be("20"); expect(S.getAttribute("height")).to.be("10"); s.remove(); }); it("removal of paper", function () { var s = Snap(20, 10); var S = s.node; expect(S).to.not.be(null); s.remove(); S = document.querySelectorAll("svg").length; expect(S).to.be(1); }); it("creates simple paper 20% × 10em", function () { var s = Snap("20%", "10em"); var S = s.node; expect(S).to.not.be(null); expect(S.getAttribute("width")).to.be("20%"); expect(S.getAttribute("height")).to.be("10em"); s.remove(); }); it("converts existing SVG element to paper", function () { var S = document.getElementById("svgroot"); var s = Snap(S); expect(document.querySelector("#svgroot circle")).to.be(null); var c = s.circle(10, 20, 5); expect(document.querySelectorAll("#svgroot circle").length).to.be(1); c.remove(); }); it("converts existing SVG element to paper (as query)", function () { var S = document.getElementById("svgroot"); var s = Snap("#svgroot"); expect(document.querySelector("#svgroot circle")).to.be(null); var c = s.circle(10, 20, 5); expect(document.querySelectorAll("#svgroot circle").length).to.be(1); c.remove(); }); }); describe("Paper methods", function () { /* Paper.el Paper.filter Paper.gradient Paper.image Paper.toString */ var paper; beforeEach(function () { paper = Snap(100, 100); }); afterEach(function () { paper.remove(); }); it("Paper.svg", function() { var c = paper.svg(); expect(c.node.nodeName).to.be("svg"); expect(c.node.parentNode).to.be(paper.node); }); it("Paper.svg(x, y)", function() { var c = paper.svg(100, 200); expect(c.node.nodeName).to.be("svg"); expect(c.node.x.baseVal.value).to.be(100); expect(c.node.y.baseVal.value).to.be(200); expect(c.node.parentNode).to.be(paper.node); }); it("Paper.svg(x, y, w, h, viewbox)", function() { var c = paper.svg(100, 200, 300, 400, 10, 20, 30, 40); expect(c.node.nodeName).to.be("svg"); expect(c.node.x.baseVal.value).to.be(100); expect(c.node.y.baseVal.value).to.be(200); expect(c.node.width.baseVal.value).to.be(300); expect(c.node.height.baseVal.value).to.be(400); expect(c.node.getAttribute("viewBox")).to.be("10 20 30 40"); expect(c.node.parentNode).to.be(paper.node); }); it("Paper.el", function() { var c = paper.el("circle"); expect(c.node.nodeName).to.be("circle"); expect(c.node.parentNode).to.be(paper.node); }); it("Paper.filter", function() { var filter = paper.filter(''); expect(filter.node.nodeName).to.be('filter'); var child = filter.node.firstChild; expect(child).to.be.ok(); expect(child.nodeName).to.be('feGaussianBlur'); expect(child.getAttribute("stdDeviation")).to.be('2'); }); it("Paper.gradient - linear", function() { var gradient = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); expect(gradient.node.nodeName).to.be('linearGradient'); expect(gradient.node.getAttribute('x1')).to.be("0"); expect(gradient.node.getAttribute('y1')).to.be("0"); expect(gradient.node.getAttribute('x2')).to.be("1"); expect(gradient.node.getAttribute('y2')).to.be("1"); var stops = gradient.node.querySelectorAll("stop"); expect(stops.length).to.be(3); }); it("Paper.gradient - radial", function() { var gradient = paper.gradient("r(0.3, 0.4, 0.5)#000-#fff"); expect(gradient.node.nodeName).to.be('radialGradient'); expect(gradient.node.getAttribute('cx')).to.be("0.3"); expect(gradient.node.getAttribute('cy')).to.be("0.4"); expect(gradient.node.getAttribute('r')).to.be("0.5"); var stops = gradient.node.querySelectorAll("stop"); expect(stops.length).to.be(2); }); it("Paper.image", function() { var image = paper.image('#', 10, 20, 30, 40); var img = document.querySelector("image"); expect(img).to.not.be(null); expect(img.getAttribute("x")).to.be("10"); expect(img.getAttribute("y")).to.be("20"); expect(img.getAttribute("width")).to.be("30"); expect(img.getAttribute("height")).to.be("40"); }); it("Paper.toString", function() { paper.circle(10, 20, 30); var str = paper.toString(); expect(str).to.match(/.*?.*?/); }); it("Paper.getBBox", function() { paper.circle(50, 50, 30); var bb = paper.getBBox(); expect(bb.x).to.be(20); expect(bb.y).to.be(20); expect(bb.width).to.be(60); expect(bb.height).to.be(60); }); }); ================================================ FILE: test/path.js ================================================ describe("Path methods", function () { it("Snap.path.getTotalLength", function () { expect(+Snap.path.getTotalLength("M0,0 100,0").toFixed(2)).to.be(100); }); it("Snap.path.getPointAtLength", function () { expect(Snap.path.getPointAtLength("M0,0 100,0", 50)).to.eql({ x: 50, y: 0, m: { x: 25, y: 0 }, n: { x: 75, y: 0 }, start: { x: 0, y: 0 }, end: { x: 100, y: 0 }, alpha: 180 }); }); it("Snap.path.getSubpath", function () { expect(Snap.path.getSubpath("M0,0 100,0", 10, 90)).to.be("M9.995,0C29.153,0,70.839,0,90,0"); expect(Snap.path.getSubpath("M0,0 100,0", 0, 90)).to.be("M0,0C0,0,64.674,0,90,0"); expect(Snap.path.getSubpath("M0,0 100,0", 10, 120)).to.be("M10,0C35.326,0,100,0,100,0"); }); it("Snap.path.findDotsAtSegment", function () { expect(Snap.path.findDotsAtSegment(0,0,0,0,100,0,100,0,.5)).to.eql({ x: 50, y: 0, m: { x: 25, y: 0 }, n: { x: 75, y: 0 }, start: { x: 0, y: 0 }, end: { x: 100, y: 0 }, alpha: 180 }); }); it("Snap.path.bezierBBox - params", function () { var bbox = Snap.path.bezierBBox(10, 10, 10, 20, 110, 0, 110, 10); expect(bbox.cx).to.be(60); expect(bbox.cy).to.be(10); expect(bbox.x).to.be(10); expect(bbox.w).to.be(100); expect(bbox.width).to.be(100); expect(bbox.x2).to.be(110); }); it("Snap.path.bezierBBox - array", function () { var bbox = Snap.path.bezierBBox([10, 10, 10, 20, 110, 0, 110, 10]); expect(bbox.cx).to.be(60); expect(bbox.cy).to.be(10); expect(bbox.x).to.be(10); expect(bbox.w).to.be(100); expect(bbox.width).to.be(100); expect(bbox.x2).to.be(110); }); it("Snap.path.getBBox", function() { // same as 10,20,30,40 rect var bbox = Snap.path.getBBox("M10,20h30v40h-30z"); expect(bbox.x).to.eql(10); expect(bbox.y).to.eql(20); expect(bbox.width).to.eql(30); expect(bbox.height).to.eql(40); expect(bbox.x2).to.eql(10 + 30); expect(bbox.y2).to.eql(20 + 40); }); it("Snap.path.isPointInsideBBox", function () { expect(Snap.path.isPointInsideBBox({x: 0, y: 0, width: 10, height: 10}, 5, 5)).to.be(true); expect(Snap.path.isPointInsideBBox({x: 0, y: 0, width: 10, height: 10}, 10, 5)).to.be(true); expect(Snap.path.isPointInsideBBox({x: 0, y: 0, width: 10, height: 10}, 10, 10)).to.be(true); }); it("Snap.path.isBBoxIntersect", function () { expect(Snap.path.isBBoxIntersect({ x: 0, y: 0, width: 10, height: 10 }, { x: 5, y: 5, width: 15, height: 15 })).to.be(true); expect(Snap.path.isBBoxIntersect({ x: 0, y: 0, width: 10, height: 10 }, { x: 5, y: 5, width: 7, height: 7 })).to.be(true); expect(Snap.path.isBBoxIntersect({ x: 0, y: 0, width: 10, height: 10 }, { x: 15, y: 15, width: 10, height: 10 })).to.be(false); }); it("Snap.path.toAbsolute", function() { var relPath = "M 10 10" + "h 40" + "v 30" + "h -40" + "l 0 -30" + "m 0 40" + "l 30 0" + "l 0 40" + "l -30 0" + "m 0 10" + "c 20 20 40 20 40 0" + "m -40 40" + "c 10 -25 20 -25 30 0" + "s 10 25 20 0" + "m 20 -130" + "q 30 30 60 0" + "m -60 40" + "q 10 20 20 0" + "t 20 0" + "m -40 30" + "a 10 10 0 0 0 40 0"; var absPath = Snap.path.toAbsolute(relPath); var i = 0; var checkNext = function(arr) { expect(absPath[i++]).to.eql(arr); } checkNext(['M', 10, 10]); checkNext(['H', 50]); checkNext(['V', 40]); checkNext(['H', 10]); checkNext(['L', 10, 10]); checkNext(['M', 10, 50]); checkNext(['L', 40, 50]); checkNext(['L', 40, 90]); checkNext(['L', 10, 90]); checkNext(['M', 10, 100]); checkNext(['C', 30, 120, 50, 120, 50, 100]); checkNext(['M', 10, 140]); checkNext(['C', 20, 115, 30, 115, 40, 140]); checkNext(['S', 50, 165, 60, 140]); checkNext(['M', 80, 10]); checkNext(['Q', 110, 40, 140, 10]); checkNext(['M', 80, 50]); checkNext(['Q', 90, 70, 100, 50]); checkNext(['T', 120, 50]); checkNext(['M', 80, 80]); checkNext(['A', 10, 10, 0, 0, 0, 120, 80]); }); it("Snap.path.toRelative", function() { var absPath = "M10 10 H 50 V 40 H 10 L 10 10" + "M10 50 L 40 50 L 40 90 L 10 90" + "M10 100 C 30 120, 50 120, 50 100" + "M10 140 C 20 115, 30, 115, 40 140 S 50 165, 60 140" + "M80 10 Q 110 40, 140 10" + "M80 50 Q 90 70, 100 50 T 120 50" + "M80 80 A 10 10 0 0 0 120 80"; var relPath = Snap.path.toRelative(absPath); var i = 0; var checkNext = function(arr) { expect(relPath[i++]).to.eql(arr); } checkNext(['M', 10, 10]); checkNext(['h', 40]); checkNext(['v', 30]); checkNext(['h', -40]); checkNext(['l', 0, -30]); checkNext(['m', 0, 40]); checkNext(['l', 30, 0]); checkNext(['l', 0, 40]); checkNext(['l', -30, 0]); checkNext(['m', 0, 10]); checkNext(['c', 20, 20, 40, 20, 40, 0]); checkNext(['m', -40, 40]); checkNext(['c', 10, -25, 20, -25, 30, 0]); checkNext(['s', 10, 25, 20, 0]); checkNext(['m', 20, -130]); checkNext(['q', 30, 30, 60, 0]); checkNext(['m', -60, 40]); checkNext(['q', 10, 20, 20, 0]); checkNext(['t', 20, 0]); checkNext(['m', -40, 30]); checkNext(['a', 10, 10, 0, 0, 0, 40, 0]); }); it("Snap.path.toCubic", function() { var absPath = "M10 10 H 50 V 40 H 10 L 10 10" + "M10 50 L 40 50 L 40 90 L 10 90" + "M10 100 C 30 120, 50 120, 50 100" + "M10 140 C 20 115, 30, 115, 40 140 S 50 165, 60 140" + "M80 10 Q 110 40, 140 10" + "M80 50 Q 90 70, 100 50 T 120 50" + "M80 80 A 10 10 0 0 0 120 80"; var relPath = "M 10 10" + "h 40" + "v 30" + "h -40" + "l 0 -30" + "m 0 40" + "l 30 0" + "l 0 40" + "l -30 0" + "m 0 10" + "c 20 20 40 20 40 0" + "m -40 40" + "c 10 -25 20 -25 30 0" + "s 10 25 20 0" + "m 20 -130" + "q 30 30 60 0" + "m -60 40" + "q 10 20 20 0" + "t 20 0" + "m -40 30" + "a 10 10 0 0 0 40 0"; var cubicPathFromAbs = Snap.path.toCubic(absPath); var cubicPathFromRel = Snap.path.toCubic(relPath); var i = 0; var checkNext = function(arr) { expect(cubicPathFromAbs[i]).to.eql(arr); expect(cubicPathFromRel[i]).to.eql(arr); i++; } checkNext(['M', 10, 10]); checkNext(['C', 10, 10, 50, 10, 50, 10]); checkNext(['C', 50, 10, 50, 40, 50, 40]); checkNext(['C', 50, 40, 10, 40, 10, 40]); checkNext(['C', 10, 40, 10, 10, 10, 10]); checkNext(['M', 10, 50]); checkNext(['C', 10, 50, 40, 50, 40, 50]); checkNext(['C', 40, 50, 40, 90, 40, 90]); checkNext(['C', 40, 90, 10, 90, 10, 90]); checkNext(['M', 10, 100]); checkNext(['C', 30, 120, 50, 120, 50, 100]); checkNext(['M', 10, 140]); checkNext(['C', 20, 115, 30, 115, 40, 140]); checkNext(['C', 50, 165, 50, 165, 60, 140]); checkNext(['M', 80, 10]); checkNext(['C', 100, 29.999999999999996, 120, 29.999999999999996, 140, 10]); checkNext(['M', 80, 50]); checkNext(['C', 86.66666666666666, 63.33333333333333, 93.33333333333333, 63.33333333333333, 100, 50]); checkNext(['C', 106.66666666666666, 36.666666666666664, 113.33333333333333, 36.666666666666664, 120, 50]); checkNext(['M', 80, 80]); checkNext(['C', 80, 95.39600717839002, 96.66666666666667, 105.01851166488379, 110, 97.32050807568878]); checkNext(['C', 116.18802153517007, 93.74785217660714, 120, 87.14531179816328, 120, 80]); }); it("Snap.path.map", function() { var absPath = "M10 10 H 50 V 40 H 10 L 10 10" + "M10 50 L 40 50 L 40 90 L 10 90" + "M10 100 C 30 120, 50 120, 50 100" + "M10 140 C 20 115, 30, 115, 40 140 S 50 165, 60 140" + "M80 10 Q 110 40, 140 10" + "M80 50 Q 90 70, 100 50 T 120 50" + "M80 80 A 10 10 0 0 0 120 80"; var matrix = new Snap.Matrix(1, 0, 0, 1, 1000, 0); var transformedPath = Snap.path.map(absPath, matrix); var i = 0; var checkNext = function(arr) { expect(transformedPath[i++]).to.eql(arr); } checkNext(['M', 1010, 10]); checkNext(['C', 1010, 10, 1050, 10, 1050, 10]); checkNext(['C', 1050, 10, 1050, 40, 1050, 40]); checkNext(['C', 1050, 40, 1010, 40, 1010, 40]); checkNext(['C', 1010, 40, 1010, 10, 1010, 10]); checkNext(['M', 1010, 50]); checkNext(['C', 1010, 50, 1040, 50, 1040, 50]); checkNext(['C', 1040, 50, 1040, 90, 1040, 90]); checkNext(['C', 1040, 90, 1010, 90, 1010, 90]); checkNext(['M', 1010, 100]); checkNext(['C', 1030, 120, 1050, 120, 1050, 100]); checkNext(['M', 1010, 140]); checkNext(['C', 1020, 115, 1030, 115, 1040, 140]); checkNext(['C', 1050, 165, 1050, 165, 1060, 140]); checkNext(['M', 1080, 10]); checkNext(['C', 1100, 29.999999999999996, 1120, 29.999999999999996, 1140, 10]); checkNext(['M', 1080, 50]); checkNext(['C', 1086.66666666666666, 63.33333333333333, 1093.33333333333333, 63.33333333333333, 1100, 50]); checkNext(['C', 1106.66666666666666, 36.666666666666664, 1113.33333333333333, 36.666666666666664, 1120, 50]); checkNext(['M', 1080, 80]); checkNext(['C', 1080, 95.39600717839002, 1096.66666666666667, 105.01851166488379, 1110, 97.32050807568878]); checkNext(['C', 1116.18802153517007, 93.74785217660714, 1120, 87.14531179816328, 1120, 80]); }); it("Snap.path.isPointInside", function () { var path = "M10 10 H 50 V 40 H 10 L 10 10 Z" + "M10 50 L 40 50 L 40 90 L 10 90 Z" + "M10 100 C 30 120, 50 120, 50 100 Z" + "M10 140 C 20 115, 30, 115, 40 140 S 50 165, 60 140 Z" + "M80 10 Q 110 40, 140 10 Z" + "M80 50 Q 90 70, 100 50 T 120 50 Z" + "M80 80 A 10 10 0 0 0 120 80 Z"; expect(Snap.path.isPointInside(path, 15, 35)).to.be(true); expect(Snap.path.isPointInside(path, 35, 75)).to.be(true); expect(Snap.path.isPointInside(path, 15, 102)).to.be(true); expect(Snap.path.isPointInside(path, 15, 135)).to.be(true); expect(Snap.path.isPointInside(path, 50, 145)).to.be(true); expect(Snap.path.isPointInside(path, 130, 15)).to.be(true); expect(Snap.path.isPointInside(path, 110, 45)).to.be(true); expect(Snap.path.isPointInside(path, 85, 55)).to.be(true); expect(Snap.path.isPointInside(path, 115, 82)).to.be(true); expect(Snap.path.isPointInside(path, 95, 98)).to.be(true); expect(Snap.path.isPointInside(path, 5, 5)).to.be(false); expect(Snap.path.isPointInside(path, 25, 48)).to.be(false); expect(Snap.path.isPointInside(path, 42, 87)).to.be(false); expect(Snap.path.isPointInside(path, 12, 105)).to.be(false); expect(Snap.path.isPointInside(path, 47, 113)).to.be(false); expect(Snap.path.isPointInside(path, 47, 135)).to.be(false); expect(Snap.path.isPointInside(path, 25, 142)).to.be(false); expect(Snap.path.isPointInside(path, 15, 125)).to.be(false); expect(Snap.path.isPointInside(path, 43, 152)).to.be(false); expect(Snap.path.isPointInside(path, 58, 152)).to.be(false); expect(Snap.path.isPointInside(path, 90, 21)).to.be(false); expect(Snap.path.isPointInside(path, 130, 21)).to.be(false); expect(Snap.path.isPointInside(path, 95, 48)).to.be(false); expect(Snap.path.isPointInside(path, 110, 55)).to.be(false); expect(Snap.path.isPointInside(path, 100, 70)).to.be(false); expect(Snap.path.isPointInside(path, 115, 96)).to.be(false); expect(Snap.path.isPointInside(path, 85, 96)).to.be(false); // bug #248 expect(Snap.path.isPointInside("M1.4315332974182866,4.405806462382467 L57.26133189673147,176.23225849529868 A185.30156250000002,185.30156250000002 0 0 1 -172.2890356108522,-68.21405480708441 L-4.307225890271305,-1.7053513701771101 A4.6325390625,4.6325390625 0 0 0 1.4315332974182866,4.405806462382467 Z", -58.296875, 70.96875)).to.be(true); }); it("Snap.path.intersection", function () { var path1 = "M10 10 H 50 V 40 H 10 L 10 10 Z" + "M10 50 L 40 50 L 40 90 L 10 90 Z" + "M10 100 C 30 120, 50 120, 50 100 Z" + "M10 140 C 20 115, 30, 115, 40 140 S 50 165, 60 140 Z" + "M80 10 Q 110 40, 140 10 Z" + "M80 50 Q 90 70, 100 50 T 120 50 Z" + "M80 80 A 10 10 0 0 0 120 80 Z"; var path2 = "M10,0 L80,200 L20, 200 L30, 0 L110, 15 L90, 150"; var intersection = Snap.path.intersection(path1, path2); expect(intersection.length).to.be(22); var first = intersection[0]; expect(first.x).to.be.a('number'); expect(first.y).to.be.a('number'); expect(first.t1).to.be.a('number'); expect(first.t2).to.be.a('number'); expect(first.segment1).to.be.a('number'); expect(first.segment2).to.be.a('number'); expect(first.bez1).to.be.an('array'); expect(first.bez2).to.be.an('array'); var checkXY = function(index, x, y) { expect(+intersection[index].x.toFixed(2)).to.be(x); expect(+intersection[index].y.toFixed(2)).to.be(y); } checkXY(0, 13.5, 10); checkXY(1, 29.5, 10); checkXY(2, 24, 40); checkXY(3, 28, 40); checkXY(4, 27.5, 50); checkXY(5, 27.5, 50); checkXY(6, 40, 85.71); checkXY(7, 25.5, 90); checkXY(8, 48.06, 108.75); checkXY(9, 24.46, 110.77); checkXY(10, 45, 100); checkXY(11, 25, 100); checkXY(12, 23.91, 121.7); checkXY(13, 59.46, 141.31); checkXY(14, 59, 140); checkXY(15, 23, 140); checkXY(16, 108.55, 24.82); checkXY(17, 83.33, 10); checkXY(18, 106, 42); checkXY(19, 104.81, 50); checkXY(20, 97.49, 99.44); checkXY(21, 100.37, 80); }); }); ================================================ FILE: test/primitives.js ================================================ describe("Primitives creation", function () { var s; beforeEach(function () { s = Snap(100, 100); }); afterEach(function () { s.remove(); }); it("creates a circle", function () { var c = s.circle(10, 20, 30); var C = document.querySelector("circle"); expect(C).to.not.be(null); expect(C.getAttribute("cx")).to.be("10"); expect(C.getAttribute("cy")).to.be("20"); expect(C.getAttribute("r")).to.be("30"); }); it("creates a rect", function () { var c = s.rect(10, 20, 30, 40, 5); var C = document.querySelector("rect"); expect(C).to.not.be(null); expect(C.getAttribute("x")).to.be("10"); expect(C.getAttribute("y")).to.be("20"); expect(C.getAttribute("width")).to.be("30"); expect(C.getAttribute("height")).to.be("40"); expect(C.getAttribute("rx")).to.be("5"); expect(C.getAttribute("ry")).to.be("5"); }); it("creates a rect with different rx & ry", function () { var c = s.rect(10, 20, 30, 40, 5, 6); var C = document.querySelector("rect"); expect(C).to.not.be(null); expect(C.getAttribute("x")).to.be("10"); expect(C.getAttribute("y")).to.be("20"); expect(C.getAttribute("width")).to.be("30"); expect(C.getAttribute("height")).to.be("40"); expect(C.getAttribute("rx")).to.be("5"); expect(C.getAttribute("ry")).to.be("6"); }); it("creates a ellipse", function () { var c = s.ellipse(10, 20, 30, 40); var C = document.querySelector("ellipse"); expect(C).to.not.be(null); expect(C.getAttribute("cx")).to.be("10"); expect(C.getAttribute("cy")).to.be("20"); expect(C.getAttribute("rx")).to.be("30"); expect(C.getAttribute("ry")).to.be("40"); }); it("creates a ellipse", function () { var c = s.ellipse(10, 20, 30, 40); var C = document.querySelector("ellipse"); expect(C).to.not.be(null); expect(C.getAttribute("cx")).to.be("10"); expect(C.getAttribute("cy")).to.be("20"); expect(C.getAttribute("rx")).to.be("30"); expect(C.getAttribute("ry")).to.be("40"); }); it("creates a path", function () { var c = s.path("M10,10,50,60"); var C = document.querySelector("path"); expect(C).to.not.be(null); expect(C.getAttribute("d")).to.be("M10,10,50,60"); expect(C.getBBox().width).to.be(40); }); it("creates a line", function () { var c = s.line(10, 10, 50, 60); var C = document.querySelector("line"); expect(C).to.not.be(null); expect(C.getAttribute("x1")).to.be("10"); expect(C.getBBox().width).to.be(40); }); it("creates a polyline", function () { var c = s.polyline(10, 10, 50, 60, 70, 80); var C = document.querySelector("polyline"); expect(C).to.not.be(null); expect(C.getAttribute("points")).to.be("10,10,50,60,70,80"); }); it("creates a polygon", function () { var c = s.polygon(10, 10, 50, 60, 70, 80); var C = document.querySelector("polygon"); expect(C).to.not.be(null); expect(C.getAttribute("points")).to.be("10,10,50,60,70,80"); }); it("creates a group", function () { var c = s.group(); var C = document.querySelector("g"); expect(C).to.not.be(null); }); it("creates and fills a group", function () { var c = s.group(), a = s.circle(10, 10, 10), b = s.circle(20, 20, 10), C = document.querySelector("g"); c.add(a, b); expect(C).to.not.be(null); expect(C.childNodes.length).to.be(2); }); it("creates and fills a group on creation", function () { var circle1 = s.circle(10, 10, 10); var circle2 = s.circle(20, 10, 10); var group = s.g(circle1, circle2); var groupEl = document.querySelector("g"); expect(groupEl).to.not.be(null); expect(groupEl.childNodes.length).to.be(2); }); it("creates a text", function () { var c = s.text(10, 10, "test"); var C = document.querySelector("text"); expect(C).to.not.be(null); expect(C.getAttribute("x")).to.be("10"); expect(C.textContent).to.be("test"); }); it("creates a mask", function () { var c = s.mask(); var C = document.querySelector("mask"); expect(C).to.not.be(null); expect(C).to.be(c.node); }); it("creates a pattern", function () { var c = s.ptrn(); var C = document.querySelector("pattern"); expect(C).to.not.be(null); expect(C).to.be(c.node); }); it("creates a pattern(x, y)", function() { var c = s.ptrn(100, 200); expect(c.node.nodeName).to.be("pattern"); expect(c.node.x.baseVal.value).to.be(100); expect(c.node.y.baseVal.value).to.be(200); expect(c.node.parentNode).to.be(s.node); }); it("creates a pattern(x, y, w, h, viewbox)", function() { var c = s.ptrn(100, 200, 300, 400, 10, 20, 30, 40); expect(c.node.nodeName).to.be("pattern"); expect(c.node.x.baseVal.value).to.be(100); expect(c.node.y.baseVal.value).to.be(200); expect(c.node.width.baseVal.value).to.be(300); expect(c.node.height.baseVal.value).to.be(400); expect(c.node.getAttribute("viewBox")).to.be("10 20 30 40"); expect(c.node.parentNode).to.be(s.node); }); }); ================================================ FILE: test/res/file-for-ajax.txt ================================================ success ================================================ FILE: test/set.js ================================================ describe("Set methods", function () { var s; beforeEach(function () { s = Snap(100, 100); }); afterEach(function () { s.remove(); }); it("Set.animate", function(done) { var circle = s.circle(10, 20, 30); var square = s.rect(60, 60, 30, 30); var set = Snap.set(circle, square); var result = set.animate({opacity: .5}, 10); setTimeout(function() { result.forEach(function (el) { var o = el.attr("opacity"); expect(o).to.be("0.5"); }); done(); }, 50); expect(result).to.be(set); }); it("Set.animate - with callback", function(done) { var circle = s.circle(10, 20, 30); var square = s.rect(60, 60, 30, 30); var set = Snap.set(circle, square); var result = set.animate({opacity: .5}, 10, function() { result.forEach(function (el) { var o = el.attr("opacity"); expect(o).to.be("0.5"); }); done(); }); expect(result).to.be(set); }); it("Set.animate - with easing", function(done) { var circle = s.circle(10, 20, 30); var square = s.rect(60, 60, 30, 30); var set = Snap.set(circle, square); var result = set.animate({opacity: .5}, 10, mina.easein); setTimeout(function() { result.forEach(function (el) { var o = el.attr("opacity"); expect(o).to.be("0.5"); }); done(); }, 50); expect(result).to.be(set); }); it("Set.animate - with callback & easing", function(done) { var circle = s.circle(10, 20, 30); var square = s.rect(60, 60, 30, 30); var set = Snap.set(circle, square); var result = set.animate({opacity: .5}, 10, mina.easeout, function() { result.forEach(function (el) { var o = el.attr("opacity"); expect(o).to.be("0.5"); }); done(); }); expect(result).to.be(set); }); it("Set.clear", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2); expect(set.length).to.be(2); set.clear(); expect(set.length).to.be(0); }); it("Set.exclude", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var rect3 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2, rect3); expect(set.length).to.be(3); var excluded = set.exclude(rect2); expect(set.length).to.be(2); expect(excluded).to.be(true); excluded = set.exclude(rect2); expect(set.length).to.be(2); expect(excluded).to.be(false); }); it("Set.remove", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var rect3 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2, rect3); expect(set.length).to.be(3); set.remove(); expect(set.length).to.be(0); expect(rect1.removed).to.be(true); expect(rect2.removed).to.be(true); expect(rect3.removed).to.be(true); }); it("Set.forEach", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var rect3 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2, rect3); var i = 0; var arr = [rect1, rect2, rect3]; var result = set.forEach(function(item) { expect(arr[i]).to.be(item); expect(this.isContext).to.be(true); i++; }, {isContext: true}); expect(result).to.be(set); expect(i).to.be(3); }); it("Set.pop", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var rect3 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2, rect3); expect(set.length).to.be(3); var result = set.pop(); expect(set.length).to.be(2); expect(result).to.be(rect3); expect(set[0]).to.be(rect1); expect(set[1]).to.be(rect2); result = set.pop(); expect(set.length).to.be(1); expect(result).to.be(rect2); expect(set[0]).to.be(rect1); }); it("Set.push", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var rect3 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2); expect(set.length).to.be(2); set.push(rect3); expect(set.length).to.be(3); expect(set[0]).to.be(rect1); expect(set[1]).to.be(rect2); expect(set[2]).to.be(rect3); }); it("Set.attr", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2); set.attr({"fill": "#ff0000"}); expect(rect1.node.getAttribute("fill")).to.be("#ff0000"); expect(rect2.node.getAttribute("fill")).to.be("#ff0000"); set.attr({"stroke": "#0000ff"}); expect(rect1.node.getAttribute("stroke")).to.be("#0000ff"); expect(rect2.node.getAttribute("stroke")).to.be("#0000ff"); }); it("Set.bind", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2); // Setting "stroke" on set only applies it to rect1 set.bind("stroke", rect1); // Setting "fill1" on set maps to fill attribute on rect1 set.bind("fill1", rect1, "fill"); // Setting "fill2" on set maps to fill attribute on rect2 set.bind("fill2", function(v) { rect2.attr({"fill": v}); }); // Set everything to black rect1.attr({"fill": "#000000", "stroke": "#000000"}); rect2.attr({"fill": "#000000", "stroke": "#000000"}); set.attr({"fill1": "#00ff00"}); expect(rect1.node.getAttribute("fill")).to.be("#00ff00"); expect(rect2.node.getAttribute("fill")).to.be("#000000"); // Will trigger the fallback implementation of attr which is // to set that attribute on all elements in the set. set.attr({"fill": "#ff0000"}); expect(rect1.node.getAttribute("fill")).to.be("#ff0000"); expect(rect2.node.getAttribute("fill")).to.be("#ff0000"); set.attr({"fill2": "#00ff00"}); expect(rect1.node.getAttribute("fill")).to.be("#ff0000"); expect(rect2.node.getAttribute("fill")).to.be("#00ff00"); set.attr({"stroke": "#0000ff"}); expect(rect1.node.getAttribute("stroke")).to.be("#0000ff"); expect(rect2.node.getAttribute("stroke")).to.be("#000000"); }); it("Set.splice - remove only", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var rect3 = s.rect(10, 20, 30, 40); var rect4 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2, rect3, rect4); var removedSet = set.splice(1, 2); expect(set.length).to.be(2); expect(set[0]).to.be(rect1); expect(set[1]).to.be(rect4); expect(removedSet.length).to.be(2); expect(removedSet[0]).to.be(rect2); expect(removedSet[1]).to.be(rect3); var emptySet = set.splice(0, 0); expect(set.length).to.be(2); expect(emptySet.length).to.be(0); }); it("Set.splice - remove & insert", function() { var rect1 = s.rect(10, 20, 30, 40); var rect2 = s.rect(10, 20, 30, 40); var rect3 = s.rect(10, 20, 30, 40); var rect4 = s.rect(10, 20, 30, 40); var set = Snap.set(rect1, rect2, rect3); var removedSet = set.splice(2, 1, rect4); expect(set.length).to.be(3); expect(set[0]).to.be(rect1); expect(set[1]).to.be(rect2); expect(set[2]).to.be(rect4); removedSet = set.splice(0, 3, rect4, rect3, rect2, rect1); expect(set[0]).to.be(rect4); expect(set[1]).to.be(rect3); expect(set[2]).to.be(rect2); expect(set[3]).to.be(rect1); expect(removedSet[0]).to.be(rect1); expect(removedSet[1]).to.be(rect2); expect(removedSet[2]).to.be(rect4); }); }); ================================================ FILE: test/snap-tests.js ================================================ describe("Snap methods", function () { it("Snap.Matrix - six params", function () { var matrix = new Snap.Matrix(1, 2, 3, 4, 5, 6); expect(matrix).to.eql({ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }); }); it("Snap.Matrix - SVGMatrix param", function () { var svgMatrix = new Snap(10, 10).node.createSVGMatrix(); var matrix = new Snap.Matrix(svgMatrix); expect(matrix).to.eql({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }); }); it("Snap.ajax - no postData", function(done) { var xhr = Snap.ajax('./res/file-for-ajax.txt', function(xhr) { var responseText = xhr.responseText; expect(responseText).to.be('success'); expect(this.isContext).to.be(true); done(); }, {'isContext': true}); expect(xhr).to.be.an('object'); }); it("Snap.ajax - with object postData", function(done) { var xhr = Snap.ajax('./res/file-for-ajax.txt', {foo: 'bar'}, function(xhr) { var responseText = xhr.responseText; expect(responseText).to.be('success'); expect(this.isContext).to.be(true); done(); }, {'isContext': true}); expect(xhr).to.be.an('object'); }); it("Snap.ajax - with string postData", function(done) { var xhr = Snap.ajax('./res/file-for-ajax.txt', 'foo=bar', function(xhr) { var responseText = xhr.responseText; expect(responseText).to.be('success'); expect(this.isContext).to.be(true); done(); }, {'isContext': true}); expect(xhr).to.be.an('object'); }); var validateMina = function(minaObj) { expect(minaObj).to.be.an('object'); expect(minaObj.id).to.be.a('string'); expect(minaObj.duration).to.be.a('function'); expect(minaObj.easing).to.be.a('function'); expect(minaObj.speed).to.be.a('function'); expect(minaObj.status).to.be.a('function'); expect(minaObj.stop).to.be.a('function'); }; it("Snap.animate - numbers, no easing or callback", function(done) { var n; var minaObj = Snap.animate(10, 20, function(newN) { n = newN; }, 50); setTimeout(function() { expect(n).to.be(20); done(); }, 100); validateMina(minaObj); }); it("Snap.animate - numbers, callback", function(done) { var n; var minaObj = Snap.animate(10, 20, function(newN) { n = newN; }, 50, function() { expect(n).to.be(20); done(); }); validateMina(minaObj); }); it("Snap.animate - numbers, easing", function(done) { var n; var minaObj = Snap.animate(10, 20, function(newN) { n = newN; }, 50, mina.easeinout); setTimeout(function() { expect(n).to.be(20); done(); }, 100); validateMina(minaObj); }); it("Snap.animate - numbers, easing & callback", function(done) { var n; var minaObj = Snap.animate(10, 20, function(newN) { n = newN; }, 50, mina.bounce, function() { expect(n).to.be(20); done(); }); validateMina(minaObj); }); it("Snap.animate - arrays, no easing or callback", function(done) { var n1, n2; var minaObj = Snap.animate([5, 10], [10, 20], function(nArr) { n1 = nArr[0]; n2 = nArr[1]; }, 50); setTimeout(function() { expect(n1).to.be(10); expect(n2).to.be(20); done(); }, 100); validateMina(minaObj); }); it("Snap.animate - arrays, callback", function(done) { var n1, n2; var minaObj = Snap.animate([5, 10], [10, 20], function(nArr) {n1 = nArr[0]; n2 = nArr[1]; }, 50, function() { expect(n1).to.be(10); expect(n2).to.be(20); done(); }); validateMina(minaObj); }); it("Snap.animate - arrays, easing", function(done) { var n1, n2; var minaObj = Snap.animate([5, 10], [10, 20], function(nArr) { n1 = nArr[0]; n2 = nArr[1]; }, 50, mina.easeinout); setTimeout(function() { expect(n1).to.be(10); expect(n2).to.be(20); done(); }, 100); validateMina(minaObj); }); it("Snap.animate - arrays, easing & callback", function(done) { var n1, n2; var minaObj = Snap.animate([5, 10], [10, 20], function(nArr) { n1 = nArr[0]; n2 = nArr[1]; }, 50, mina.backin, function() { expect(n1).to.be(10); expect(n2).to.be(20); done(); }); validateMina(minaObj); }); it("Snap.animation - no easing or callback", function() { var anim = Snap.animation({ foo: "bar" }, 100); expect(anim).to.be.an("object"); expect(anim.dur).to.be(100); expect(anim.attr.foo).to.be("bar"); }); it("Snap.animation - with easing", function() { var anim = Snap.animation({ foo: "bar" }, 100, mina.easein); expect(anim).to.be.an("object"); expect(anim.dur).to.be(100); expect(anim.attr.foo).to.be("bar"); expect(anim.easing).to.be.a("function"); }); it("Snap.animation - with callback", function() { var cb = function(){}; var anim = Snap.animation({ foo: "bar" }, 100, cb); expect(anim).to.be.an("object"); expect(anim.dur).to.be(100); expect(anim.attr.foo).to.be("bar"); expect(anim.callback).to.be.a("function"); }); it("Snap.animation - with easing & callback", function() { var cb = function(){}; var anim = Snap.animation({ foo: "bar" }, 100, mina.linear, cb); expect(anim).to.be.an("object"); expect(anim.dur).to.be(100); expect(anim.attr.foo).to.be("bar"); expect(anim.easing).to.be.a("function"); expect(anim.callback).to.be.a("function"); expect(anim.easing).to.not.be(anim.callback); }); it("Snap.deg", function() { expect(Snap.deg(Math.PI)).to.be(180); expect(Snap.deg(Math.PI / 2)).to.be(90); expect(Snap.deg(Math.PI / 4)).to.be(45); expect(Snap.deg(Math.PI * 2)).to.be(0); }); it("Snap.rad", function() { expect(Snap.rad(180)).to.be(Math.PI); expect(Snap.rad(90)).to.be(Math.PI / 2); expect(Snap.rad(45)).to.be(Math.PI / 4); expect(Snap.rad(0)).to.be(0); }); it("Snap.format", function() { var outputStr; outputStr = Snap.format("{x}", {x: 1}); expect(outputStr).to.be("1"); outputStr = Snap.format("{a['foo']}", { a: { foo: 'bar' } }); expect(outputStr).to.be("bar"); outputStr = Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", { x: 10, y: 20, dim: { width: 40, height: 50, "negative width": -40 } }); expect(outputStr).to.be("M10,20h40v50h-40z"); }); it("Snap.fragment", function() { var frag = Snap.fragment('', ''); expect(frag).to.be.an("object"); expect(frag.node.childNodes.length).to.be(2); expect(frag.node.firstChild.nodeName).to.be("g"); expect(frag.node.firstChild.getAttribute("class")).to.be("foo"); expect(frag.node.lastChild.getAttribute("class")).to.be("foo2"); frag = Snap.fragment(''); var rectWidth = frag.select('rect').attr('width'); expect(rectWidth).to.be("10"); }); it("Snap.is", function() { var undef; expect(Snap.is("foo", "string")).to.be.ok(); expect(Snap.is(123, "number")).to.be.ok(); expect(Snap.is({}, "object")).to.be.ok(); expect(Snap.is([], "array")).to.be.ok(); expect(Snap.is([], "object")).to.be.ok(); expect(Snap.is(null, "null")).to.be.ok(); expect(Snap.is(false, "boolean")).to.be.ok(); expect(Snap.is(undef, "undefined")).to.be.ok(); expect(Snap.is(function(){}, "function")).to.be.ok(); expect(Snap.is(function(){}, "object")).to.be.ok(); }); it("Snap.load - with context", function(done) { Snap.load('./res/external-svg.svg', function(fragment) { expect(fragment.node.querySelector("svg")).to.not.be(null); expect(this.myContext).to.be(true); done(); }, {myContext: true}); }); it("Snap.load - without context", function(done) { Snap.load('./res/external-svg.svg', function(fragment) { expect(fragment.node.querySelector("svg")).to.not.be(null); done(); }); }); it("Snap.parse", function() { var frag = Snap.parse(''); expect(frag).to.be.an("object"); expect(frag.node.childNodes.length).to.be(1); expect(frag.node.firstChild.nodeName).to.be("g"); expect(frag.node.firstChild.getAttribute("class")).to.be("foo"); frag = Snap.parse(''); var rectWidth = frag.select('rect').attr('width'); expect(rectWidth).to.be("10"); }); it("Snap.parsePathString - string", function() { var pathArrs = Snap.parsePathString( "M1 2" + "m3 4" + "L 5, 6" + "l 7, 8" + "H 9" + "h 10" + "V 11" + "v 12" + "C 13 14, 15 16, 17 18" + "c 19 20, 21 22, 23 24" + "S 25 26, 27 28" + "s 29 30, 31 32" + "Q 33 34, 35 36" + "q 37 38, 39 40" + "T 41 42" + "t 43 44" + "A 45 46 47 0 1 48 49" + "a 50 51 52 1 0 53 54" + "Z"); expect(pathArrs[0]).to.eql(["M", 1, 2]); expect(pathArrs[1]).to.eql(["m", 3, 4]); expect(pathArrs[2]).to.eql(["L", 5, 6]); expect(pathArrs[3]).to.eql(["l", 7, 8]); expect(pathArrs[4]).to.eql(["H", 9]); expect(pathArrs[5]).to.eql(["h", 10]); expect(pathArrs[6]).to.eql(["V", 11]); expect(pathArrs[7]).to.eql(["v", 12]); expect(pathArrs[8]).to.eql(["C", 13, 14, 15, 16, 17, 18]); expect(pathArrs[9]).to.eql(["c", 19, 20, 21, 22, 23, 24]); expect(pathArrs[10]).to.eql(["S", 25, 26, 27, 28]); expect(pathArrs[11]).to.eql(["s", 29, 30, 31, 32]); expect(pathArrs[12]).to.eql(["Q", 33, 34, 35, 36]); expect(pathArrs[13]).to.eql(["q", 37, 38, 39, 40]); expect(pathArrs[14]).to.eql(["T", 41, 42]); expect(pathArrs[15]).to.eql(["t", 43, 44]); expect(pathArrs[16]).to.eql(["A", 45, 46, 47, 0, 1, 48, 49]); expect(pathArrs[17]).to.eql(["a", 50, 51, 52, 1, 0, 53, 54]); expect(pathArrs[18]).to.eql(["Z"]); }); it("Snap.parsePathString - array", function() { var pathArrs = Snap.parsePathString(["M1 2"]); expect(pathArrs[0]).to.eql(["M", 1, 2]); }); it("Snap.parseTransformString - string", function() { var matrix = new Snap.Matrix(1, 0, 0, 2, 0, 0); var str = matrix.toTransformString(); var output = Snap.parseTransformString(str); expect(output[0]).to.eql(['s', 1, 2, 0, 0]); }); it("Snap.parseTransformString - array", function() { var output = Snap.parseTransformString(['s', 1, 2, 0, 0]); expect(output[0]).to.eql(['s', 1, 2, 0, 0]); }); it("Snap.select", function() { var s = Snap(10, 10); var group1 = s.group(); var group2 = s.group(); var group3 = s.group(); var circle1 = s.circle(10, 20, 30).attr({ 'class': 'circle-one' }); var circle2 = s.circle(5, 10, 25).attr({ 'class': 'circle-two' }); group1.add(group2); group2.add(group3); group2.add(circle1); group3.add(circle2); var c1 = Snap.select('.circle-one'); expect(circle1).to.be(c1); var c2 = Snap.select('.circle-two'); expect(circle2).to.be(c2); s.remove(); }); it("Snap.selectAll", function() { var s = Snap(10, 10); var group1 = s.group(); var group2 = s.group(); var group3 = s.group(); var circle1 = s.circle(10, 20, 30).attr({ 'class': 'circle-one' }); var circle2 = s.circle(5, 10, 25).attr({ 'class': 'circle-two' }); group1.add(group2); group2.add(group3); group2.add(circle1); group3.add(circle2); var circles = Snap.selectAll('circle'); expect(circles.length).to.be(2); expect(circles).to.contain(circle1); expect(circles).to.contain(circle2); s.remove(); }); it("Snap.snapTo - number, no tolerance", function() { expect(Snap.snapTo(100, -5)).to.be(0); expect(Snap.snapTo(100, 0.0001)).to.be(0); expect(Snap.snapTo(100, 9)).to.be(0); expect(Snap.snapTo(100, 50)).to.be(50); expect(Snap.snapTo(100, 75)).to.be(75); expect(Snap.snapTo(100, 90)).to.be(90); expect(Snap.snapTo(100, 91)).to.be(100); expect(Snap.snapTo(100, 95)).to.be(100); expect(Snap.snapTo(100, 1204)).to.be(1200); }); it("Snap.snapTo - array, no tolerance", function() { var grid = [0, 55, 200]; expect(Snap.snapTo(grid, -5)).to.be(0); expect(Snap.snapTo(grid, 0.0001)).to.be(0); expect(Snap.snapTo(grid, 9)).to.be(0); expect(Snap.snapTo(grid, 50)).to.be(55); expect(Snap.snapTo(grid, 75)).to.be(75); expect(Snap.snapTo(grid, 90)).to.be(90); expect(Snap.snapTo(grid, 91)).to.be(91); expect(Snap.snapTo(grid, 195)).to.be(200); expect(Snap.snapTo(grid, 1204)).to.be(1204); }); it("Snap.snapTo - number, with tolerance", function() { expect(Snap.snapTo(100, 95, 0)).to.be(95); expect(Snap.snapTo(100, 95, 5)).to.be(95); expect(Snap.snapTo(100, 95, 6)).to.be(100); expect(Snap.snapTo(100, 105, 6)).to.be(100); expect(Snap.snapTo(100, 105, 5)).to.be(105); expect(Snap.snapTo(100, 105, 0)).to.be(105); // expect(Snap.snapTo(10, 19, 50)).to.be(20); // TODO: Find out what tolerance > grid should do }); it("Snap.snapTo - array, with tolerance", function() { var grid = [0, 55, 200]; expect(Snap.snapTo(grid, -5, 20)).to.be(0); expect(Snap.snapTo(grid, -5, 3)).to.be(-5); expect(Snap.snapTo(grid, 0.0001, 0.00001)).to.be(0.0001); expect(Snap.snapTo(grid, 0.0001, 1)).to.be(0); expect(Snap.snapTo(grid, 9, 9)).to.be(0); expect(Snap.snapTo(grid, 9, 10.5)).to.be(0); expect(Snap.snapTo(grid, 50, 6)).to.be(55); expect(Snap.snapTo(grid, 50, 1)).to.be(50); expect(Snap.snapTo(grid, 201, 0.5)).to.be(201); expect(Snap.snapTo(grid, 199, 1)).to.be(200); expect(Snap.snapTo(grid, 299, 100)).to.be(200); }); it("Snap.path.getBBox", function() { // same as 10,20,30,40 rect var bbox = Snap.path.getBBox("M10,20h30v40h-30z"); expect(bbox.x).to.eql(10); expect(bbox.y).to.eql(20); expect(bbox.width).to.eql(30); expect(bbox.height).to.eql(40); expect(bbox.x2).to.eql(10 + 30); expect(bbox.y2).to.eql(20 + 40); }); it("Snap.angle - 2 points", function() { var angle = Snap.angle(0, 0, 10, 10); expect(angle).to.not.be(0); expect(angle % 45).to.be(0); angle = Snap.angle(0, 0, 10, 0); expect(angle % 90).to.be(0); }); it("Snap.angle - 3 points", function() { var angle = Snap.angle(10, 0, 0, 10, 0, 0); expect(angle).to.not.be(0); expect(angle % 45).to.be(0); angle = Snap.angle(10, 0, 0, 10, 0, 0); expect(Math.abs(angle)).to.be(90); }); }); ================================================ FILE: test/system.js ================================================ describe("System check", function () { it("Snap exists", function () { expect(Snap).to.be.a("function"); }); it("eve exists", function () { expect(eve).to.be.a("function"); }); it("mina exists", function () { expect(mina).to.be.a("function"); }); }); ================================================ FILE: test/test.html ================================================ Snap Tests
    ================================================ FILE: test/test.js ================================================ var page = require("webpage").create(); page.open("test.html", function (status) { var errors = 0; if (status === "success") { errors = page.evaluate(function () { return +document.querySelector("li.failures em").innerText; }); console.log(errors); } phantom.exit(errors); });