first commit
authorHarris Baptiste <harris.baptiste@iri.centrepompidou.fr>
Tue, 31 May 2016 17:46:32 +0200
changeset 6 37baf9d13f32
parent 5 b458138b8ab6
child 7 62bf1f65855f
first commit
design/iconolab/home_page.ep
src/iconolab/__init__.py
src/iconolab/admin.py
src/iconolab/apps.py
src/iconolab/auth/__init__.py
src/iconolab/auth/forms.py
src/iconolab/auth/urls.py
src/iconolab/auth/views.py
src/iconolab/config.py.tmpl
src/iconolab/migrations/0001_initial.py
src/iconolab/migrations/0002_auto_20160511_1423.py
src/iconolab/migrations/0003_auto_20160512_1422.py
src/iconolab/migrations/0004_auto_20160512_1426.py
src/iconolab/migrations/0005_auto_20160517_1758.py
src/iconolab/migrations/0006_folder.py
src/iconolab/migrations/__init__.py
src/iconolab/models.py
src/iconolab/settings.py
src/iconolab/signals/__init__.py
src/iconolab/signals/handlers.py
src/iconolab/templates/iconolab/annotation.html
src/iconolab/templates/iconolab_base.html
src/iconolab/templates/registration/login.html
src/iconolab/urls.py
src/iconolab/utils.py
src/iconolab/views.py
src/iconolab/wsgi.py
src/manage.py
src/restapi/__init__.py
src/restapi/apps.py
src/restapi/models.py
src/restapi/serializers.py
src/restapi/tests.py
src/restapi/urls.py
src/restapi/views.py
src/static/iconolab/js/components/cutout/index.js
src/static/iconolab/js/components/egonomy-cutout/index.js
src/static/iconolab/js/dist/bundle.js
src/static/iconolab/js/main.js
src/static/iconolab/js/package.json
src/static/iconolab/js/webpack.config.js
--- a/design/iconolab/home_page.ep	Wed Apr 13 18:08:36 2016 +0200
+++ b/design/iconolab/home_page.ep	Tue May 31 17:46:32 2016 +0200
@@ -227,13 +227,13 @@
     			<path style="stroke-linejoin: round;" p:name="line1" id="087e1ac821c54e7580ca6dc81f0492fa" d="M 0 0 C 30 0 60 0 90 0 C 90 8 90 17 90 25 C 60 25 30 25 0 25 C 0 17 0 8 0 0 z"/>
     		</g>
     		<text p:name="text" id="076f69e1623a45da89ab20d55e7905e3" style="font-family: 'Comic Sans MS'; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;" transform="translate(15,17)">Contribuer</text>
-        </g><g xmlns="http://www.w3.org/2000/svg" p:type="Shape" xmlns:p="http://www.evolus.vn/Namespace/Pencil" p:def="Evolus.Sketchy.GUI:tab" id="c9914ea4b1224e8e91c41026c106fd24" transform="matrix(1,0,0,1,460,113)" p:RelatedPage="1457961367776_6378"><p:metadata><p:property name="box"><![CDATA[90,35]]></p:property><p:property name="disabled"><![CDATA[false]]></p:property><p:property name="selected"><![CDATA[false]]></p:property><p:property name="fillColor"><![CDATA[#FFFFFFFF]]></p:property><p:property name="strokeColor"><![CDATA[#000000FF]]></p:property><p:property name="strokeStyle"><![CDATA[1|]]></p:property><p:property name="textContent"><![CDATA[Images à découvrir<br />]]></p:property><p:property name="textFont"><![CDATA['Comic Sans MS'|normal|normal|12px|none]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textAlign"><![CDATA[1,1]]></p:property></p:metadata>
+        </g><g xmlns="http://www.w3.org/2000/svg" p:type="Shape" xmlns:p="http://www.evolus.vn/Namespace/Pencil" p:def="Evolus.Sketchy.GUI:tab" id="c9914ea4b1224e8e91c41026c106fd24" transform="matrix(1,0,0,1,460,113)" p:RelatedPage="1457961367776_6378"><p:metadata><p:property name="box"><![CDATA[90,35]]></p:property><p:property name="disabled"><![CDATA[false]]></p:property><p:property name="selected"><![CDATA[false]]></p:property><p:property name="fillColor"><![CDATA[#FFFFFFFF]]></p:property><p:property name="strokeColor"><![CDATA[#000000FF]]></p:property><p:property name="strokeStyle"><![CDATA[1|]]></p:property><p:property name="textContent"><![CDATA[Appel à contribution<br />]]></p:property><p:property name="textFont"><![CDATA['Comic Sans MS'|normal|normal|12px|none]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textAlign"><![CDATA[1,1]]></p:property></p:metadata>
             <g p:name="rect" id="b253ebbdabe440fc81dd44d4dfd1b816" style="fill: rgb(229, 229, 229); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;">
                 <path d="m 12.801673,0.54494216 c 3.314701,-0.0352053 7.573557,-0.0890949 7.573557,-0.0890949 l 9.303121,0.28181817 8.814904,-0.0695124 7.648939,-0.21610285 10.722374,0.34092343 8.532977,-0.06653 7.912504,-0.13988077 7.482767,0.24608667 c 3.626099,0.54342239 8.567352,3.03702269 8.395326,6.17228789 l 0.09915,3.0270916 0.169651,5.440829 0.320925,4.665193 -0.220173,5.51029 0.05642,4.261539 -0.194682,4.709292 L 78.972906,34.79986 71.358529,34.78491 61.024168,34.622897 51.670695,34.441941 44.075442,34.552909 34.433383,34.74434 27.610762,34.66976 18.987081,34.795183 9.3624747,34.593551 0.50906708,34.619172 0.7793527,29.919697 0.7538155,26.107691 0.62109819,22.175759 0.91240035,18.01618 0.44387272,13.884022 0.50371576,8.4666042 C 1.4978957,3.4882952 3.1208872,3.295014 7.2739642,1.133705 8.3402909,0.57877605 10.899509,1.1741957 12.801673,0.54494216 z" p:name="line1" id="a8d6b5933b754b0a959b980b50214737" transform="scale(1,1)"/>
 
             </g>
             <path style="fill: white; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1; visibility: hidden; display: none;" p:name="line2" id="d461d9d92b0140758eaf1b3ed296e013" d="M 1.5 31 C 30 32 60 32 88.5 31 C 88 33 88 36 88.5 38 C 60 39 30 39 1.5 38 z"/>
-            <foreignObject x="0" y="1" width="90" height="34" p:name="text" id="508596e0186b49fdaebadaee1a84fdaf" style="font-family: 'Comic Sans MS'; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; color: rgb(0, 0, 0); opacity: 1; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml">Images à découvrir<br /></div></foreignObject>
+            <foreignObject x="0" y="1" width="90" height="34" p:name="text" id="508596e0186b49fdaebadaee1a84fdaf" style="font-family: 'Comic Sans MS'; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; color: rgb(0, 0, 0); opacity: 1; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml">Appel à contribution<br /></div></foreignObject>
 
         </g><g xmlns="http://www.w3.org/2000/svg" p:type="Shape" xmlns:p="http://www.evolus.vn/Namespace/Pencil" p:def="Evolus.Common:PlainTextV2" id="67b868e78acf4edd8a34016301972c01" transform="matrix(1,0,0,1,23,39)" p:sizing-ox="0" p:sizing-oy="11" p:sizing-ow="72" p:sizing-oh="15"><p:metadata><p:property name="disabled"><![CDATA[false]]></p:property><p:property name="width"><![CDATA[100,0]]></p:property><p:property name="fixedWidth"><![CDATA[false]]></p:property><p:property name="label"><![CDATA[ICONOLAB]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textFont"><![CDATA["Liberation Sans",Arial,sans-serif|bold|normal|13px|none]]></p:property><p:property name="textAlign"><![CDATA[0,0]]></p:property></p:metadata>
             <rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="2a2f34cf77ee49e9910efcfe45e1af3c" width="0" height="0"/>
@@ -421,7 +421,15 @@
             <foreignObject x="0" y="0" width="69" height="17" p:name="htmlObject" id="b28405a07049412f9b9b15aaef5520bd" style="color: rgb(0, 0, 255); opacity: 1; font-family: 'Comic Sans MS'; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none;">
                 <div xmlns="http://www.w3.org/1999/xhtml" p:name="textDiv" id="5e2583573fdb4ee0b3042d1bee85e3ea" style="white-space: nowrap; text-decoration: underline; display: inline-block !important; "><div xmlns="http://www.w3.org/1999/xhtml">utilisateur 1</div></div>
             </foreignObject>
-        </g></g></Content></Page><Page><Properties><Property name="name">Detail Fragment</Property><Property name="id">1457961784950_7912</Property><Property name="width">1191</Property><Property name="height">2074</Property><Property name="dimBackground"/><Property name="transparentBackground">true</Property><Property name="backgroundColor">#FFFFFFFF</Property><Property name="fid">detail_fragment</Property><Property name="background">transparent</Property></Properties><Content><g xmlns="http://www.w3.org/2000/svg" p:type="Group" xmlns:p="http://www.evolus.vn/Namespace/Pencil" transform="matrix(1,0,0,1,-2520.425048828125,-192.87008666992188)" id="88ac58ab4bf54674852c794b649ae91a" p:sizing-gow="9" p:sizing-goh="18"><g p:type="Shape" p:def="Evolus.Common:triangle2" id="ab62a58ee67d416ba6f43834c91de0c7" transform="matrix(1,0,0,1,-0.5,-0.5)" p:sizing-ox="0" p:sizing-oy="0" p:sizing-ow="9" p:sizing-oh="7"><p:metadata><p:property name="box"><![CDATA[9,7]]></p:property><p:property name="a"><![CDATA[4.604651162790699,0]]></p:property><p:property name="fillColor"><![CDATA[#33FF33FF]]></p:property><p:property name="strokeColor"><![CDATA[#1B3280FF]]></p:property><p:property name="strokeStyle"><![CDATA[1|]]></p:property><p:property name="textContent"><![CDATA[]]></p:property><p:property name="textFont"><![CDATA[Comic Sans MS|normal|normal|8px|none]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textAlign"><![CDATA[1,1]]></p:property></p:metadata>
+        </g></g><g xmlns="http://www.w3.org/2000/svg" p:type="Shape" xmlns:p="http://www.evolus.vn/Namespace/Pencil" p:def="Evolus.Sketchy.GUI:tab" id="4dbb0ae14554480a8920166ae3737c64" transform="matrix(1,0,0,1,268,113)" p:RelatedPage="1457961367776_6378"><p:metadata><p:property name="box"><![CDATA[90,35]]></p:property><p:property name="disabled"><![CDATA[false]]></p:property><p:property name="selected"><![CDATA[false]]></p:property><p:property name="fillColor"><![CDATA[#FFFFFFFF]]></p:property><p:property name="strokeColor"><![CDATA[#000000FF]]></p:property><p:property name="strokeStyle"><![CDATA[1|]]></p:property><p:property name="textContent"><![CDATA[Images à découvrir<br />]]></p:property><p:property name="textFont"><![CDATA['Comic Sans MS'|normal|normal|12px|none]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textAlign"><![CDATA[1,1]]></p:property></p:metadata>
+            <g p:name="rect" id="e3a5e8fdd11e4045a9ecec7cbbd4def1" style="fill: rgb(229, 229, 229); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;">
+                <path d="m 12.801673,0.54494216 c 3.314701,-0.0352053 7.573557,-0.0890949 7.573557,-0.0890949 l 9.303121,0.28181817 8.814904,-0.0695124 7.648939,-0.21610285 10.722374,0.34092343 8.532977,-0.06653 7.912504,-0.13988077 7.482767,0.24608667 c 3.626099,0.54342239 8.567352,3.03702269 8.395326,6.17228789 l 0.09915,3.0270916 0.169651,5.440829 0.320925,4.665193 -0.220173,5.51029 0.05642,4.261539 -0.194682,4.709292 L 78.972906,34.79986 71.358529,34.78491 61.024168,34.622897 51.670695,34.441941 44.075442,34.552909 34.433383,34.74434 27.610762,34.66976 18.987081,34.795183 9.3624747,34.593551 0.50906708,34.619172 0.7793527,29.919697 0.7538155,26.107691 0.62109819,22.175759 0.91240035,18.01618 0.44387272,13.884022 0.50371576,8.4666042 C 1.4978957,3.4882952 3.1208872,3.295014 7.2739642,1.133705 8.3402909,0.57877605 10.899509,1.1741957 12.801673,0.54494216 z" p:name="line1" id="e412c3a2257b4e70822bc38d23a0a11e" transform="scale(1,1)"/>
+
+            </g>
+            <path style="fill: white; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1; visibility: hidden; display: none;" p:name="line2" id="0b89132132d14cb8bdd4e6329585edaf" d="M 1.5 31 C 30 32 60 32 88.5 31 C 88 33 88 36 88.5 38 C 60 39 30 39 1.5 38 z"/>
+            <foreignObject x="0" y="1" width="90" height="34" p:name="text" id="049d482c0c6649279b82ec7eba8dd403" style="font-family: 'Comic Sans MS'; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; color: rgb(0, 0, 0); opacity: 1; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml">Images à découvrir<br /></div></foreignObject>
+
+        </g></Content></Page><Page><Properties><Property name="name">Detail Fragment</Property><Property name="id">1457961784950_7912</Property><Property name="width">1191</Property><Property name="height">2074</Property><Property name="dimBackground"/><Property name="transparentBackground">true</Property><Property name="backgroundColor">#FFFFFFFF</Property><Property name="fid">detail_fragment</Property><Property name="background">transparent</Property></Properties><Content><g xmlns="http://www.w3.org/2000/svg" p:type="Group" xmlns:p="http://www.evolus.vn/Namespace/Pencil" transform="matrix(1,0,0,1,-2520.425048828125,-192.87008666992188)" id="88ac58ab4bf54674852c794b649ae91a" p:sizing-gow="9" p:sizing-goh="18"><g p:type="Shape" p:def="Evolus.Common:triangle2" id="ab62a58ee67d416ba6f43834c91de0c7" transform="matrix(1,0,0,1,-0.5,-0.5)" p:sizing-ox="0" p:sizing-oy="0" p:sizing-ow="9" p:sizing-oh="7"><p:metadata><p:property name="box"><![CDATA[9,7]]></p:property><p:property name="a"><![CDATA[4.604651162790699,0]]></p:property><p:property name="fillColor"><![CDATA[#33FF33FF]]></p:property><p:property name="strokeColor"><![CDATA[#1B3280FF]]></p:property><p:property name="strokeStyle"><![CDATA[1|]]></p:property><p:property name="textContent"><![CDATA[]]></p:property><p:property name="textFont"><![CDATA[Comic Sans MS|normal|normal|8px|none]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textAlign"><![CDATA[1,1]]></p:property></p:metadata>
             <path style="stroke-linejoin: round; stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 1; fill: rgb(51, 255, 51); fill-opacity: 1;" p:name="path" id="0d741bf3ad1342b7b14747b5534ab7f9" transform="translate(0.5,0.5)" d="M 4.604651162790699 0 L 9 7 L 0 7 z"/>
             <text p:name="text" id="bf166f085b5847978c8a64de3ad9b0b9" style="font-family: Comic Sans MS; font-size: 8px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;" transform="translate(5,5)"/>
         </g><g xmlns="http://www.w3.org/2000/svg" p:type="Shape" xmlns:p="http://www.evolus.vn/Namespace/Pencil" p:def="Evolus.Common:triangle2" id="5097cc9a401f4c6d93fb0e19331f5f3d" transform="matrix(-1,2.693795464470128e-10,-2.693795464470128e-10,-1,9.5,18.5)" p:sizing-ox="-2.0203465567192325e-9" p:sizing-oy="11.00000000013469" p:sizing-ow="9" p:sizing-oh="7"><p:metadata><p:property name="box"><![CDATA[9,7]]></p:property><p:property name="a"><![CDATA[4.6125,0]]></p:property><p:property name="fillColor"><![CDATA[#FF0000FF]]></p:property><p:property name="strokeColor"><![CDATA[#1B3280FF]]></p:property><p:property name="strokeStyle"><![CDATA[1|]]></p:property><p:property name="textContent"><![CDATA[]]></p:property><p:property name="textFont"><![CDATA["Liberation Sans",Arial,sans-serif|normal|normal|13px|none]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textAlign"><![CDATA[1,1]]></p:property></p:metadata>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/admin.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,30 @@
+from django.contrib import admin
+from reversion_compare.admin import CompareVersionAdmin
+
+
+# Register your models here.
+from iconolab.models import (Tag, Annotation, Collection, Image,
+	Comment, CommentAttachement, MetaCategory, Activity, Notification)
+
+
+class CommentAttachmentInline(admin.TabularInline):
+	model = CommentAttachement
+	extra = 1
+
+
+class CommentAdmin(admin.ModelAdmin):
+	inlines = [CommentAttachmentInline]
+
+
+class AnnotationAdmin(CompareVersionAdmin):
+	history_latest_first = True
+
+#reversion
+admin.site.register(Image)
+admin.site.register(Collection)
+admin.site.register(Tag)
+admin.site.register(Annotation, AnnotationAdmin)
+admin.site.register(MetaCategory)
+admin.site.register(Comment, CommentAdmin)
+admin.site.register(Activity)
+admin.site.register(Notification)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/apps.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,8 @@
+from django.apps import AppConfig
+
+class IconolabApp(AppConfig):
+	name = 'iconolab'
+	verbose_name = 'Iconolab'
+
+	def ready(self):
+		import iconolab.signals.handlers
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/auth/urls.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+
+from django.conf.urls import url
+from django.contrib.auth.views import login, password_change
+from . import views
+
+urlpatterns = [
+    url(r'^login/', login, name='login'),
+    url(r'^password/reset', password_change, name='password_reset'),
+ 	#url(r'^logout/', views.logout_view, name='logout'),
+ 	#url(r'^password/reset', view)
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/auth/views.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,12 @@
+from django.contrib.auth import authenticate, login
+from django.shortcuts import redirect, render, HttpResponse
+
+
+def login_form(request):
+	pass
+
+def login_view(request):
+	return HttpResponse('show show a login form')
+	
+def logout_view(request):
+	logout(request)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/config.py.tmpl	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,153 @@
+"""
+Django settings for iconolab project.
+
+Generated by 'django-admin startproject' using Django 1.9.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.9/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.9/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+STATIC_ROOT = os.path.join(BASE_DIR, 'web')
+
+LOGIN_URL = '/account/login/'
+#Static path
+MEDIA_ROOT = os.path.join(BASE_DIR, 'iconolab', 'media')
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '#8)+upuo3vc7fi15czxz53ml7*(1__q8hg=m&+9ylq&st1_kqv'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+THUMBNAIL_DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'restapi',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'notifications',
+    'reversion',
+    'reversion_compare',
+    'iconolab.apps.IconolabApp',
+    'sorl.thumbnail',
+]
+
+ADD_REVERSION_ADMIN=True
+
+
+MIDDLEWARE_CLASSES = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'reversion.middleware.RevisionMiddleware',
+]
+
+STATICFILES_DIRS = [
+    os.path.join(BASE_DIR, 'static'),
+    os.path.join(BASE_DIR, 'iconolab', 'media'),
+]
+
+MEDIA_URL = os.path.join(BASE_DIR, 'iconolab', 'media') + "/"
+
+ROOT_URLCONF = 'iconolab.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [os.path.join(BASE_DIR,'iconolab','templates')],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'iconolab.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+CACHES = {
+    'default': {
+        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
+        'LOCATION': os.path.join(MEDIA_ROOT, 'cache'),
+#        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+#        'LOCATION': 'unix:/var/run/memcached/memcached.socket',
+#        'KEY_PREFIX': 'ldt',
+    }
+}
+# Password validation
+# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.9/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.9/howto/static-files/
+
+STATIC_URL = '/static/'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0001_initial.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.6 on 2016-05-11 12:01
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Annotation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=255)),
+                ('description', models.TextField(null=True)),
+                ('fragment', models.TextField()),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Collection',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=50)),
+                ('description', models.CharField(max_length=255)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Comment',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created_date', models.DateTimeField(auto_now_add=True)),
+                ('content', models.TextField()),
+                ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+                ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Annotation')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='CommentAttachement',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('attachment_type', models.IntegerField(choices=[(1, 'link'), (2, 'image'), (3, 'pdf')], default=1)),
+                ('created_date', models.DateTimeField(auto_now_add=True)),
+                ('data', models.TextField()),
+                ('comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='iconolab.Comment')),
+                ('main_annotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Annotation')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Image',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=200)),
+                ('media', models.ImageField(height_field='height', upload_to='uploads/', width_field='width')),
+                ('height', models.IntegerField()),
+                ('width', models.IntegerField()),
+                ('mimetype', models.CharField(blank=True, max_length=1024, null=True)),
+                ('exif', models.TextField(blank=True, null=True)),
+                ('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Collection')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Tag',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('label', models.SlugField()),
+                ('link', models.URLField()),
+                ('description', models.TextField()),
+                ('source', models.CharField(max_length=50)),
+            ],
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='image',
+            field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='iconolab.Image'),
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='tags',
+            field=models.ManyToManyField(to='iconolab.Tag'),
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0002_auto_20160511_1423.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.6 on 2016-05-11 12:23
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('iconolab', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='comment',
+            name='content',
+            field=models.TextField(blank=True),
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0003_auto_20160512_1422.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.6 on 2016-05-12 12:22
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('iconolab', '0002_auto_20160511_1423'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='MetaCategories',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('label', models.CharField(max_length=200)),
+                ('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Collection')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='comment',
+            name='metacategories',
+            field=models.ManyToManyField(to='iconolab.MetaCategories'),
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0004_auto_20160512_1426.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.6 on 2016-05-12 12:26
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('iconolab', '0003_auto_20160512_1422'),
+    ]
+
+    operations = [
+        migrations.RenameModel(
+            old_name='MetaCategories',
+            new_name='MetaCategory',
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0005_auto_20160517_1758.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.6 on 2016-05-17 15:58
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('iconolab', '0004_auto_20160512_1426'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Activity',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('verb', models.IntegerField(choices=[(1, 'Nouveau commentaire'), (2, 'Nouvelle révision'), (3, 'Nouveau commentaire de révision'), (4, 'New expert ans')])),
+                ('target_object_id', models.PositiveIntegerField()),
+                ('action_object_id', models.PositiveIntegerField(blank=True, null=True)),
+                ('created_date', models.DateTimeField(auto_now_add=True)),
+                ('action_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='activity_action', to='contenttypes.ContentType')),
+                ('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+                ('target_content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Notification',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('status', models.IntegerField(choices=[(1, 'Lu'), (0, 'Non lu'), (2, 'Effacé')], default=0)),
+                ('created_date', models.DateTimeField(auto_now_add=True)),
+                ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Activity')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.AlterModelOptions(
+            name='metacategory',
+            options={'verbose_name_plural': 'Metacategories'},
+        ),
+        migrations.RemoveField(
+            model_name='tag',
+            name='source',
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='author',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='date_created',
+            field=models.DateTimeField(auto_now_add=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='date_modified',
+            field=models.DateTimeField(auto_now=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='comment',
+            name='annotation',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='iconolab.Annotation'),
+        ),
+        migrations.AddField(
+            model_name='image',
+            name='date_created',
+            field=models.DateTimeField(auto_now_add=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='image',
+            name='date_modified',
+            field=models.DateTimeField(auto_now=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='comment',
+            name='target',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Comment'),
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0006_folder.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.6 on 2016-05-31 12:42
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('iconolab', '0005_auto_20160517_1758'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Folder',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('label', models.CharField(max_length=255)),
+                ('images', models.ManyToManyField(to='iconolab.Image')),
+                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/models.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,157 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.models import ContentType
+from reversion import revisions as reversion
+from reversion.models import Revision
+
+
+class Tag(models.Model):
+	label = models.SlugField()
+	link = models.URLField()
+	description = models.TextField()
+
+	def __str__(self):
+		return '%d:%s' % (self.id, self.label)
+
+
+# fonds Ingres - Musée de la Poste 
+class Collection(models.Model):
+	name =	models.CharField(max_length=50)
+	description = models.CharField(max_length=255)
+
+	def __str__(self):
+		return self.name
+
+# class image_metadata
+class Image(models.Model):
+	name = models.CharField(max_length=200)
+	media = models.ImageField(upload_to='uploads/', height_field='height', width_field='width')
+	height = models.IntegerField(null=False, blank=False)
+	width = models.IntegerField(null=False, blank=False)
+	mimetype = models.CharField(null=True, blank=True, max_length=1024)
+	exif = models.TextField(null=True, blank=True) 
+	collection = models.ForeignKey(Collection)
+	date_created = models.DateTimeField(auto_now_add=True, null=True)
+	date_modified = models.DateTimeField(auto_now=True, null=True)
+	
+	def __str__(self):
+		return self.name
+
+# Folders
+class Folder(models.Model):
+	label = models.CharField(max_length=255)
+	owner = models.ForeignKey(User)
+	images = models.ManyToManyField(Image)
+
+	def __str__(self):
+		return label
+
+@reversion.register()
+class Annotation(models.Model):
+	title = models.CharField(max_length=255)
+	description = models.TextField(null=True)
+	fragment = models.TextField() # path string
+	tags = models.ManyToManyField(Tag)
+	image = models.ForeignKey(Image, default=0, on_delete=models.CASCADE)
+	author = models.ForeignKey(User, null=True)
+	date_created = models.DateTimeField(auto_now_add=True, null=True)
+	date_modified = models.DateTimeField(auto_now=True, null=True)
+
+	def __str__(self):
+		return self.title
+
+# comments
+
+LINK = 1
+IMAGE = 2
+PDF = 3
+
+COMMENT_CHOICES = (
+	(LINK, 'link'),
+	(IMAGE, 'image'),
+	(PDF, 'pdf')
+	)
+
+
+class MetaCategory(models.Model):
+	collection = models.ForeignKey(Collection)
+	label = models.CharField(max_length=200)
+
+	def __str__(self):
+		return self.label
+
+	class Meta:
+		verbose_name_plural = "Metacategories"
+
+class Comment(models.Model):
+	author =  models.ForeignKey(User)
+	created_date = models.DateTimeField(blank=False, null=False, auto_now_add=True)
+	annotation = models.ForeignKey(Annotation, null=True) # revision de l'utilisateur?
+	target = models.ForeignKey("self") #
+	#thread_id #django-contrib-comment #threadedcomment
+	content = models.TextField(blank=True)
+	metacategories = models.ManyToManyField(MetaCategory) 
+
+
+class CommentAttachement(models.Model):
+	comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name='attachments')
+	main_annotation = models.ForeignKey(Annotation)
+	attachment_type = models.IntegerField(choices=COMMENT_CHOICES, default=1)
+	created_date = models.DateTimeField(auto_now_add=True)
+	data = models.TextField(blank=False)
+
+
+# Activity & Notification
+
+class Activity(models.Model):
+
+	NEW_COMMENT = 1
+	NEW_REVISION = 2
+	NEW_REVISION_COMMENT = 3
+	NEW_EXPERT_ANSWER = 4
+	NEW_REFERENCE = 5
+	EXPERT_CALL = 6
+
+	ACTIVITY_VERBS =(
+		(NEW_COMMENT, 'Nouveau commentaire'),
+		(NEW_REVISION, 'Nouvelle révision'),
+		(NEW_REVISION_COMMENT, 'Nouveau commentaire de révision'),
+		(NEW_EXPERT_ANSWER, 'New expert ans')
+		) 
+
+	verb = models.IntegerField(choices=ACTIVITY_VERBS)
+	actor = models.ForeignKey(User)
+
+	target_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
+	target_object_id = models.PositiveIntegerField()
+	target = GenericForeignKey('target_content_type', 'target_object_id')
+
+	action_content_type = models.ForeignKey(ContentType, related_name='activity_action', on_delete=models.CASCADE, null=True, blank=True)
+	action_object_id = models.PositiveIntegerField(null=True, blank=True)
+	action_content = GenericForeignKey('action_content_type', 'action_object_id') 
+	
+	created_date = models.DateTimeField(auto_now_add=True)
+
+	def __str__(self):
+		return '%s:%s' % (author.name, verbe)
+
+
+class Notification(models.Model):
+
+	UNREAD = 0
+	READ = 1
+	DELETED = 2
+
+	STATUS = (
+		(READ, 'Lu'),
+		(UNREAD, 'Non lu'),
+		(DELETED, 'Effacé')
+		)
+
+	activity = models.ForeignKey(Activity)
+	user = models.ForeignKey(User)
+	status = models.IntegerField(choices=STATUS, default=UNREAD)
+	created_date = models.DateTimeField(auto_now_add=True)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/settings.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,153 @@
+"""
+Django settings for iconolab project.
+
+Generated by 'django-admin startproject' using Django 1.9.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.9/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.9/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+STATIC_ROOT = os.path.join(BASE_DIR, 'web')
+
+LOGIN_URL = '/account/login/'
+#Static path
+MEDIA_ROOT = os.path.join(BASE_DIR, 'iconolab', 'media')
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '#8)+upuo3vc7fi15czxz53ml7*(1__q8hg=m&+9ylq&st1_kqv'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+THUMBNAIL_DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'restapi',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'notifications',
+    'reversion',
+    'reversion_compare',
+    'iconolab.apps.IconolabApp',
+    'sorl.thumbnail',
+]
+
+ADD_REVERSION_ADMIN=True
+
+
+MIDDLEWARE_CLASSES = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'reversion.middleware.RevisionMiddleware',
+]
+
+STATICFILES_DIRS = [
+    os.path.join(BASE_DIR, 'static'),
+    os.path.join(BASE_DIR, 'iconolab', 'media'),
+]
+
+MEDIA_URL = os.path.join(BASE_DIR, 'iconolab', 'media') + "/"
+
+ROOT_URLCONF = 'iconolab.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [os.path.join(BASE_DIR,'iconolab','templates')],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'iconolab.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+CACHES = {
+    'default': {
+        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
+        'LOCATION': os.path.join(MEDIA_ROOT, 'cache'),
+#        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+#        'LOCATION': 'unix:/var/run/memcached/memcached.socket',
+#        'KEY_PREFIX': 'ldt',
+    }
+}
+# Password validation
+# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.9/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.9/howto/static-files/
+
+STATIC_URL = '/static/'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/signals/handlers.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,29 @@
+from iconolab.models import Comment, Annotation
+from django.db.models import signals
+from django.dispatch import receiver
+from pprint import pprint
+from notifications.signals import notify
+from iconolab.utils import NotificationManager
+import pprint
+
+
+#handle metacategories
+
+@receiver(signals.post_save, sender=Comment)
+def handle_metacategory(sender, **kwargs):
+	commentInstance = kwargs.get('instance')
+	if commentInstance is not None:
+		metacategories_list = [mcat.label for mcat in commentInstance.metacategories.all()]
+		print(", ".join(metacategories_list))
+
+
+@receiver(signals.post_save, sender=Annotation)
+def handle_new_annotation(sender, **kwargs):
+	object_instance = kwargs.get('instance')
+	if object_instance is None:
+		pass
+
+	notification = NotificationManager.create_notification(object_instance.author, verb=NotificationManager.NEW_ANNOTATION)
+	NotificationManager.notify(notification=notification)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/annotation.html	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,54 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+
+{% block page_js %}
+	<script src="{% static 'iconolab/js/dist/bundle.js' %}" type="text/javascript"></script>
+{% endblock%}
+
+{% block content %}
+	<div>
+		
+		<div class="col-md-4">
+			<div id="fragmentContainer">
+			{% thumbnail annotation.image.media "100x100" crop="center" as im %}
+    				<img src=" {{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+    		{% empty %}
+    			<p>No image</p>
+			{% endthumbnail %}
+			</div>
+		</div>
+
+		<div class="col-md-4">
+
+			<div id='iconolab-image' style='position:relative'>
+				<img src="{% static annotation.image.media.url %}" 
+				width="{{ annotation.image.width }}" 
+				height="{{ annotation.image.height }}" />
+				<!-- deal with path here -->
+				<svg class='cut-canvas' style='border: 1px solid red;'></svg>
+				<path class='image-path' d='M150 0 L75 200 L225 200 Z' />
+			</div>
+
+			<div sytle='border: 1px solid red; width: 300px; height:500px'>
+
+				{% thumbnail annotation.image.media "100x100" crop="center" as im %}
+    				<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+				{% endthumbnail %}
+
+				<p>Test iri</p>
+			</div>
+
+		</div>
+
+	</div>
+{% endblock %}
+
+{% block footer_js %}
+	<script>
+		iconolab.initCutoutComponent({ imageId: '#iconolab-image' });
+	</script>
+{% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab_base.html	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,29 @@
+{% load staticfiles %}
+<!DOCTYPE html>
+<html>
+
+{% block head %}
+	<head>
+		<title>{% block title %} {% endblock %}</title>
+		
+		{% block js %} {% endblock %}
+
+		{% block page_js %} {% endblock %}
+
+		{% block main_css %} 
+		<link rel="stylesheet" href="{% static 'iconolab/js/node_modules/bootstrap/dist/css/bootstrap.min.css' %}">
+		{% endblock %}
+		
+		{% block page_css %} {% endblock %}
+
+	</head>
+{% endblock %}
+
+	<body>
+		<!-- navigation -->
+	    <div class="container">
+			{% block content %} {% endblock %}
+	    </div>
+	    {% block footer_js %} {% endblock %}
+	</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/registration/login.html	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,38 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+{% if form.errors %}
+<p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+{% if next %}
+    {% if user.is_authenticated %}
+    <p>Your account doesn't have access to this page. To proceed,
+    please login with an account that has access.</p>
+    {% else %}
+    <p>Please login to see this page.</p>
+    {% endif %}
+{% endif %}
+
+<form method="post" action="{% url 'account:login' %}">
+{% csrf_token %}
+<table>
+<tr>
+    <td>{{ form.username.label_tag }}</td>
+    <td>{{ form.username }}</td>
+</tr>
+<tr>
+    <td>{{ form.password.label_tag }}</td>
+    <td>{{ form.password }}</td>
+</tr>
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="{{ next }}" />
+</form>
+
+{# Assumes you setup the password_reset view in your URLconf #}
+<p><a href="{% url 'account:password_reset' %}">Lost password?</a></p>
+
+{% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/urls.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,37 @@
+"""iconolab URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.9/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.conf.urls import url, include
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url, include
+from django.contrib import admin
+from notifications import urls
+from . import views
+from . import settings
+
+from django.conf.urls.static import static
+from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+
+urlpatterns = [
+    url(r'^admin/', admin.site.urls),
+    url(r'^home', views.index, name="home"),
+    url(r'^annotation/(?P<pk>[0-9]+)/$', views.detail_annotation, name='annotation_detail'),
+    url(r'^rest', include('restapi.urls')),
+    url('^inbox/notifications/', include(urls, namespace='notifications')),
+    url('^account/', include('iconolab.auth.urls', namespace='account')),
+]
+
+urlpatterns += staticfiles_urlpatterns()
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+
+print(urlpatterns)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/utils.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,35 @@
+from django.db.models.signals import post_save
+from notifications.signals import notify
+
+class NotificationManager:
+
+	NEW_ANNOTATION = 'Nouvelle annotation' 
+	NEW_COMMENT = 'Nouveau commentaire'
+
+	class Notification:
+
+		def __init__(self, sender=None, verb=None):
+			self.sender = sender
+			self.verb = verb
+			self.recipient = None
+			self.target = None
+			self.description = None
+
+		def set_recipient(self, recipient):
+			self.recipient = recipient
+			
+		def set_target(self, target):
+			self.target = target
+
+		def set_description(self, description):
+			self.description = description
+
+	@classmethod
+	def create_notification(self, sender=None, verb=None):
+		annotation = NotificationManager.Notification(sender, verb=verb)
+		return annotation
+
+	@classmethod
+	def notify(self, notification=None):
+		#send to all users or a Group
+		notify.send(notification.sender, recipient=notification.sender, verb=notification.verb)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/views.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,14 @@
+from django.shortcuts import HttpResponse, get_object_or_404, render
+from iconolab.models import Annotation
+from django.http import Http404
+from django.contrib.auth.decorators import login_required
+
+@login_required
+def index(request):
+	print(request.user)
+	return HttpResponse("<p>Home</p>");
+
+
+def detail_annotation(request, pk):
+	annotation = get_object_or_404(Annotation, pk=pk)
+	return render(request, 'iconolab/annotation.html', {'annotation': annotation})
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/wsgi.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,16 @@
+"""
+WSGI config for iconolab project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "iconolab.settings")
+
+application = get_wsgi_application()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/manage.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "iconolab.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/restapi/apps.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class RestapiConfig(AppConfig):
+    name = 'restapi'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/restapi/models.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,1 @@
+from django.db import models
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/restapi/serializers.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,22 @@
+from rest_framework import serializers
+from iconolab.models import Annotation
+
+
+class AnnotationSerializer(serializers.ModelSerializer):
+	tags = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='get')
+	class Meta:
+		model = Annotation
+		fields = ('id', 'title', 'description', 'fragment', 'tags')
+		
+	def create(self, validated_data):
+		"""
+		Create a new Annotation
+		"""
+		return Annotation.create(**validated_data)
+
+	def update(self, instance, validated_data):
+		instance.title = validated_data.get('title', instance.title)
+		instance.description = validated_data.get('description', instance.description)
+		instance.fragment = validated_data.get('fragment', instance.fragment)
+		instance.save()
+		return instance
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/restapi/tests.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/restapi/urls.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,8 @@
+from django.conf.urls import url
+from . import views
+
+urlpatterns = [
+	url(r'^$', views.index, name='index'),
+	url(r'annotation$', views.annotation_list),
+	url(r'annotation/(?P<pk>[0-9]+)/$', views.get)
+]
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/restapi/views.py	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,41 @@
+from django.shortcuts import render, HttpResponse
+from pprint import pprint
+
+from django.views.decorators.csrf import csrf_exempt
+from rest_framework.renderers import JSONRenderer
+from rest_framework.parsers import JSONParser
+from iconolab.models import Annotation
+from .serializers import AnnotationSerializer
+
+class JSONResponse(HttpResponse):
+	def __init__(self, data, **kwargs):
+		content = JSONRenderer().render(data)
+		kwargs['content_type'] = 'application/json'
+		super(JSONResponse, self).__init__(content)
+
+
+# Create your views here.
+
+def index(r):
+	return HttpResponse('<p>You better know ... </p>')
+
+@csrf_exempt
+def annotation_list(request):
+	
+	if request.method == 'GET':
+		annotations = Annotation.objects.all()
+		serializer = AnnotationSerializer(annotations, many=True)
+		return JSONResponse(serializer.data)
+
+
+
+def get(request, pk):
+	if request.method == 'GET':
+		
+		try:
+			annotation = Annotation.objects.get(pk=pk)
+		except Annotation.DoesNotExist:
+			return HttpResponse(status=404)
+
+		serializer = AnnotationSerializer(annotation) 
+		return JSONResponse(serializer.data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/static/iconolab/js/components/cutout/index.js	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,237 @@
+
+var Snap = require('snapsvg');
+
+/* custom plugin */
+Snap.plugin(function (Snap, Element, Paper, glob) {
+	var elproto = Element.prototype;
+
+	elproto.toBack = function () {
+		this.prependTo(this.paper);
+	};
+
+	elproto.toFront = function () {
+		this.appendTo(this.paper);
+	};
+});
+
+var paper;
+var pointData = [];
+var startPoint = null;
+var drawing_path = null;
+var PATH_COLOR = "#ff00ff";
+var SELECTED_COLOR = "#ffff00";
+var FIRST_NODE_COLOR = "#FF0000";
+var HANDLE_SIZE = 6;
+var isDragged = false;
+var enablePoint = true;
+var pathIsClosed = false;
+var ENABLE_NEW_NODE = true;
+
+var getId = (function () {
+		var cpt = 0;
+		var defautPrefix = "item_"; 
+		return function (prefix) {
+			prefix = (typeof prefix === 'string') ? prefix : defautPrefix;
+			cpt = cpt + 1;
+			return prefix + cpt; 
+		}
+	}());
+
+var pathToPoint = function (path) {
+	if (typeof path === "string") { return false; }
+};
+
+//transform point to path
+var updatePath = function (paper, updateCallback) {
+	var path = "M";
+
+	if (pointData.length <= 1) {
+		return;
+	}
+
+	/*if (pathIsClosed) {
+		pointData.pop(); 
+	}*/
+
+	path += pointData[0].x + ',' + pointData[0].y;
+
+	for (var i=0; i < pointData.length; i++) {
+		if (i == 0) continue;
+
+		var pointInfos = pointData[i];
+		lPath = "L" + pointInfos.x + "," + pointInfos.y;
+		path += " " + lPath;
+	}
+	
+	path += (pathIsClosed) ? " Z": "";
+	
+	console.log("stra", updateCallback);
+
+	if (typeof updateCallback === 'function' && pathIsClosed) {
+		updateCallback();
+	}
+		
+	/* remove prev path */
+	if (drawing_path) {
+		drawing_path.remove();
+	}	
+
+	drawing_path = paper.path(path);
+
+	drawing_path.attr({
+		stroke: "red",
+		opacity: 0.6
+	});
+	
+	/* bring all handler to front */
+	pointData.map(function (point) {
+		if (point.handler) {
+			point.handler.toFront();
+		}
+	});
+
+	console.log("radical...", path);
+	console.log({isDragged: isDragged, enablePoint: enablePoint, pathIsClosed: pathIsClosed});
+};
+
+onClosePath = function () {
+	ENABLE_NEW_NODE = false;
+	/* Group path an handler - add remove_btn */
+};
+
+onClickOnHandler = function (point, p, e) {
+	//close path
+	console.log("new click handler ...");
+	if (point.isFirst && pointData.length > 2) {
+		pathIsClosed = true;
+	}
+};
+
+var updatePointPosition = function (newPoint, x, y) {
+	var index = pointData.indexOf(newPoint);
+	if (index !== -1) {
+		pointData[index].x = x;
+		pointData[index].y = y; 
+		return true;
+	} else {
+		return false;
+	}
+};
+
+var clearPreviousPath = function () {
+	drawing_path.remove();
+};
+
+var onMoveHandler = function (dx, dy, posX, posY, e) {
+	console.log("in onMoveHandler...");
+	isDragged = true;
+	/* update point then update the view */
+	var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy];
+	this.attr({ transform: transformValue});
+	var boxSize = this.getBBox();
+
+	var wasUpdated = updatePointPosition(this.data('point'), boxSize.x + (HANDLE_SIZE / 2) , boxSize.y + (HANDLE_SIZE / 2));
+	
+	if (wasUpdated) {
+		updatePath(this.paper);
+	}
+}
+
+var bindHandlerEvent = function (point, p) {
+	point.handler.click(onClickOnHandler.bind(this, point, p));
+	/* -- handler -- */
+	point.handler.hover(function () {
+		point.handler.attr({fill: 'yellow'});
+	}, function () {
+		var fillColor = point.isFirst ? FIRST_NODE_COLOR : "";
+		point.handler.attr({fill: fillColor});
+	});
+	
+	point.handler.drag(onMoveHandler, function () {
+        this.data('origTransform', this.transform().local );
+	}, function () {
+		if (!isDragged) { return true; }
+		isDragged = false;
+		enablePoint = false;
+	});
+}
+
+var createPointHandler = function (p, point) {
+
+	var handleX = point.x - HANDLE_SIZE/2;
+	var handleY = point.y - HANDLE_SIZE/2;
+
+	handler = p.rect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE);
+
+	point.handler = handler;
+	point.handler.data('point', point);
+	if (pointData.length === 0) {
+		point.isFirst = true;
+	}
+	
+	bindHandlerEvent(point, p);
+	point.handler.attr({
+		fill: (pointData.length === 0) ? FIRST_NODE_COLOR : "",
+		opacity: 0.9,
+		stroke: PATH_COLOR
+	});
+
+	return point;
+}
+
+var setPath = function () {
+
+}
+
+//create paper
+var createPoint = function (paper, x, y, pointData) {
+
+	console.log("... Inside createPoint ... ");
+	var point = {x:x, y:y, id: getId()};
+	
+	/**/
+	if (pathIsClosed) {
+		updatePath(paper, onClosePath);
+		return;
+	}
+
+	if (!enablePoint) {
+		enablePoint = true;
+		return false;
+	}
+
+	point = createPointHandler(paper, point);
+	pointData.push(point);
+	updatePath(paper);
+};
+
+
+module.exports = {
+
+	init: function(config) {
+
+		/* strange ... */
+		var mainImage = jQuery(config.imageId).find('img').eq(0);
+		var cutCanvas = jQuery(config.imageId).find('.cut-canvas').eq(0);
+		var path = jQuery(config.imageId).find('.image-path').eq(0);
+		
+		if (path.length) {
+			jQuery(cutCanvas).append(path);
+		}
+		
+		cutCanvas.css({
+			position: 'absolute', 
+			top: '0px', 
+			left: '0px', 
+			width: mainImage.width(),
+			height: mainImage.height()
+		});
+
+		paper = new Snap(cutCanvas.get(0));
+		/* show path, init the drawing module*/
+		paper.click( function(e) {
+			if (!ENABLE_NEW_NODE) { return true; }
+			createPoint(paper, e.offsetX, e.offsetY, pointData);
+		});
+	},
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/static/iconolab/js/components/egonomy-cutout/index.js	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,287 @@
+    
+var initCutout = funtion () {
+    var $ = jQuery;
+    
+    var startPath = $(".fragment-path").val();
+    
+    var PATHCOLOR = "#ff00ff",
+        SELECTEDCOLOR = "#ffff00",
+        HANDLESIZE = 6;
+    
+    var jqs = $(".cutout-canvas"),
+        offset = jqs.offset(),
+        paper = new Raphael(jqs[0]),
+        closed = false,
+        rectangleMode = false,
+        closeTimeout,
+        points = [];
+    
+    paper.rect(0, 0, paper.width, paper.height)
+        .attr({
+            stroke: "none",
+            fill: "#fff",
+            "fill-opacity": .01
+        })
+        .click(clickAddPoint)
+        .drag(
+            function(dx, dy, mx, my) {
+                
+                if (dx*dx+dy*dy < 4) {
+                    return;
+                }
+                
+                if (!pathDragging) {
+                    clearTimeout(closeTimeout);
+                    closed = true;
+                    resetPoints();
+                    for (var i = 0; i < 4; i++) {
+                        addPoint(mx - offset.left, my - offset.top)
+                    }
+                    redrawPath();
+                    pathDragging = true;
+                    rectangleMode = true;
+                }
+                
+                var x = mx - offset.left,
+                    y = my - offset.top;
+                points[1].x = points[2].x = x;
+                points[2].y = points[3].y = y;
+                redrawPath();
+            },
+            function(mx, my) {
+                pathDragging = false;
+            },
+            function() {
+                setTimeout(function() {
+                    pointDragging = false;
+                },0);
+            }
+        );
+        
+    function resetPoints() {
+    	rectangleMode = false;
+        points.forEach(function(p) {
+           p.handle.remove(); 
+        });
+        points = [];
+    }
+    
+    function addPoint(x, y) {
+        
+        var dragdeltax, dragdeltay, pointDragging,
+            point = {
+                x: Math.floor(x),
+                y: Math.floor(y)
+            }
+        
+        var pointsWithSameX = [], pointsWithSameY = [];
+        
+        var pointrect = paper.rect(0, 0, HANDLESIZE, HANDLESIZE)
+            .attr({
+                stroke: PATHCOLOR,
+                fill: PATHCOLOR,
+                "fill-opacity": .3
+            })
+            .hover(shapeMouseOver, shapeMouseOut)
+            .drag(
+                function(dx, dy) {
+                    pointDragging = true;
+                    point.x = dx + dragdeltax;
+                    point.y = dy + dragdeltay;
+                    if (rectangleMode) {
+                    	pointsWithSameX.forEach(function(p) {
+                    		p.x = point.x;
+                    	});
+                    	pointsWithSameY.forEach(function(p) {
+                    		p.y = point.y;
+                    	});
+                    }
+                    redrawPath();
+                },
+                function() {
+                    dragdeltax = point.x;
+                    dragdeltay = point.y;
+                    if (rectangleMode) {
+                    	pointsWithSameX = points.filter(function(p) {
+                    		return p !== point && p.x === point.x;
+                    	});
+                    	pointsWithSameY = points.filter(function(p) {
+                    		return p !== point && p.y === point.y;
+                    	});
+                    }
+                },
+                function() {
+                    setTimeout(function() {
+                        pointDragging = false;
+                        shapeMouseOut(pointrect);
+                    },0);
+                }
+            )
+            .click(function() {
+                if (pointDragging) {
+                    return;
+                }
+                this.remove();
+                points = points.filter(function(p) {
+                    return p != point;
+                });
+                redrawPath();
+            });
+        
+        point.handle = pointrect;
+            
+        points.push(point);
+        
+    }
+    
+    function clickAddPoint(e, mx, my) {
+        
+        if (pathDragging) {
+            return;
+        }
+        
+        if (rectangleMode) {
+        	resetPoints();
+        }
+        
+        clearTimeout(closeTimeout);
+        closed = false;
+        
+        addPoint(mx - offset.left, my - offset.top);
+        
+        redrawPath();
+        
+        closeTimeout = setTimeout(function() {
+            closed = true;
+            redrawPath();
+        }, 1000)
+
+    }
+    
+    function shapeMouseOver() {
+        points.forEach(function(point) {
+            if (point.handle !== this) {
+                point.handle.attr({
+                    stroke: PATHCOLOR,
+                    fill: PATHCOLOR
+                });
+            }
+        });
+        if (this !== path) {
+            path.attr({
+                stroke: PATHCOLOR,
+                fill: PATHCOLOR
+            });
+        }
+        this.attr({
+            stroke: SELECTEDCOLOR,
+            fill: SELECTEDCOLOR
+        });
+    }
+    
+    function shapeMouseOut() {
+        if (pathDragging || !this || !this.attr) {
+            return;
+        }
+        this.attr({
+            stroke: PATHCOLOR,
+            fill: PATHCOLOR
+        });
+    }
+    
+    function redrawPath() {
+        var d = "M"
+            + points.map(function(p) { return p.x + " " + p.y }).join("L")
+            + (closed ? "Z" : "");
+        path.attr({
+            path: d
+        });
+        points.forEach(function(point) {
+            point.handle.attr({
+                x: point.x - HANDLESIZE / 2,
+                y: point.y - HANDLESIZE / 2
+            });
+        });
+        var transd = "M"
+            + points.map(function(p) { return (p.x / paper.width).toString().replace(/(\.\d{4})\d*/,"$1") + " " + (p.y / paper.height).toString().replace(/(\.\d{4})\d*/,"$1") }).join("L")
+            + "Z";
+        $(".fragment-path").val(transd).change();
+    }
+    
+    var dragdeltax, dragdeltay, pathDragging;
+    
+    var path = paper.path()
+        .attr({
+            stroke: PATHCOLOR,
+            fill: PATHCOLOR,
+            "fill-opacity": .1
+        })
+        .click(clickAddPoint)
+        .hover(shapeMouseOver, shapeMouseOut)
+        .drag(
+            function(dx, dy) {
+                pathDragging = true;
+                points.forEach(function(point) {
+                    point.x += dx - dragdeltax;
+                    point.y += dy - dragdeltay;
+                });
+                dragdeltax = dx;
+                dragdeltay = dy;
+                redrawPath();
+            },
+            function() {
+                dragdeltax = 0;
+                dragdeltay = 0;
+            },
+            function() {
+                setTimeout(function() {
+                    pathDragging = false;
+                    shapeMouseOut(path);
+                },0);
+            }
+        );
+    
+    $(".clear-fragment").click(function() {
+        resetPoints();
+        redrawPath();
+        return false;
+    });
+    
+    function revertPath() {
+        startPath.split(/\s*[A-Z]\s*/).forEach(function(coords) {
+            xy = coords.split(/[\s,]/);
+            if (xy.length === 2) {
+                addPoint(paper.width * parseFloat(xy[0]), paper.height * parseFloat(xy[1]));
+            }
+        });
+        
+        if (points.length) {
+            closed = true;
+        }
+        
+        if (
+        	points.length === 4
+        	&& points[0].x === points[3].x
+        	&& points[0].y === points[1].y
+    		&& points[1].x === points[2].x
+            && points[2].y === points[3].y
+  		) {
+          	rectangleMode = true;
+		}
+        
+        redrawPath();
+    }
+    
+    revertPath();
+    
+    $(".reset-fragment").click(function() {
+        resetPoints();
+        revertPath();
+        return false;
+    });
+
+};
+
+module.export = {
+    initCutout: initCutout
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/static/iconolab/js/dist/bundle.js	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,18317 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false
+/******/ 		};
+
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
+
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+
+
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var cutout = __webpack_require__(1);
+
+	/* expose jQuery */
+	var test = __webpack_require__(4);
+
+	var api = {
+		initCutoutComponent: function (config) {
+			return cutout.init(config);
+		},
+		testComponent: function () {}
+	};
+
+	module.exports = api;
+
+	iconolab = api;
+
+	console.log(test);
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+	
+	var Snap = __webpack_require__(2);
+
+	/* custom plugin */
+	Snap.plugin(function (Snap, Element, Paper, glob) {
+		var elproto = Element.prototype;
+
+		elproto.toBack = function () {
+			this.prependTo(this.paper);
+		};
+
+		elproto.toFront = function () {
+			this.appendTo(this.paper);
+		};
+	});
+
+	var paper;
+	var pointData = [];
+	var startPoint = null;
+	var drawing_path = null;
+	var PATH_COLOR = "#ff00ff";
+	var SELECTED_COLOR = "#ffff00";
+	var FIRST_NODE_COLOR = "#FF0000";
+	var HANDLE_SIZE = 6;
+	var isDragged = false;
+	var enablePoint = true;
+	var pathIsClosed = false;
+	var ENABLE_NEW_NODE = true;
+
+	var getId = (function () {
+			var cpt = 0;
+			var defautPrefix = "item_"; 
+			return function (prefix) {
+				prefix = (typeof prefix === 'string') ? prefix : defautPrefix;
+				cpt = cpt + 1;
+				return prefix + cpt; 
+			}
+		}());
+
+	var pathToPoint = function (path) {
+		if (typeof path === "string") { return false; }
+	};
+
+	//transform point to path
+	var updatePath = function (paper, updateCallback) {
+		var path = "M";
+
+		if (pointData.length <= 1) {
+			return;
+		}
+
+		/*if (pathIsClosed) {
+			pointData.pop(); 
+		}*/
+
+		path += pointData[0].x + ',' + pointData[0].y;
+
+		for (var i=0; i < pointData.length; i++) {
+			if (i == 0) continue;
+
+			var pointInfos = pointData[i];
+			lPath = "L" + pointInfos.x + "," + pointInfos.y;
+			path += " " + lPath;
+		}
+		
+		path += (pathIsClosed) ? " Z": "";
+		
+		console.log("stra", updateCallback);
+
+		if (typeof updateCallback === 'function' && pathIsClosed) {
+			updateCallback();
+		}
+			
+		/* remove prev path */
+		if (drawing_path) {
+			drawing_path.remove();
+		}	
+
+		drawing_path = paper.path(path);
+
+		drawing_path.attr({
+			stroke: "red",
+			opacity: 0.6
+		});
+		
+		/* bring all handler to front */
+		pointData.map(function (point) {
+			if (point.handler) {
+				point.handler.toFront();
+			}
+		});
+
+		console.log("radical...", path);
+		console.log({isDragged: isDragged, enablePoint: enablePoint, pathIsClosed: pathIsClosed});
+	};
+
+	onClosePath = function () {
+		ENABLE_NEW_NODE = false;
+		/* Group path an handler - add remove_btn */
+	};
+
+	onClickOnHandler = function (point, p, e) {
+		//close path
+		console.log("new click handler ...");
+		if (point.isFirst && pointData.length > 2) {
+			pathIsClosed = true;
+		}
+	};
+
+	var updatePointPosition = function (newPoint, x, y) {
+		var index = pointData.indexOf(newPoint);
+		if (index !== -1) {
+			pointData[index].x = x;
+			pointData[index].y = y; 
+			return true;
+		} else {
+			return false;
+		}
+	};
+
+	var clearPreviousPath = function () {
+		drawing_path.remove();
+	};
+
+	var onMoveHandler = function (dx, dy, posX, posY, e) {
+		console.log("in onMoveHandler...");
+		isDragged = true;
+		/* update point then update the view */
+		var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy];
+		this.attr({ transform: transformValue});
+		var boxSize = this.getBBox();
+
+		var wasUpdated = updatePointPosition(this.data('point'), boxSize.x + (HANDLE_SIZE / 2) , boxSize.y + (HANDLE_SIZE / 2));
+		
+		if (wasUpdated) {
+			updatePath(this.paper);
+		}
+	}
+
+	var bindHandlerEvent = function (point, p) {
+		point.handler.click(onClickOnHandler.bind(this, point, p));
+		/* -- handler -- */
+		point.handler.hover(function () {
+			point.handler.attr({fill: 'yellow'});
+		}, function () {
+			var fillColor = point.isFirst ? FIRST_NODE_COLOR : "";
+			point.handler.attr({fill: fillColor});
+		});
+		
+		point.handler.drag(onMoveHandler, function () {
+	        this.data('origTransform', this.transform().local );
+		}, function () {
+			if (!isDragged) { return true; }
+			isDragged = false;
+			enablePoint = false;
+		});
+	}
+
+	var createPointHandler = function (p, point) {
+
+		var handleX = point.x - HANDLE_SIZE/2;
+		var handleY = point.y - HANDLE_SIZE/2;
+
+		handler = p.rect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE);
+
+		point.handler = handler;
+		point.handler.data('point', point);
+		if (pointData.length === 0) {
+			point.isFirst = true;
+		}
+		
+		bindHandlerEvent(point, p);
+		point.handler.attr({
+			fill: (pointData.length === 0) ? FIRST_NODE_COLOR : "",
+			opacity: 0.9,
+			stroke: PATH_COLOR
+		});
+
+		return point;
+	}
+
+	var setPath = function () {
+
+	}
+
+	//create paper
+	var createPoint = function (paper, x, y, pointData) {
+
+		console.log("... Inside createPoint ... ");
+		var point = {x:x, y:y, id: getId()};
+		
+		/**/
+		if (pathIsClosed) {
+			updatePath(paper, onClosePath);
+			return;
+		}
+
+		if (!enablePoint) {
+			enablePoint = true;
+			return false;
+		}
+
+		point = createPointHandler(paper, point);
+		pointData.push(point);
+		updatePath(paper);
+	};
+
+
+	module.exports = {
+
+		init: function(config) {
+
+			/* strange ... */
+			var mainImage = jQuery(config.imageId).find('img').eq(0);
+			var cutCanvas = jQuery(config.imageId).find('.cut-canvas').eq(0);
+			var path = jQuery(config.imageId).find('.image-path').eq(0);
+			
+			if (path.length) {
+				jQuery(cutCanvas).append(path);
+			}
+			
+			cutCanvas.css({
+				position: 'absolute', 
+				top: '0px', 
+				left: '0px', 
+				width: mainImage.width(),
+				height: mainImage.height()
+			});
+
+			paper = new Snap(cutCanvas.get(0));
+			/* show path, init the drawing module*/
+			paper.click( function(e) {
+				if (!ENABLE_NEW_NODE) { return true; }
+				createPoint(paper, e.offsetX, e.offsetY, pointData);
+			});
+		},
+	};
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_LOCAL_MODULE_0__;/*** IMPORTS FROM imports-loader ***/
+	(function() {
+	var fix = module.exports=0;
+
+	// Snap.svg 0.4.0
+	// 
+	// Copyright (c) 2013 – 2015 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: 2015-04-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.4.2 - JavaScript Events Library                      │ \\
+	// ├────────────────────────────────────────────────────────────┤ \\
+	// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+	// └────────────────────────────────────────────────────────────┘ \\
+
+	(function (glob) {
+	    var version = "0.4.2",
+	        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];
+	                }
+	            }
+	        },
+	    /*\
+	     * 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) {
+	            name = String(name);
+	            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 = 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.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.
+	     **
+	     > Arguments
+	     **
+	     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+	     - 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) {
+	        name = String(name);
+	        if (typeof f != "function") {
+	            return function () {};
+	        }
+	        var names = name.split(comaseparator);
+	        for (var i = 0, ii = names.length; i < ii; i++) {
+	            (function (name) {
+	                var names = 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) {
+	        if (subname) {
+	            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+	        }
+	        return current_event;
+	    };
+	    /*\
+	     * 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 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 = name.split(comaseparator);
+	        if (names.length > 1) {
+	            for (var i = 0, ii = names.length; i < ii; i++) {
+	                eve.off(names[i], f);
+	            }
+	            return;
+	        }
+	        names = name.split(separator);
+	        var e,
+	            key,
+	            splice,
+	            i, ii, j, jj,
+	            cur = [events];
+	        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]]);
+	                    }
+	                } else {
+	                    for (key in e) if (e[has](key)) {
+	                        splice.push(e[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;
+	            }
+	        }
+	    };
+	    /*\
+	     * 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.unbind(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) : ( true ? (!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_LOCAL_MODULE_0__ = (function() { return eve; }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)))) : (glob.eve = eve));
+	})(this);
+
+	(function (glob, factory) {
+	    // AMD support
+	    if (true) {
+	        // Define as an anonymous module
+	        !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__WEBPACK_LOCAL_MODULE_0__], __WEBPACK_AMD_DEFINE_RESULT__ = function (eve) {
+	            return factory(glob, eve);
+	        }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	    } 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) 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.
+	var mina = (function (eve) {
+	    var animations = {},
+	    requestAnimFrame = window.requestAnimationFrame       ||
+	                       window.webkitRequestAnimationFrame ||
+	                       window.mozRequestAnimationFrame    ||
+	                       window.oRequestAnimationFrame      ||
+	                       window.msRequestAnimationFrame     ||
+	                       function (callback) {
+	                           setTimeout(callback, 16);
+	                       },
+	    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;
+	    },
+	    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 () {
+	        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();
+	        }
+	        len && requestAnimFrame(frame);
+	    },
+	    /*\
+	     * 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 gereal 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 && requestAnimFrame(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 - 2015 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.4.0";
+	/*\
+	 * 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) {
+	            w = glob.doc.querySelector(String(w));
+	            return wrap(w);
+	        }
+	    }
+	    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\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+	    reURLValue = /^url\(#?([^)]+)\)$/,
+	    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 = {},
+	    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 `{<name>}` 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
+	 > Parameters
+	 - 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
+	 > Parameters
+	 - 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
+	 > Parameters
+	 - 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.
+	 > Parameters
+	 - 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:
+	 # <ul>
+	 #     <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li>
+	 #     <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li>
+	 #     <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li>
+	 #     <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200,&nbsp;100,&nbsp;0)</code>)</li>
+	 #     <li>rgba(•••, •••, •••, •••) — also with opacity</li>
+	 #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li>
+	 #     <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
+	 #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>)</li>
+	 #     <li>hsba(•••, •••, •••, •••) — also with opacity</li>
+	 #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+	 #     <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li>
+	 #     <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;0.5)</code>)</li>
+	 #     <li>hsla(•••, •••, •••, •••) — also with opacity</li>
+	 #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
+	 #     <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li>
+	 # </ul>
+	 * 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>" + 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 == "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 (!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 || E);
+	            }
+	            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) {
+	        query = Str(query).replace(/([^\\]):/g, "$1\\:");
+	        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 <use> instantiates. It's a part of SVG with which ordinary web developers may be least familiar.
+	    /*\
+	     * Element.use
+	     [ method ]
+	     **
+	     * Creates a `<use>` element linked to the current element
+	     **
+	     = (Element) the `<use>` 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] = 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 `<defs>` area
+	     **
+	     = (Element) the element
+	    \*/
+	    elproto.toDefs = function () {
+	        var defs = getSomeDefs(this);
+	        defs.appendChild(this.node);
+	        return this;
+	    };
+	    /*\
+	     * Element.toPattern
+	     [ method ]
+	     **
+	     * Creates a `<pattern>` 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 `<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
+	     | });
+	    \*/
+	    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 `<marker>` 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 `<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.
+	    \*/
+	    // 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;
+	    };
+	    // animation
+	    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 = easing.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 () {
+	            delete el.anims[anim.id];
+	            callback && callback.call(el);
+	        });
+	        eve.once("mina.stop." + anim.id, function () {
+	            delete el.anims[anim.id];
+	        });
+	        return el;
+	    };
+	    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 += "</" + this.type + ">");
+	            } else {
+	                type && (res += "/>");
+	            }
+	            return res;
+	        };
+	    }
+	    elproto.toDataURL = function () {
+	        if (window && window.btoa) {
+	            var bb = this.getBBox(),
+	                svg = Snap.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>', {
+	                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) 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) {
+	            var out = [[], [], []],
+	                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+	                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+	                x, y, z, res;
+
+	            if (a && a instanceof Matrix) {
+	                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+	            }
+
+	            for (x = 0; x < 3; x++) {
+	                for (y = 0; y < 3; y++) {
+	                    res = 0;
+	                    for (z = 0; z < 3; z++) {
+	                        res += m[x][z] * matrix[z][y];
+	                    }
+	                    out[x][y] = res;
+	                }
+	            }
+	            this.a = out[0][0];
+	            this.b = out[1][0];
+	            this.c = out[0][1];
+	            this.d = out[1][1];
+	            this.e = out[0][2];
+	            this.f = out[1][2];
+	            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) {
+	            return this.add(1, 0, 0, 1, x, y);
+	        };
+	        /*\
+	         * 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.add(1, 0, 0, 1, cx, cy);
+	            this.add(x, 0, 0, y, 0, 0);
+	            (cx || cy) && this.add(1, 0, 0, 1, -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.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.c], [this.b, 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.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+	                        (s.rotate ? "r" + [+s.rotate.toFixed(4), 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\(#?([^)]+)\)$/,
+	        $ = Snap._.$,
+	        URL = Snap.url,
+	        Str = String,
+	        separator = Snap._.separator,
+	        E = "";
+	    // 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();
+	            if (value.type == "clipPath") {
+	                var clip = value;
+	            } else {
+	                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;
+	        });
+	        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.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 `<path>` 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.
+	     *
+	     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
+	     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
+	     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
+	     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
+	     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
+	     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
+	     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
+	     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
+	     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
+	     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
+	     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
+	     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
+	     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
+	     * * _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 <use> 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 <symbol> 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 `<tspan>` 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
+	        function Gstops() {
+	            return this.selectAll("stop");
+	        }
+	        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);
+	            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);
+	            }
+	        }
+	        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,
+	                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);
+	            for (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;
+	            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: `<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
+	         | });
+	         = (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 <defs>.
+	        \*/
+	        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 (!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 <path> 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);
+	            }
+	        });
+	    };
+	    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;
+	    };
+	    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;
+	    };
+	    setproto.insertAfter = function (el) {
+	        var i = this.items.length;
+	        while (i--) {
+	            this.items[i].insertAfter(el);
+	        }
+	        return this;
+	    };
+	    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
+	        };
+	    };
+	    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 = 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) {
+	        t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+	        t1 = Snap.parseTransformString(t1) || [];
+	        t2 = Snap.parseTransformString(t2) || [];
+	        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]);
+	    }
+	    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(parseFloat(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 (isNumeric(a) && isNumeric(b)) {
+	            return {
+	                from: parseFloat(a),
+	                to: parseFloat(b),
+	                f: getNumber
+	            };
+	        }
+	        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 (b instanceof Snap.Matrix) {
+	                b = b.toTransformString();
+	            }
+	            if (!Snap._.rgTransform.test(b)) {
+	                b = Snap._.svgTransform2string(b);
+	            }
+	            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; }
+	            };
+	        }
+	        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.<id> on start, drag.end.<id> on end and drag.move.<id> 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.<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:
+	     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.<id>` 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 `<filter>` 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('<feGaussianBlur stdDeviation="2"/>'),
+	     |     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('\<feGaussianBlur stdDeviation="{def}"/>', {
+	            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 (typeof blur == "string") {
+	            color = blur;
+	            opacity = color;
+	            blur = 4;
+	        }
+	        if (typeof color != "string") {
+	            opacity = color;
+	            color = "#000";
+	        }
+	        color = color || "#000";
+	        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('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', {
+	            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('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>', {
+	            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('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>', {
+	            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('<feColorMatrix type="saturate" values="{amount}"/>', {
+	            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('<feColorMatrix type="hueRotate" values="{angle}"/>', {
+	            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;
+	        }
+	//        <feColorMatrix type="matrix" values="-1 0 0 0 1  0 -1 0 0 1  0 0 -1 0 1  0 0 0 1 0" color-interpolation-filters="sRGB"/>
+	        return Snap.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>', {
+	            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('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>', {
+	            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('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>', {
+	            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 `<svg>` 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 `<svg>` 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));
+	    };
+	});
+
+	return Snap;
+	}));
+	}.call(window));
+
+/***/ },
+/* 3 */,
+/* 4 */
+/***/ function(module, exports, __webpack_require__) {
+
+	/* WEBPACK VAR INJECTION */(function(global) {module.exports = global["jQuery"] = __webpack_require__(5);
+	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
+	 * jQuery JavaScript Library v2.2.4
+	 * http://jquery.com/
+	 *
+	 * Includes Sizzle.js
+	 * http://sizzlejs.com/
+	 *
+	 * Copyright jQuery Foundation and other contributors
+	 * Released under the MIT license
+	 * http://jquery.org/license
+	 *
+	 * Date: 2016-05-20T17:23Z
+	 */
+
+	(function( global, factory ) {
+
+		if ( typeof module === "object" && typeof module.exports === "object" ) {
+			// For CommonJS and CommonJS-like environments where a proper `window`
+			// is present, execute the factory and get jQuery.
+			// For environments that do not have a `window` with a `document`
+			// (such as Node.js), expose a factory as module.exports.
+			// This accentuates the need for the creation of a real `window`.
+			// e.g. var jQuery = require("jquery")(window);
+			// See ticket #14549 for more info.
+			module.exports = global.document ?
+				factory( global, true ) :
+				function( w ) {
+					if ( !w.document ) {
+						throw new Error( "jQuery requires a window with a document" );
+					}
+					return factory( w );
+				};
+		} else {
+			factory( global );
+		}
+
+	// Pass this if window is not defined yet
+	}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+	// Support: Firefox 18+
+	// Can't be in strict mode, several libs including ASP.NET trace
+	// the stack via arguments.caller.callee and Firefox dies if
+	// you try to trace through "use strict" call chains. (#13335)
+	//"use strict";
+	var arr = [];
+
+	var document = window.document;
+
+	var slice = arr.slice;
+
+	var concat = arr.concat;
+
+	var push = arr.push;
+
+	var indexOf = arr.indexOf;
+
+	var class2type = {};
+
+	var toString = class2type.toString;
+
+	var hasOwn = class2type.hasOwnProperty;
+
+	var support = {};
+
+
+
+	var
+		version = "2.2.4",
+
+		// Define a local copy of jQuery
+		jQuery = function( selector, context ) {
+
+			// The jQuery object is actually just the init constructor 'enhanced'
+			// Need init if jQuery is called (just allow error to be thrown if not included)
+			return new jQuery.fn.init( selector, context );
+		},
+
+		// Support: Android<4.1
+		// Make sure we trim BOM and NBSP
+		rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+		// Matches dashed string for camelizing
+		rmsPrefix = /^-ms-/,
+		rdashAlpha = /-([\da-z])/gi,
+
+		// Used by jQuery.camelCase as callback to replace()
+		fcamelCase = function( all, letter ) {
+			return letter.toUpperCase();
+		};
+
+	jQuery.fn = jQuery.prototype = {
+
+		// The current version of jQuery being used
+		jquery: version,
+
+		constructor: jQuery,
+
+		// Start with an empty selector
+		selector: "",
+
+		// The default length of a jQuery object is 0
+		length: 0,
+
+		toArray: function() {
+			return slice.call( this );
+		},
+
+		// Get the Nth element in the matched element set OR
+		// Get the whole matched element set as a clean array
+		get: function( num ) {
+			return num != null ?
+
+				// Return just the one element from the set
+				( num < 0 ? this[ num + this.length ] : this[ num ] ) :
+
+				// Return all the elements in a clean array
+				slice.call( this );
+		},
+
+		// Take an array of elements and push it onto the stack
+		// (returning the new matched element set)
+		pushStack: function( elems ) {
+
+			// Build a new jQuery matched element set
+			var ret = jQuery.merge( this.constructor(), elems );
+
+			// Add the old object onto the stack (as a reference)
+			ret.prevObject = this;
+			ret.context = this.context;
+
+			// Return the newly-formed element set
+			return ret;
+		},
+
+		// Execute a callback for every element in the matched set.
+		each: function( callback ) {
+			return jQuery.each( this, callback );
+		},
+
+		map: function( callback ) {
+			return this.pushStack( jQuery.map( this, function( elem, i ) {
+				return callback.call( elem, i, elem );
+			} ) );
+		},
+
+		slice: function() {
+			return this.pushStack( slice.apply( this, arguments ) );
+		},
+
+		first: function() {
+			return this.eq( 0 );
+		},
+
+		last: function() {
+			return this.eq( -1 );
+		},
+
+		eq: function( i ) {
+			var len = this.length,
+				j = +i + ( i < 0 ? len : 0 );
+			return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+		},
+
+		end: function() {
+			return this.prevObject || this.constructor();
+		},
+
+		// For internal use only.
+		// Behaves like an Array's method, not like a jQuery method.
+		push: push,
+		sort: arr.sort,
+		splice: arr.splice
+	};
+
+	jQuery.extend = jQuery.fn.extend = function() {
+		var options, name, src, copy, copyIsArray, clone,
+			target = arguments[ 0 ] || {},
+			i = 1,
+			length = arguments.length,
+			deep = false;
+
+		// Handle a deep copy situation
+		if ( typeof target === "boolean" ) {
+			deep = target;
+
+			// Skip the boolean and the target
+			target = arguments[ i ] || {};
+			i++;
+		}
+
+		// Handle case when target is a string or something (possible in deep copy)
+		if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
+			target = {};
+		}
+
+		// Extend jQuery itself if only one argument is passed
+		if ( i === length ) {
+			target = this;
+			i--;
+		}
+
+		for ( ; i < length; i++ ) {
+
+			// Only deal with non-null/undefined values
+			if ( ( options = arguments[ i ] ) != null ) {
+
+				// Extend the base object
+				for ( name in options ) {
+					src = target[ name ];
+					copy = options[ name ];
+
+					// Prevent never-ending loop
+					if ( target === copy ) {
+						continue;
+					}
+
+					// Recurse if we're merging plain objects or arrays
+					if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+						( copyIsArray = jQuery.isArray( copy ) ) ) ) {
+
+						if ( copyIsArray ) {
+							copyIsArray = false;
+							clone = src && jQuery.isArray( src ) ? src : [];
+
+						} else {
+							clone = src && jQuery.isPlainObject( src ) ? src : {};
+						}
+
+						// Never move original objects, clone them
+						target[ name ] = jQuery.extend( deep, clone, copy );
+
+					// Don't bring in undefined values
+					} else if ( copy !== undefined ) {
+						target[ name ] = copy;
+					}
+				}
+			}
+		}
+
+		// Return the modified object
+		return target;
+	};
+
+	jQuery.extend( {
+
+		// Unique for each copy of jQuery on the page
+		expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+		// Assume jQuery is ready without the ready module
+		isReady: true,
+
+		error: function( msg ) {
+			throw new Error( msg );
+		},
+
+		noop: function() {},
+
+		isFunction: function( obj ) {
+			return jQuery.type( obj ) === "function";
+		},
+
+		isArray: Array.isArray,
+
+		isWindow: function( obj ) {
+			return obj != null && obj === obj.window;
+		},
+
+		isNumeric: function( obj ) {
+
+			// parseFloat NaNs numeric-cast false positives (null|true|false|"")
+			// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+			// subtraction forces infinities to NaN
+			// adding 1 corrects loss of precision from parseFloat (#15100)
+			var realStringObj = obj && obj.toString();
+			return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
+		},
+
+		isPlainObject: function( obj ) {
+			var key;
+
+			// Not plain objects:
+			// - Any object or value whose internal [[Class]] property is not "[object Object]"
+			// - DOM nodes
+			// - window
+			if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+				return false;
+			}
+
+			// Not own constructor property must be Object
+			if ( obj.constructor &&
+					!hasOwn.call( obj, "constructor" ) &&
+					!hasOwn.call( obj.constructor.prototype || {}, "isPrototypeOf" ) ) {
+				return false;
+			}
+
+			// Own properties are enumerated firstly, so to speed up,
+			// if last one is own, then all properties are own
+			for ( key in obj ) {}
+
+			return key === undefined || hasOwn.call( obj, key );
+		},
+
+		isEmptyObject: function( obj ) {
+			var name;
+			for ( name in obj ) {
+				return false;
+			}
+			return true;
+		},
+
+		type: function( obj ) {
+			if ( obj == null ) {
+				return obj + "";
+			}
+
+			// Support: Android<4.0, iOS<6 (functionish RegExp)
+			return typeof obj === "object" || typeof obj === "function" ?
+				class2type[ toString.call( obj ) ] || "object" :
+				typeof obj;
+		},
+
+		// Evaluates a script in a global context
+		globalEval: function( code ) {
+			var script,
+				indirect = eval;
+
+			code = jQuery.trim( code );
+
+			if ( code ) {
+
+				// If the code includes a valid, prologue position
+				// strict mode pragma, execute code by injecting a
+				// script tag into the document.
+				if ( code.indexOf( "use strict" ) === 1 ) {
+					script = document.createElement( "script" );
+					script.text = code;
+					document.head.appendChild( script ).parentNode.removeChild( script );
+				} else {
+
+					// Otherwise, avoid the DOM node creation, insertion
+					// and removal by using an indirect global eval
+
+					indirect( code );
+				}
+			}
+		},
+
+		// Convert dashed to camelCase; used by the css and data modules
+		// Support: IE9-11+
+		// Microsoft forgot to hump their vendor prefix (#9572)
+		camelCase: function( string ) {
+			return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+		},
+
+		nodeName: function( elem, name ) {
+			return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+		},
+
+		each: function( obj, callback ) {
+			var length, i = 0;
+
+			if ( isArrayLike( obj ) ) {
+				length = obj.length;
+				for ( ; i < length; i++ ) {
+					if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+						break;
+					}
+				}
+			}
+
+			return obj;
+		},
+
+		// Support: Android<4.1
+		trim: function( text ) {
+			return text == null ?
+				"" :
+				( text + "" ).replace( rtrim, "" );
+		},
+
+		// results is for internal usage only
+		makeArray: function( arr, results ) {
+			var ret = results || [];
+
+			if ( arr != null ) {
+				if ( isArrayLike( Object( arr ) ) ) {
+					jQuery.merge( ret,
+						typeof arr === "string" ?
+						[ arr ] : arr
+					);
+				} else {
+					push.call( ret, arr );
+				}
+			}
+
+			return ret;
+		},
+
+		inArray: function( elem, arr, i ) {
+			return arr == null ? -1 : indexOf.call( arr, elem, i );
+		},
+
+		merge: function( first, second ) {
+			var len = +second.length,
+				j = 0,
+				i = first.length;
+
+			for ( ; j < len; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+
+			first.length = i;
+
+			return first;
+		},
+
+		grep: function( elems, callback, invert ) {
+			var callbackInverse,
+				matches = [],
+				i = 0,
+				length = elems.length,
+				callbackExpect = !invert;
+
+			// Go through the array, only saving the items
+			// that pass the validator function
+			for ( ; i < length; i++ ) {
+				callbackInverse = !callback( elems[ i ], i );
+				if ( callbackInverse !== callbackExpect ) {
+					matches.push( elems[ i ] );
+				}
+			}
+
+			return matches;
+		},
+
+		// arg is for internal usage only
+		map: function( elems, callback, arg ) {
+			var length, value,
+				i = 0,
+				ret = [];
+
+			// Go through the array, translating each of the items to their new values
+			if ( isArrayLike( elems ) ) {
+				length = elems.length;
+				for ( ; i < length; i++ ) {
+					value = callback( elems[ i ], i, arg );
+
+					if ( value != null ) {
+						ret.push( value );
+					}
+				}
+
+			// Go through every key on the object,
+			} else {
+				for ( i in elems ) {
+					value = callback( elems[ i ], i, arg );
+
+					if ( value != null ) {
+						ret.push( value );
+					}
+				}
+			}
+
+			// Flatten any nested arrays
+			return concat.apply( [], ret );
+		},
+
+		// A global GUID counter for objects
+		guid: 1,
+
+		// Bind a function to a context, optionally partially applying any
+		// arguments.
+		proxy: function( fn, context ) {
+			var tmp, args, proxy;
+
+			if ( typeof context === "string" ) {
+				tmp = fn[ context ];
+				context = fn;
+				fn = tmp;
+			}
+
+			// Quick check to determine if target is callable, in the spec
+			// this throws a TypeError, but we will just return undefined.
+			if ( !jQuery.isFunction( fn ) ) {
+				return undefined;
+			}
+
+			// Simulated bind
+			args = slice.call( arguments, 2 );
+			proxy = function() {
+				return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+			};
+
+			// Set the guid of unique handler to the same of original handler, so it can be removed
+			proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+			return proxy;
+		},
+
+		now: Date.now,
+
+		// jQuery.support is not used in Core but other projects attach their
+		// properties to it so it needs to exist.
+		support: support
+	} );
+
+	// JSHint would error on this code due to the Symbol not being defined in ES5.
+	// Defining this global in .jshintrc would create a danger of using the global
+	// unguarded in another place, it seems safer to just disable JSHint for these
+	// three lines.
+	/* jshint ignore: start */
+	if ( typeof Symbol === "function" ) {
+		jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+	}
+	/* jshint ignore: end */
+
+	// Populate the class2type map
+	jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+	function( i, name ) {
+		class2type[ "[object " + name + "]" ] = name.toLowerCase();
+	} );
+
+	function isArrayLike( obj ) {
+
+		// Support: iOS 8.2 (not reproducible in simulator)
+		// `in` check used to prevent JIT error (gh-2145)
+		// hasOwn isn't used here due to false negatives
+		// regarding Nodelist length in IE
+		var length = !!obj && "length" in obj && obj.length,
+			type = jQuery.type( obj );
+
+		if ( type === "function" || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		return type === "array" || length === 0 ||
+			typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+	}
+	var Sizzle =
+	/*!
+	 * Sizzle CSS Selector Engine v2.2.1
+	 * http://sizzlejs.com/
+	 *
+	 * Copyright jQuery Foundation and other contributors
+	 * Released under the MIT license
+	 * http://jquery.org/license
+	 *
+	 * Date: 2015-10-17
+	 */
+	(function( window ) {
+
+	var i,
+		support,
+		Expr,
+		getText,
+		isXML,
+		tokenize,
+		compile,
+		select,
+		outermostContext,
+		sortInput,
+		hasDuplicate,
+
+		// Local document vars
+		setDocument,
+		document,
+		docElem,
+		documentIsHTML,
+		rbuggyQSA,
+		rbuggyMatches,
+		matches,
+		contains,
+
+		// Instance-specific data
+		expando = "sizzle" + 1 * new Date(),
+		preferredDoc = window.document,
+		dirruns = 0,
+		done = 0,
+		classCache = createCache(),
+		tokenCache = createCache(),
+		compilerCache = createCache(),
+		sortOrder = function( a, b ) {
+			if ( a === b ) {
+				hasDuplicate = true;
+			}
+			return 0;
+		},
+
+		// General-purpose constants
+		MAX_NEGATIVE = 1 << 31,
+
+		// Instance methods
+		hasOwn = ({}).hasOwnProperty,
+		arr = [],
+		pop = arr.pop,
+		push_native = arr.push,
+		push = arr.push,
+		slice = arr.slice,
+		// Use a stripped-down indexOf as it's faster than native
+		// http://jsperf.com/thor-indexof-vs-for/5
+		indexOf = function( list, elem ) {
+			var i = 0,
+				len = list.length;
+			for ( ; i < len; i++ ) {
+				if ( list[i] === elem ) {
+					return i;
+				}
+			}
+			return -1;
+		},
+
+		booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+		// Regular expressions
+
+		// http://www.w3.org/TR/css3-selectors/#whitespace
+		whitespace = "[\\x20\\t\\r\\n\\f]",
+
+		// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+		identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+		// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+		attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+			// Operator (capture 2)
+			"*([*^$|!~]?=)" + whitespace +
+			// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+			"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+			"*\\]",
+
+		pseudos = ":(" + identifier + ")(?:\\((" +
+			// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+			// 1. quoted (capture 3; capture 4 or capture 5)
+			"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+			// 2. simple (capture 6)
+			"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+			// 3. anything else (capture 2)
+			".*" +
+			")\\)|)",
+
+		// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+		rwhitespace = new RegExp( whitespace + "+", "g" ),
+		rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+		rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+		rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+		rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
+
+		rpseudo = new RegExp( pseudos ),
+		ridentifier = new RegExp( "^" + identifier + "$" ),
+
+		matchExpr = {
+			"ID": new RegExp( "^#(" + identifier + ")" ),
+			"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+			"TAG": new RegExp( "^(" + identifier + "|[*])" ),
+			"ATTR": new RegExp( "^" + attributes ),
+			"PSEUDO": new RegExp( "^" + pseudos ),
+			"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+				"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+				"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+			"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+			// For use in libraries implementing .is()
+			// We use this for POS matching in `select`
+			"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+				whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+		},
+
+		rinputs = /^(?:input|select|textarea|button)$/i,
+		rheader = /^h\d$/i,
+
+		rnative = /^[^{]+\{\s*\[native \w/,
+
+		// Easily-parseable/retrievable ID or TAG or CLASS selectors
+		rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+		rsibling = /[+~]/,
+		rescape = /'|\\/g,
+
+		// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+		runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+		funescape = function( _, escaped, escapedWhitespace ) {
+			var high = "0x" + escaped - 0x10000;
+			// NaN means non-codepoint
+			// Support: Firefox<24
+			// Workaround erroneous numeric interpretation of +"0x"
+			return high !== high || escapedWhitespace ?
+				escaped :
+				high < 0 ?
+					// BMP codepoint
+					String.fromCharCode( high + 0x10000 ) :
+					// Supplemental Plane codepoint (surrogate pair)
+					String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+		},
+
+		// Used for iframes
+		// See setDocument()
+		// Removing the function wrapper causes a "Permission Denied"
+		// error in IE
+		unloadHandler = function() {
+			setDocument();
+		};
+
+	// Optimize for push.apply( _, NodeList )
+	try {
+		push.apply(
+			(arr = slice.call( preferredDoc.childNodes )),
+			preferredDoc.childNodes
+		);
+		// Support: Android<4.0
+		// Detect silently failing push.apply
+		arr[ preferredDoc.childNodes.length ].nodeType;
+	} catch ( e ) {
+		push = { apply: arr.length ?
+
+			// Leverage slice if possible
+			function( target, els ) {
+				push_native.apply( target, slice.call(els) );
+			} :
+
+			// Support: IE<9
+			// Otherwise append directly
+			function( target, els ) {
+				var j = target.length,
+					i = 0;
+				// Can't trust NodeList.length
+				while ( (target[j++] = els[i++]) ) {}
+				target.length = j - 1;
+			}
+		};
+	}
+
+	function Sizzle( selector, context, results, seed ) {
+		var m, i, elem, nid, nidselect, match, groups, newSelector,
+			newContext = context && context.ownerDocument,
+
+			// nodeType defaults to 9, since context defaults to document
+			nodeType = context ? context.nodeType : 9;
+
+		results = results || [];
+
+		// Return early from calls with invalid selector or context
+		if ( typeof selector !== "string" || !selector ||
+			nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+			return results;
+		}
+
+		// Try to shortcut find operations (as opposed to filters) in HTML documents
+		if ( !seed ) {
+
+			if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+				setDocument( context );
+			}
+			context = context || document;
+
+			if ( documentIsHTML ) {
+
+				// If the selector is sufficiently simple, try using a "get*By*" DOM method
+				// (excepting DocumentFragment context, where the methods don't exist)
+				if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+
+					// ID selector
+					if ( (m = match[1]) ) {
+
+						// Document context
+						if ( nodeType === 9 ) {
+							if ( (elem = context.getElementById( m )) ) {
+
+								// Support: IE, Opera, Webkit
+								// TODO: identify versions
+								// getElementById can match elements by name instead of ID
+								if ( elem.id === m ) {
+									results.push( elem );
+									return results;
+								}
+							} else {
+								return results;
+							}
+
+						// Element context
+						} else {
+
+							// Support: IE, Opera, Webkit
+							// TODO: identify versions
+							// getElementById can match elements by name instead of ID
+							if ( newContext && (elem = newContext.getElementById( m )) &&
+								contains( context, elem ) &&
+								elem.id === m ) {
+
+								results.push( elem );
+								return results;
+							}
+						}
+
+					// Type selector
+					} else if ( match[2] ) {
+						push.apply( results, context.getElementsByTagName( selector ) );
+						return results;
+
+					// Class selector
+					} else if ( (m = match[3]) && support.getElementsByClassName &&
+						context.getElementsByClassName ) {
+
+						push.apply( results, context.getElementsByClassName( m ) );
+						return results;
+					}
+				}
+
+				// Take advantage of querySelectorAll
+				if ( support.qsa &&
+					!compilerCache[ selector + " " ] &&
+					(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+
+					if ( nodeType !== 1 ) {
+						newContext = context;
+						newSelector = selector;
+
+					// qSA looks outside Element context, which is not what we want
+					// Thanks to Andrew Dupont for this workaround technique
+					// Support: IE <=8
+					// Exclude object elements
+					} else if ( context.nodeName.toLowerCase() !== "object" ) {
+
+						// Capture the context ID, setting it first if necessary
+						if ( (nid = context.getAttribute( "id" )) ) {
+							nid = nid.replace( rescape, "\\$&" );
+						} else {
+							context.setAttribute( "id", (nid = expando) );
+						}
+
+						// Prefix every selector in the list
+						groups = tokenize( selector );
+						i = groups.length;
+						nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
+						while ( i-- ) {
+							groups[i] = nidselect + " " + toSelector( groups[i] );
+						}
+						newSelector = groups.join( "," );
+
+						// Expand context for sibling selectors
+						newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+							context;
+					}
+
+					if ( newSelector ) {
+						try {
+							push.apply( results,
+								newContext.querySelectorAll( newSelector )
+							);
+							return results;
+						} catch ( qsaError ) {
+						} finally {
+							if ( nid === expando ) {
+								context.removeAttribute( "id" );
+							}
+						}
+					}
+				}
+			}
+		}
+
+		// All others
+		return select( selector.replace( rtrim, "$1" ), context, results, seed );
+	}
+
+	/**
+	 * Create key-value caches of limited size
+	 * @returns {function(string, object)} Returns the Object data after storing it on itself with
+	 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+	 *	deleting the oldest entry
+	 */
+	function createCache() {
+		var keys = [];
+
+		function cache( key, value ) {
+			// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+			if ( keys.push( key + " " ) > Expr.cacheLength ) {
+				// Only keep the most recent entries
+				delete cache[ keys.shift() ];
+			}
+			return (cache[ key + " " ] = value);
+		}
+		return cache;
+	}
+
+	/**
+	 * Mark a function for special use by Sizzle
+	 * @param {Function} fn The function to mark
+	 */
+	function markFunction( fn ) {
+		fn[ expando ] = true;
+		return fn;
+	}
+
+	/**
+	 * Support testing using an element
+	 * @param {Function} fn Passed the created div and expects a boolean result
+	 */
+	function assert( fn ) {
+		var div = document.createElement("div");
+
+		try {
+			return !!fn( div );
+		} catch (e) {
+			return false;
+		} finally {
+			// Remove from its parent by default
+			if ( div.parentNode ) {
+				div.parentNode.removeChild( div );
+			}
+			// release memory in IE
+			div = null;
+		}
+	}
+
+	/**
+	 * Adds the same handler for all of the specified attrs
+	 * @param {String} attrs Pipe-separated list of attributes
+	 * @param {Function} handler The method that will be applied
+	 */
+	function addHandle( attrs, handler ) {
+		var arr = attrs.split("|"),
+			i = arr.length;
+
+		while ( i-- ) {
+			Expr.attrHandle[ arr[i] ] = handler;
+		}
+	}
+
+	/**
+	 * Checks document order of two siblings
+	 * @param {Element} a
+	 * @param {Element} b
+	 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+	 */
+	function siblingCheck( a, b ) {
+		var cur = b && a,
+			diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+				( ~b.sourceIndex || MAX_NEGATIVE ) -
+				( ~a.sourceIndex || MAX_NEGATIVE );
+
+		// Use IE sourceIndex if available on both nodes
+		if ( diff ) {
+			return diff;
+		}
+
+		// Check if b follows a
+		if ( cur ) {
+			while ( (cur = cur.nextSibling) ) {
+				if ( cur === b ) {
+					return -1;
+				}
+			}
+		}
+
+		return a ? 1 : -1;
+	}
+
+	/**
+	 * Returns a function to use in pseudos for input types
+	 * @param {String} type
+	 */
+	function createInputPseudo( type ) {
+		return function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && elem.type === type;
+		};
+	}
+
+	/**
+	 * Returns a function to use in pseudos for buttons
+	 * @param {String} type
+	 */
+	function createButtonPseudo( type ) {
+		return function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && elem.type === type;
+		};
+	}
+
+	/**
+	 * Returns a function to use in pseudos for positionals
+	 * @param {Function} fn
+	 */
+	function createPositionalPseudo( fn ) {
+		return markFunction(function( argument ) {
+			argument = +argument;
+			return markFunction(function( seed, matches ) {
+				var j,
+					matchIndexes = fn( [], seed.length, argument ),
+					i = matchIndexes.length;
+
+				// Match elements found at the specified indexes
+				while ( i-- ) {
+					if ( seed[ (j = matchIndexes[i]) ] ) {
+						seed[j] = !(matches[j] = seed[j]);
+					}
+				}
+			});
+		});
+	}
+
+	/**
+	 * Checks a node for validity as a Sizzle context
+	 * @param {Element|Object=} context
+	 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+	 */
+	function testContext( context ) {
+		return context && typeof context.getElementsByTagName !== "undefined" && context;
+	}
+
+	// Expose support vars for convenience
+	support = Sizzle.support = {};
+
+	/**
+	 * Detects XML nodes
+	 * @param {Element|Object} elem An element or a document
+	 * @returns {Boolean} True iff elem is a non-HTML XML node
+	 */
+	isXML = Sizzle.isXML = function( elem ) {
+		// documentElement is verified for cases where it doesn't yet exist
+		// (such as loading iframes in IE - #4833)
+		var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+		return documentElement ? documentElement.nodeName !== "HTML" : false;
+	};
+
+	/**
+	 * Sets document-related variables once based on the current document
+	 * @param {Element|Object} [doc] An element or document object to use to set the document
+	 * @returns {Object} Returns the current document
+	 */
+	setDocument = Sizzle.setDocument = function( node ) {
+		var hasCompare, parent,
+			doc = node ? node.ownerDocument || node : preferredDoc;
+
+		// Return early if doc is invalid or already selected
+		if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+			return document;
+		}
+
+		// Update global variables
+		document = doc;
+		docElem = document.documentElement;
+		documentIsHTML = !isXML( document );
+
+		// Support: IE 9-11, Edge
+		// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+		if ( (parent = document.defaultView) && parent.top !== parent ) {
+			// Support: IE 11
+			if ( parent.addEventListener ) {
+				parent.addEventListener( "unload", unloadHandler, false );
+
+			// Support: IE 9 - 10 only
+			} else if ( parent.attachEvent ) {
+				parent.attachEvent( "onunload", unloadHandler );
+			}
+		}
+
+		/* Attributes
+		---------------------------------------------------------------------- */
+
+		// Support: IE<8
+		// Verify that getAttribute really returns attributes and not properties
+		// (excepting IE8 booleans)
+		support.attributes = assert(function( div ) {
+			div.className = "i";
+			return !div.getAttribute("className");
+		});
+
+		/* getElement(s)By*
+		---------------------------------------------------------------------- */
+
+		// Check if getElementsByTagName("*") returns only elements
+		support.getElementsByTagName = assert(function( div ) {
+			div.appendChild( document.createComment("") );
+			return !div.getElementsByTagName("*").length;
+		});
+
+		// Support: IE<9
+		support.getElementsByClassName = rnative.test( document.getElementsByClassName );
+
+		// Support: IE<10
+		// Check if getElementById returns elements by name
+		// The broken getElementById methods don't pick up programatically-set names,
+		// so use a roundabout getElementsByName test
+		support.getById = assert(function( div ) {
+			docElem.appendChild( div ).id = expando;
+			return !document.getElementsByName || !document.getElementsByName( expando ).length;
+		});
+
+		// ID find and filter
+		if ( support.getById ) {
+			Expr.find["ID"] = function( id, context ) {
+				if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+					var m = context.getElementById( id );
+					return m ? [ m ] : [];
+				}
+			};
+			Expr.filter["ID"] = function( id ) {
+				var attrId = id.replace( runescape, funescape );
+				return function( elem ) {
+					return elem.getAttribute("id") === attrId;
+				};
+			};
+		} else {
+			// Support: IE6/7
+			// getElementById is not reliable as a find shortcut
+			delete Expr.find["ID"];
+
+			Expr.filter["ID"] =  function( id ) {
+				var attrId = id.replace( runescape, funescape );
+				return function( elem ) {
+					var node = typeof elem.getAttributeNode !== "undefined" &&
+						elem.getAttributeNode("id");
+					return node && node.value === attrId;
+				};
+			};
+		}
+
+		// Tag
+		Expr.find["TAG"] = support.getElementsByTagName ?
+			function( tag, context ) {
+				if ( typeof context.getElementsByTagName !== "undefined" ) {
+					return context.getElementsByTagName( tag );
+
+				// DocumentFragment nodes don't have gEBTN
+				} else if ( support.qsa ) {
+					return context.querySelectorAll( tag );
+				}
+			} :
+
+			function( tag, context ) {
+				var elem,
+					tmp = [],
+					i = 0,
+					// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+					results = context.getElementsByTagName( tag );
+
+				// Filter out possible comments
+				if ( tag === "*" ) {
+					while ( (elem = results[i++]) ) {
+						if ( elem.nodeType === 1 ) {
+							tmp.push( elem );
+						}
+					}
+
+					return tmp;
+				}
+				return results;
+			};
+
+		// Class
+		Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+			if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+				return context.getElementsByClassName( className );
+			}
+		};
+
+		/* QSA/matchesSelector
+		---------------------------------------------------------------------- */
+
+		// QSA and matchesSelector support
+
+		// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+		rbuggyMatches = [];
+
+		// qSa(:focus) reports false when true (Chrome 21)
+		// We allow this because of a bug in IE8/9 that throws an error
+		// whenever `document.activeElement` is accessed on an iframe
+		// So, we allow :focus to pass through QSA all the time to avoid the IE error
+		// See http://bugs.jquery.com/ticket/13378
+		rbuggyQSA = [];
+
+		if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
+			// Build QSA regex
+			// Regex strategy adopted from Diego Perini
+			assert(function( div ) {
+				// Select is set to empty string on purpose
+				// This is to test IE's treatment of not explicitly
+				// setting a boolean content attribute,
+				// since its presence should be enough
+				// http://bugs.jquery.com/ticket/12359
+				docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" +
+					"<select id='" + expando + "-\r\\' msallowcapture=''>" +
+					"<option selected=''></option></select>";
+
+				// Support: IE8, Opera 11-12.16
+				// Nothing should be selected when empty strings follow ^= or $= or *=
+				// The test attribute must be unknown in Opera but "safe" for WinRT
+				// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+				if ( div.querySelectorAll("[msallowcapture^='']").length ) {
+					rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+				}
+
+				// Support: IE8
+				// Boolean attributes and "value" are not treated correctly
+				if ( !div.querySelectorAll("[selected]").length ) {
+					rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+				}
+
+				// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
+				if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+					rbuggyQSA.push("~=");
+				}
+
+				// Webkit/Opera - :checked should return selected option elements
+				// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+				// IE8 throws error here and will not see later tests
+				if ( !div.querySelectorAll(":checked").length ) {
+					rbuggyQSA.push(":checked");
+				}
+
+				// Support: Safari 8+, iOS 8+
+				// https://bugs.webkit.org/show_bug.cgi?id=136851
+				// In-page `selector#id sibing-combinator selector` fails
+				if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
+					rbuggyQSA.push(".#.+[+~]");
+				}
+			});
+
+			assert(function( div ) {
+				// Support: Windows 8 Native Apps
+				// The type and name attributes are restricted during .innerHTML assignment
+				var input = document.createElement("input");
+				input.setAttribute( "type", "hidden" );
+				div.appendChild( input ).setAttribute( "name", "D" );
+
+				// Support: IE8
+				// Enforce case-sensitivity of name attribute
+				if ( div.querySelectorAll("[name=d]").length ) {
+					rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+				}
+
+				// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+				// IE8 throws error here and will not see later tests
+				if ( !div.querySelectorAll(":enabled").length ) {
+					rbuggyQSA.push( ":enabled", ":disabled" );
+				}
+
+				// Opera 10-11 does not throw on post-comma invalid pseudos
+				div.querySelectorAll("*,:x");
+				rbuggyQSA.push(",.*:");
+			});
+		}
+
+		if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+			docElem.webkitMatchesSelector ||
+			docElem.mozMatchesSelector ||
+			docElem.oMatchesSelector ||
+			docElem.msMatchesSelector) )) ) {
+
+			assert(function( div ) {
+				// Check to see if it's possible to do matchesSelector
+				// on a disconnected node (IE 9)
+				support.disconnectedMatch = matches.call( div, "div" );
+
+				// This should fail with an exception
+				// Gecko does not error, returns false instead
+				matches.call( div, "[s!='']:x" );
+				rbuggyMatches.push( "!=", pseudos );
+			});
+		}
+
+		rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+		rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+		/* Contains
+		---------------------------------------------------------------------- */
+		hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+		// Element contains another
+		// Purposefully self-exclusive
+		// As in, an element does not contain itself
+		contains = hasCompare || rnative.test( docElem.contains ) ?
+			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.parentNode) ) {
+						if ( b === a ) {
+							return true;
+						}
+					}
+				}
+				return false;
+			};
+
+		/* Sorting
+		---------------------------------------------------------------------- */
+
+		// Document order sorting
+		sortOrder = hasCompare ?
+		function( a, b ) {
+
+			// Flag for duplicate removal
+			if ( a === b ) {
+				hasDuplicate = true;
+				return 0;
+			}
+
+			// Sort on method existence if only one input has compareDocumentPosition
+			var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+			if ( compare ) {
+				return compare;
+			}
+
+			// Calculate position if both inputs belong to the same document
+			compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+				a.compareDocumentPosition( b ) :
+
+				// Otherwise we know they are disconnected
+				1;
+
+			// Disconnected nodes
+			if ( compare & 1 ||
+				(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+				// Choose the first element that is related to our preferred document
+				if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+					return -1;
+				}
+				if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+					return 1;
+				}
+
+				// Maintain original order
+				return sortInput ?
+					( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+					0;
+			}
+
+			return compare & 4 ? -1 : 1;
+		} :
+		function( a, b ) {
+			// Exit early if the nodes are identical
+			if ( a === b ) {
+				hasDuplicate = true;
+				return 0;
+			}
+
+			var cur,
+				i = 0,
+				aup = a.parentNode,
+				bup = b.parentNode,
+				ap = [ a ],
+				bp = [ b ];
+
+			// Parentless nodes are either documents or disconnected
+			if ( !aup || !bup ) {
+				return a === document ? -1 :
+					b === document ? 1 :
+					aup ? -1 :
+					bup ? 1 :
+					sortInput ?
+					( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+					0;
+
+			// If the nodes are siblings, we can do a quick check
+			} else if ( aup === bup ) {
+				return siblingCheck( a, b );
+			}
+
+			// Otherwise we need full lists of their ancestors for comparison
+			cur = a;
+			while ( (cur = cur.parentNode) ) {
+				ap.unshift( cur );
+			}
+			cur = b;
+			while ( (cur = cur.parentNode) ) {
+				bp.unshift( cur );
+			}
+
+			// Walk down the tree looking for a discrepancy
+			while ( ap[i] === bp[i] ) {
+				i++;
+			}
+
+			return i ?
+				// Do a sibling check if the nodes have a common ancestor
+				siblingCheck( ap[i], bp[i] ) :
+
+				// Otherwise nodes in our document sort first
+				ap[i] === preferredDoc ? -1 :
+				bp[i] === preferredDoc ? 1 :
+				0;
+		};
+
+		return document;
+	};
+
+	Sizzle.matches = function( expr, elements ) {
+		return Sizzle( expr, null, null, elements );
+	};
+
+	Sizzle.matchesSelector = function( elem, expr ) {
+		// Set document vars if needed
+		if ( ( elem.ownerDocument || elem ) !== document ) {
+			setDocument( elem );
+		}
+
+		// Make sure that attribute selectors are quoted
+		expr = expr.replace( rattributeQuotes, "='$1']" );
+
+		if ( support.matchesSelector && documentIsHTML &&
+			!compilerCache[ expr + " " ] &&
+			( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+			( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
+
+			try {
+				var ret = matches.call( elem, expr );
+
+				// IE 9's matchesSelector returns false on disconnected nodes
+				if ( ret || support.disconnectedMatch ||
+						// As well, disconnected nodes are said to be in a document
+						// fragment in IE 9
+						elem.document && elem.document.nodeType !== 11 ) {
+					return ret;
+				}
+			} catch (e) {}
+		}
+
+		return Sizzle( expr, document, null, [ elem ] ).length > 0;
+	};
+
+	Sizzle.contains = function( context, elem ) {
+		// Set document vars if needed
+		if ( ( context.ownerDocument || context ) !== document ) {
+			setDocument( context );
+		}
+		return contains( context, elem );
+	};
+
+	Sizzle.attr = function( elem, name ) {
+		// Set document vars if needed
+		if ( ( elem.ownerDocument || elem ) !== document ) {
+			setDocument( elem );
+		}
+
+		var fn = Expr.attrHandle[ name.toLowerCase() ],
+			// Don't get fooled by Object.prototype properties (jQuery #13807)
+			val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+				fn( elem, name, !documentIsHTML ) :
+				undefined;
+
+		return val !== undefined ?
+			val :
+			support.attributes || !documentIsHTML ?
+				elem.getAttribute( name ) :
+				(val = elem.getAttributeNode(name)) && val.specified ?
+					val.value :
+					null;
+	};
+
+	Sizzle.error = function( msg ) {
+		throw new Error( "Syntax error, unrecognized expression: " + msg );
+	};
+
+	/**
+	 * Document sorting and removing duplicates
+	 * @param {ArrayLike} results
+	 */
+	Sizzle.uniqueSort = function( results ) {
+		var elem,
+			duplicates = [],
+			j = 0,
+			i = 0;
+
+		// Unless we *know* we can detect duplicates, assume their presence
+		hasDuplicate = !support.detectDuplicates;
+		sortInput = !support.sortStable && results.slice( 0 );
+		results.sort( sortOrder );
+
+		if ( hasDuplicate ) {
+			while ( (elem = results[i++]) ) {
+				if ( elem === results[ i ] ) {
+					j = duplicates.push( i );
+				}
+			}
+			while ( j-- ) {
+				results.splice( duplicates[ j ], 1 );
+			}
+		}
+
+		// Clear input after sorting to release objects
+		// See https://github.com/jquery/sizzle/pull/225
+		sortInput = null;
+
+		return results;
+	};
+
+	/**
+	 * Utility function for retrieving the text value of an array of DOM nodes
+	 * @param {Array|Element} elem
+	 */
+	getText = Sizzle.getText = function( elem ) {
+		var node,
+			ret = "",
+			i = 0,
+			nodeType = elem.nodeType;
+
+		if ( !nodeType ) {
+			// If no nodeType, this is expected to be an array
+			while ( (node = elem[i++]) ) {
+				// Do not traverse comment nodes
+				ret += getText( node );
+			}
+		} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+			// Use textContent for elements
+			// innerText usage removed for consistency of new lines (jQuery #11153)
+			if ( typeof elem.textContent === "string" ) {
+				return elem.textContent;
+			} else {
+				// Traverse its children
+				for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+					ret += getText( elem );
+				}
+			}
+		} else if ( nodeType === 3 || nodeType === 4 ) {
+			return elem.nodeValue;
+		}
+		// Do not include comment or processing instruction nodes
+
+		return ret;
+	};
+
+	Expr = Sizzle.selectors = {
+
+		// Can be adjusted by the user
+		cacheLength: 50,
+
+		createPseudo: markFunction,
+
+		match: matchExpr,
+
+		attrHandle: {},
+
+		find: {},
+
+		relative: {
+			">": { dir: "parentNode", first: true },
+			" ": { dir: "parentNode" },
+			"+": { dir: "previousSibling", first: true },
+			"~": { dir: "previousSibling" }
+		},
+
+		preFilter: {
+			"ATTR": function( match ) {
+				match[1] = match[1].replace( runescape, funescape );
+
+				// Move the given value to match[3] whether quoted or unquoted
+				match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+				if ( match[2] === "~=" ) {
+					match[3] = " " + match[3] + " ";
+				}
+
+				return match.slice( 0, 4 );
+			},
+
+			"CHILD": function( match ) {
+				/* matches from matchExpr["CHILD"]
+					1 type (only|nth|...)
+					2 what (child|of-type)
+					3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+					4 xn-component of xn+y argument ([+-]?\d*n|)
+					5 sign of xn-component
+					6 x of xn-component
+					7 sign of y-component
+					8 y of y-component
+				*/
+				match[1] = match[1].toLowerCase();
+
+				if ( match[1].slice( 0, 3 ) === "nth" ) {
+					// nth-* requires argument
+					if ( !match[3] ) {
+						Sizzle.error( match[0] );
+					}
+
+					// numeric x and y parameters for Expr.filter.CHILD
+					// remember that false/true cast respectively to 0/1
+					match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+					match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+				// other types prohibit arguments
+				} else if ( match[3] ) {
+					Sizzle.error( match[0] );
+				}
+
+				return match;
+			},
+
+			"PSEUDO": function( match ) {
+				var excess,
+					unquoted = !match[6] && match[2];
+
+				if ( matchExpr["CHILD"].test( match[0] ) ) {
+					return null;
+				}
+
+				// Accept quoted arguments as-is
+				if ( match[3] ) {
+					match[2] = match[4] || match[5] || "";
+
+				// Strip excess characters from unquoted arguments
+				} else if ( unquoted && rpseudo.test( unquoted ) &&
+					// Get excess from tokenize (recursively)
+					(excess = tokenize( unquoted, true )) &&
+					// advance to the next closing parenthesis
+					(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+					// excess is a negative index
+					match[0] = match[0].slice( 0, excess );
+					match[2] = unquoted.slice( 0, excess );
+				}
+
+				// Return only captures needed by the pseudo filter method (type and argument)
+				return match.slice( 0, 3 );
+			}
+		},
+
+		filter: {
+
+			"TAG": function( nodeNameSelector ) {
+				var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+				return nodeNameSelector === "*" ?
+					function() { return true; } :
+					function( elem ) {
+						return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+					};
+			},
+
+			"CLASS": function( className ) {
+				var pattern = classCache[ className + " " ];
+
+				return pattern ||
+					(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+					classCache( className, function( elem ) {
+						return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
+					});
+			},
+
+			"ATTR": function( name, operator, check ) {
+				return function( elem ) {
+					var result = Sizzle.attr( elem, name );
+
+					if ( result == null ) {
+						return operator === "!=";
+					}
+					if ( !operator ) {
+						return true;
+					}
+
+					result += "";
+
+					return operator === "=" ? result === check :
+						operator === "!=" ? result !== check :
+						operator === "^=" ? check && result.indexOf( check ) === 0 :
+						operator === "*=" ? check && result.indexOf( check ) > -1 :
+						operator === "$=" ? check && result.slice( -check.length ) === check :
+						operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+						operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+						false;
+				};
+			},
+
+			"CHILD": function( type, what, argument, first, last ) {
+				var simple = type.slice( 0, 3 ) !== "nth",
+					forward = type.slice( -4 ) !== "last",
+					ofType = what === "of-type";
+
+				return first === 1 && last === 0 ?
+
+					// Shortcut for :nth-*(n)
+					function( elem ) {
+						return !!elem.parentNode;
+					} :
+
+					function( elem, context, xml ) {
+						var cache, uniqueCache, outerCache, node, nodeIndex, start,
+							dir = simple !== forward ? "nextSibling" : "previousSibling",
+							parent = elem.parentNode,
+							name = ofType && elem.nodeName.toLowerCase(),
+							useCache = !xml && !ofType,
+							diff = false;
+
+						if ( parent ) {
+
+							// :(first|last|only)-(child|of-type)
+							if ( simple ) {
+								while ( dir ) {
+									node = elem;
+									while ( (node = node[ dir ]) ) {
+										if ( ofType ?
+											node.nodeName.toLowerCase() === name :
+											node.nodeType === 1 ) {
+
+											return false;
+										}
+									}
+									// Reverse direction for :only-* (if we haven't yet done so)
+									start = dir = type === "only" && !start && "nextSibling";
+								}
+								return true;
+							}
+
+							start = [ forward ? parent.firstChild : parent.lastChild ];
+
+							// non-xml :nth-child(...) stores cache data on `parent`
+							if ( forward && useCache ) {
+
+								// Seek `elem` from a previously-cached index
+
+								// ...in a gzip-friendly way
+								node = parent;
+								outerCache = node[ expando ] || (node[ expando ] = {});
+
+								// Support: IE <9 only
+								// Defend against cloned attroperties (jQuery gh-1709)
+								uniqueCache = outerCache[ node.uniqueID ] ||
+									(outerCache[ node.uniqueID ] = {});
+
+								cache = uniqueCache[ type ] || [];
+								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+								diff = nodeIndex && cache[ 2 ];
+								node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+								while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+									// Fallback to seeking `elem` from the start
+									(diff = nodeIndex = 0) || start.pop()) ) {
+
+									// When found, cache indexes on `parent` and break
+									if ( node.nodeType === 1 && ++diff && node === elem ) {
+										uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
+										break;
+									}
+								}
+
+							} else {
+								// Use previously-cached element index if available
+								if ( useCache ) {
+									// ...in a gzip-friendly way
+									node = elem;
+									outerCache = node[ expando ] || (node[ expando ] = {});
+
+									// Support: IE <9 only
+									// Defend against cloned attroperties (jQuery gh-1709)
+									uniqueCache = outerCache[ node.uniqueID ] ||
+										(outerCache[ node.uniqueID ] = {});
+
+									cache = uniqueCache[ type ] || [];
+									nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+									diff = nodeIndex;
+								}
+
+								// xml :nth-child(...)
+								// or :nth-last-child(...) or :nth(-last)?-of-type(...)
+								if ( diff === false ) {
+									// Use the same loop as above to seek `elem` from the start
+									while ( (node = ++nodeIndex && node && node[ dir ] ||
+										(diff = nodeIndex = 0) || start.pop()) ) {
+
+										if ( ( ofType ?
+											node.nodeName.toLowerCase() === name :
+											node.nodeType === 1 ) &&
+											++diff ) {
+
+											// Cache the index of each encountered element
+											if ( useCache ) {
+												outerCache = node[ expando ] || (node[ expando ] = {});
+
+												// Support: IE <9 only
+												// Defend against cloned attroperties (jQuery gh-1709)
+												uniqueCache = outerCache[ node.uniqueID ] ||
+													(outerCache[ node.uniqueID ] = {});
+
+												uniqueCache[ type ] = [ dirruns, diff ];
+											}
+
+											if ( node === elem ) {
+												break;
+											}
+										}
+									}
+								}
+							}
+
+							// Incorporate the offset, then check against cycle size
+							diff -= last;
+							return diff === first || ( diff % first === 0 && diff / first >= 0 );
+						}
+					};
+			},
+
+			"PSEUDO": function( pseudo, argument ) {
+				// pseudo-class names are case-insensitive
+				// http://www.w3.org/TR/selectors/#pseudo-classes
+				// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+				// Remember that setFilters inherits from pseudos
+				var args,
+					fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+						Sizzle.error( "unsupported pseudo: " + pseudo );
+
+				// The user may use createPseudo to indicate that
+				// arguments are needed to create the filter function
+				// just as Sizzle does
+				if ( fn[ expando ] ) {
+					return fn( argument );
+				}
+
+				// But maintain support for old signatures
+				if ( fn.length > 1 ) {
+					args = [ pseudo, pseudo, "", argument ];
+					return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+						markFunction(function( seed, matches ) {
+							var idx,
+								matched = fn( seed, argument ),
+								i = matched.length;
+							while ( i-- ) {
+								idx = indexOf( seed, matched[i] );
+								seed[ idx ] = !( matches[ idx ] = matched[i] );
+							}
+						}) :
+						function( elem ) {
+							return fn( elem, 0, args );
+						};
+				}
+
+				return fn;
+			}
+		},
+
+		pseudos: {
+			// Potentially complex pseudos
+			"not": markFunction(function( selector ) {
+				// Trim the selector passed to compile
+				// to avoid treating leading and trailing
+				// spaces as combinators
+				var input = [],
+					results = [],
+					matcher = compile( selector.replace( rtrim, "$1" ) );
+
+				return matcher[ expando ] ?
+					markFunction(function( seed, matches, context, xml ) {
+						var elem,
+							unmatched = matcher( seed, null, xml, [] ),
+							i = seed.length;
+
+						// Match elements unmatched by `matcher`
+						while ( i-- ) {
+							if ( (elem = unmatched[i]) ) {
+								seed[i] = !(matches[i] = elem);
+							}
+						}
+					}) :
+					function( elem, context, xml ) {
+						input[0] = elem;
+						matcher( input, null, xml, results );
+						// Don't keep the element (issue #299)
+						input[0] = null;
+						return !results.pop();
+					};
+			}),
+
+			"has": markFunction(function( selector ) {
+				return function( elem ) {
+					return Sizzle( selector, elem ).length > 0;
+				};
+			}),
+
+			"contains": markFunction(function( text ) {
+				text = text.replace( runescape, funescape );
+				return function( elem ) {
+					return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+				};
+			}),
+
+			// "Whether an element is represented by a :lang() selector
+			// is based solely on the element's language value
+			// being equal to the identifier C,
+			// or beginning with the identifier C immediately followed by "-".
+			// The matching of C against the element's language value is performed case-insensitively.
+			// The identifier C does not have to be a valid language name."
+			// http://www.w3.org/TR/selectors/#lang-pseudo
+			"lang": markFunction( function( lang ) {
+				// lang value must be a valid identifier
+				if ( !ridentifier.test(lang || "") ) {
+					Sizzle.error( "unsupported lang: " + lang );
+				}
+				lang = lang.replace( runescape, funescape ).toLowerCase();
+				return function( elem ) {
+					var elemLang;
+					do {
+						if ( (elemLang = documentIsHTML ?
+							elem.lang :
+							elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+							elemLang = elemLang.toLowerCase();
+							return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+						}
+					} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+					return false;
+				};
+			}),
+
+			// Miscellaneous
+			"target": function( elem ) {
+				var hash = window.location && window.location.hash;
+				return hash && hash.slice( 1 ) === elem.id;
+			},
+
+			"root": function( elem ) {
+				return elem === docElem;
+			},
+
+			"focus": function( elem ) {
+				return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+			},
+
+			// Boolean properties
+			"enabled": function( elem ) {
+				return elem.disabled === false;
+			},
+
+			"disabled": function( elem ) {
+				return elem.disabled === true;
+			},
+
+			"checked": function( elem ) {
+				// In CSS3, :checked should return both checked and selected elements
+				// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+				var nodeName = elem.nodeName.toLowerCase();
+				return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+			},
+
+			"selected": function( elem ) {
+				// Accessing this property makes selected-by-default
+				// options in Safari work properly
+				if ( elem.parentNode ) {
+					elem.parentNode.selectedIndex;
+				}
+
+				return elem.selected === true;
+			},
+
+			// Contents
+			"empty": function( elem ) {
+				// http://www.w3.org/TR/selectors/#empty-pseudo
+				// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+				//   but not by others (comment: 8; processing instruction: 7; etc.)
+				// nodeType < 6 works because attributes (2) do not appear as children
+				for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+					if ( elem.nodeType < 6 ) {
+						return false;
+					}
+				}
+				return true;
+			},
+
+			"parent": function( elem ) {
+				return !Expr.pseudos["empty"]( elem );
+			},
+
+			// Element/input types
+			"header": function( elem ) {
+				return rheader.test( elem.nodeName );
+			},
+
+			"input": function( elem ) {
+				return rinputs.test( elem.nodeName );
+			},
+
+			"button": function( elem ) {
+				var name = elem.nodeName.toLowerCase();
+				return name === "input" && elem.type === "button" || name === "button";
+			},
+
+			"text": function( elem ) {
+				var attr;
+				return elem.nodeName.toLowerCase() === "input" &&
+					elem.type === "text" &&
+
+					// Support: IE<8
+					// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+					( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+			},
+
+			// Position-in-collection
+			"first": createPositionalPseudo(function() {
+				return [ 0 ];
+			}),
+
+			"last": createPositionalPseudo(function( matchIndexes, length ) {
+				return [ length - 1 ];
+			}),
+
+			"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+				return [ argument < 0 ? argument + length : argument ];
+			}),
+
+			"even": createPositionalPseudo(function( matchIndexes, length ) {
+				var i = 0;
+				for ( ; i < length; i += 2 ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			}),
+
+			"odd": createPositionalPseudo(function( matchIndexes, length ) {
+				var i = 1;
+				for ( ; i < length; i += 2 ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			}),
+
+			"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+				var i = argument < 0 ? argument + length : argument;
+				for ( ; --i >= 0; ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			}),
+
+			"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+				var i = argument < 0 ? argument + length : argument;
+				for ( ; ++i < length; ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			})
+		}
+	};
+
+	Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+	// Add button/input type pseudos
+	for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+		Expr.pseudos[ i ] = createInputPseudo( i );
+	}
+	for ( i in { submit: true, reset: true } ) {
+		Expr.pseudos[ i ] = createButtonPseudo( i );
+	}
+
+	// Easy API for creating new setFilters
+	function setFilters() {}
+	setFilters.prototype = Expr.filters = Expr.pseudos;
+	Expr.setFilters = new setFilters();
+
+	tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+		var matched, match, tokens, type,
+			soFar, groups, preFilters,
+			cached = tokenCache[ selector + " " ];
+
+		if ( cached ) {
+			return parseOnly ? 0 : cached.slice( 0 );
+		}
+
+		soFar = selector;
+		groups = [];
+		preFilters = Expr.preFilter;
+
+		while ( soFar ) {
+
+			// Comma and first run
+			if ( !matched || (match = rcomma.exec( soFar )) ) {
+				if ( match ) {
+					// Don't consume trailing commas as valid
+					soFar = soFar.slice( match[0].length ) || soFar;
+				}
+				groups.push( (tokens = []) );
+			}
+
+			matched = false;
+
+			// Combinators
+			if ( (match = rcombinators.exec( soFar )) ) {
+				matched = match.shift();
+				tokens.push({
+					value: matched,
+					// Cast descendant combinators to space
+					type: match[0].replace( rtrim, " " )
+				});
+				soFar = soFar.slice( matched.length );
+			}
+
+			// Filters
+			for ( type in Expr.filter ) {
+				if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+					(match = preFilters[ type ]( match ))) ) {
+					matched = match.shift();
+					tokens.push({
+						value: matched,
+						type: type,
+						matches: match
+					});
+					soFar = soFar.slice( matched.length );
+				}
+			}
+
+			if ( !matched ) {
+				break;
+			}
+		}
+
+		// Return the length of the invalid excess
+		// if we're just parsing
+		// Otherwise, throw an error or return tokens
+		return parseOnly ?
+			soFar.length :
+			soFar ?
+				Sizzle.error( selector ) :
+				// Cache the tokens
+				tokenCache( selector, groups ).slice( 0 );
+	};
+
+	function toSelector( tokens ) {
+		var i = 0,
+			len = tokens.length,
+			selector = "";
+		for ( ; i < len; i++ ) {
+			selector += tokens[i].value;
+		}
+		return selector;
+	}
+
+	function addCombinator( matcher, combinator, base ) {
+		var dir = combinator.dir,
+			checkNonElements = base && dir === "parentNode",
+			doneName = done++;
+
+		return combinator.first ?
+			// Check against closest ancestor/preceding element
+			function( elem, context, xml ) {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						return matcher( elem, context, xml );
+					}
+				}
+			} :
+
+			// Check against all ancestor/preceding elements
+			function( elem, context, xml ) {
+				var oldCache, uniqueCache, outerCache,
+					newCache = [ dirruns, doneName ];
+
+				// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+				if ( xml ) {
+					while ( (elem = elem[ dir ]) ) {
+						if ( elem.nodeType === 1 || checkNonElements ) {
+							if ( matcher( elem, context, xml ) ) {
+								return true;
+							}
+						}
+					}
+				} else {
+					while ( (elem = elem[ dir ]) ) {
+						if ( elem.nodeType === 1 || checkNonElements ) {
+							outerCache = elem[ expando ] || (elem[ expando ] = {});
+
+							// Support: IE <9 only
+							// Defend against cloned attroperties (jQuery gh-1709)
+							uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
+
+							if ( (oldCache = uniqueCache[ dir ]) &&
+								oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+								// Assign to newCache so results back-propagate to previous elements
+								return (newCache[ 2 ] = oldCache[ 2 ]);
+							} else {
+								// Reuse newcache so results back-propagate to previous elements
+								uniqueCache[ dir ] = newCache;
+
+								// A match means we're done; a fail means we have to keep checking
+								if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+									return true;
+								}
+							}
+						}
+					}
+				}
+			};
+	}
+
+	function elementMatcher( matchers ) {
+		return matchers.length > 1 ?
+			function( elem, context, xml ) {
+				var i = matchers.length;
+				while ( i-- ) {
+					if ( !matchers[i]( elem, context, xml ) ) {
+						return false;
+					}
+				}
+				return true;
+			} :
+			matchers[0];
+	}
+
+	function multipleContexts( selector, contexts, results ) {
+		var i = 0,
+			len = contexts.length;
+		for ( ; i < len; i++ ) {
+			Sizzle( selector, contexts[i], results );
+		}
+		return results;
+	}
+
+	function condense( unmatched, map, filter, context, xml ) {
+		var elem,
+			newUnmatched = [],
+			i = 0,
+			len = unmatched.length,
+			mapped = map != null;
+
+		for ( ; i < len; i++ ) {
+			if ( (elem = unmatched[i]) ) {
+				if ( !filter || filter( elem, context, xml ) ) {
+					newUnmatched.push( elem );
+					if ( mapped ) {
+						map.push( i );
+					}
+				}
+			}
+		}
+
+		return newUnmatched;
+	}
+
+	function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+		if ( postFilter && !postFilter[ expando ] ) {
+			postFilter = setMatcher( postFilter );
+		}
+		if ( postFinder && !postFinder[ expando ] ) {
+			postFinder = setMatcher( postFinder, postSelector );
+		}
+		return markFunction(function( seed, results, context, xml ) {
+			var temp, i, elem,
+				preMap = [],
+				postMap = [],
+				preexisting = results.length,
+
+				// Get initial elements from seed or context
+				elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+				// Prefilter to get matcher input, preserving a map for seed-results synchronization
+				matcherIn = preFilter && ( seed || !selector ) ?
+					condense( elems, preMap, preFilter, context, xml ) :
+					elems,
+
+				matcherOut = matcher ?
+					// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+					postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+						// ...intermediate processing is necessary
+						[] :
+
+						// ...otherwise use results directly
+						results :
+					matcherIn;
+
+			// Find primary matches
+			if ( matcher ) {
+				matcher( matcherIn, matcherOut, context, xml );
+			}
+
+			// Apply postFilter
+			if ( postFilter ) {
+				temp = condense( matcherOut, postMap );
+				postFilter( temp, [], context, xml );
+
+				// Un-match failing elements by moving them back to matcherIn
+				i = temp.length;
+				while ( i-- ) {
+					if ( (elem = temp[i]) ) {
+						matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+					}
+				}
+			}
+
+			if ( seed ) {
+				if ( postFinder || preFilter ) {
+					if ( postFinder ) {
+						// Get the final matcherOut by condensing this intermediate into postFinder contexts
+						temp = [];
+						i = matcherOut.length;
+						while ( i-- ) {
+							if ( (elem = matcherOut[i]) ) {
+								// Restore matcherIn since elem is not yet a final match
+								temp.push( (matcherIn[i] = elem) );
+							}
+						}
+						postFinder( null, (matcherOut = []), temp, xml );
+					}
+
+					// Move matched elements from seed to results to keep them synchronized
+					i = matcherOut.length;
+					while ( i-- ) {
+						if ( (elem = matcherOut[i]) &&
+							(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
+
+							seed[temp] = !(results[temp] = elem);
+						}
+					}
+				}
+
+			// Add elements to results, through postFinder if defined
+			} else {
+				matcherOut = condense(
+					matcherOut === results ?
+						matcherOut.splice( preexisting, matcherOut.length ) :
+						matcherOut
+				);
+				if ( postFinder ) {
+					postFinder( null, results, matcherOut, xml );
+				} else {
+					push.apply( results, matcherOut );
+				}
+			}
+		});
+	}
+
+	function matcherFromTokens( tokens ) {
+		var checkContext, matcher, j,
+			len = tokens.length,
+			leadingRelative = Expr.relative[ tokens[0].type ],
+			implicitRelative = leadingRelative || Expr.relative[" "],
+			i = leadingRelative ? 1 : 0,
+
+			// The foundational matcher ensures that elements are reachable from top-level context(s)
+			matchContext = addCombinator( function( elem ) {
+				return elem === checkContext;
+			}, implicitRelative, true ),
+			matchAnyContext = addCombinator( function( elem ) {
+				return indexOf( checkContext, elem ) > -1;
+			}, implicitRelative, true ),
+			matchers = [ function( elem, context, xml ) {
+				var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+					(checkContext = context).nodeType ?
+						matchContext( elem, context, xml ) :
+						matchAnyContext( elem, context, xml ) );
+				// Avoid hanging onto element (issue #299)
+				checkContext = null;
+				return ret;
+			} ];
+
+		for ( ; i < len; i++ ) {
+			if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+				matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+			} else {
+				matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+				// Return special upon seeing a positional matcher
+				if ( matcher[ expando ] ) {
+					// Find the next relative operator (if any) for proper handling
+					j = ++i;
+					for ( ; j < len; j++ ) {
+						if ( Expr.relative[ tokens[j].type ] ) {
+							break;
+						}
+					}
+					return setMatcher(
+						i > 1 && elementMatcher( matchers ),
+						i > 1 && toSelector(
+							// If the preceding token was a descendant combinator, insert an implicit any-element `*`
+							tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+						).replace( rtrim, "$1" ),
+						matcher,
+						i < j && matcherFromTokens( tokens.slice( i, j ) ),
+						j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+						j < len && toSelector( tokens )
+					);
+				}
+				matchers.push( matcher );
+			}
+		}
+
+		return elementMatcher( matchers );
+	}
+
+	function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+		var bySet = setMatchers.length > 0,
+			byElement = elementMatchers.length > 0,
+			superMatcher = function( seed, context, xml, results, outermost ) {
+				var elem, j, matcher,
+					matchedCount = 0,
+					i = "0",
+					unmatched = seed && [],
+					setMatched = [],
+					contextBackup = outermostContext,
+					// We must always have either seed elements or outermost context
+					elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+					// Use integer dirruns iff this is the outermost matcher
+					dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+					len = elems.length;
+
+				if ( outermost ) {
+					outermostContext = context === document || context || outermost;
+				}
+
+				// Add elements passing elementMatchers directly to results
+				// Support: IE<9, Safari
+				// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
+				for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+					if ( byElement && elem ) {
+						j = 0;
+						if ( !context && elem.ownerDocument !== document ) {
+							setDocument( elem );
+							xml = !documentIsHTML;
+						}
+						while ( (matcher = elementMatchers[j++]) ) {
+							if ( matcher( elem, context || document, xml) ) {
+								results.push( elem );
+								break;
+							}
+						}
+						if ( outermost ) {
+							dirruns = dirrunsUnique;
+						}
+					}
+
+					// Track unmatched elements for set filters
+					if ( bySet ) {
+						// They will have gone through all possible matchers
+						if ( (elem = !matcher && elem) ) {
+							matchedCount--;
+						}
+
+						// Lengthen the array for every element, matched or not
+						if ( seed ) {
+							unmatched.push( elem );
+						}
+					}
+				}
+
+				// `i` is now the count of elements visited above, and adding it to `matchedCount`
+				// makes the latter nonnegative.
+				matchedCount += i;
+
+				// Apply set filters to unmatched elements
+				// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+				// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+				// no element matchers and no seed.
+				// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+				// case, which will result in a "00" `matchedCount` that differs from `i` but is also
+				// numerically zero.
+				if ( bySet && i !== matchedCount ) {
+					j = 0;
+					while ( (matcher = setMatchers[j++]) ) {
+						matcher( unmatched, setMatched, context, xml );
+					}
+
+					if ( seed ) {
+						// Reintegrate element matches to eliminate the need for sorting
+						if ( matchedCount > 0 ) {
+							while ( i-- ) {
+								if ( !(unmatched[i] || setMatched[i]) ) {
+									setMatched[i] = pop.call( results );
+								}
+							}
+						}
+
+						// Discard index placeholder values to get only actual matches
+						setMatched = condense( setMatched );
+					}
+
+					// Add matches to results
+					push.apply( results, setMatched );
+
+					// Seedless set matches succeeding multiple successful matchers stipulate sorting
+					if ( outermost && !seed && setMatched.length > 0 &&
+						( matchedCount + setMatchers.length ) > 1 ) {
+
+						Sizzle.uniqueSort( results );
+					}
+				}
+
+				// Override manipulation of globals by nested matchers
+				if ( outermost ) {
+					dirruns = dirrunsUnique;
+					outermostContext = contextBackup;
+				}
+
+				return unmatched;
+			};
+
+		return bySet ?
+			markFunction( superMatcher ) :
+			superMatcher;
+	}
+
+	compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+		var i,
+			setMatchers = [],
+			elementMatchers = [],
+			cached = compilerCache[ selector + " " ];
+
+		if ( !cached ) {
+			// Generate a function of recursive functions that can be used to check each element
+			if ( !match ) {
+				match = tokenize( selector );
+			}
+			i = match.length;
+			while ( i-- ) {
+				cached = matcherFromTokens( match[i] );
+				if ( cached[ expando ] ) {
+					setMatchers.push( cached );
+				} else {
+					elementMatchers.push( cached );
+				}
+			}
+
+			// Cache the compiled function
+			cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+			// Save selector and tokenization
+			cached.selector = selector;
+		}
+		return cached;
+	};
+
+	/**
+	 * A low-level selection function that works with Sizzle's compiled
+	 *  selector functions
+	 * @param {String|Function} selector A selector or a pre-compiled
+	 *  selector function built with Sizzle.compile
+	 * @param {Element} context
+	 * @param {Array} [results]
+	 * @param {Array} [seed] A set of elements to match against
+	 */
+	select = Sizzle.select = function( selector, context, results, seed ) {
+		var i, tokens, token, type, find,
+			compiled = typeof selector === "function" && selector,
+			match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+		results = results || [];
+
+		// Try to minimize operations if there is only one selector in the list and no seed
+		// (the latter of which guarantees us context)
+		if ( match.length === 1 ) {
+
+			// Reduce context if the leading compound selector is an ID
+			tokens = match[0] = match[0].slice( 0 );
+			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+					support.getById && context.nodeType === 9 && documentIsHTML &&
+					Expr.relative[ tokens[1].type ] ) {
+
+				context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+				if ( !context ) {
+					return results;
+
+				// Precompiled matchers will still verify ancestry, so step up a level
+				} else if ( compiled ) {
+					context = context.parentNode;
+				}
+
+				selector = selector.slice( tokens.shift().value.length );
+			}
+
+			// Fetch a seed set for right-to-left matching
+			i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+			while ( i-- ) {
+				token = tokens[i];
+
+				// Abort if we hit a combinator
+				if ( Expr.relative[ (type = token.type) ] ) {
+					break;
+				}
+				if ( (find = Expr.find[ type ]) ) {
+					// Search, expanding context for leading sibling combinators
+					if ( (seed = find(
+						token.matches[0].replace( runescape, funescape ),
+						rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+					)) ) {
+
+						// If seed is empty or no tokens remain, we can return early
+						tokens.splice( i, 1 );
+						selector = seed.length && toSelector( tokens );
+						if ( !selector ) {
+							push.apply( results, seed );
+							return results;
+						}
+
+						break;
+					}
+				}
+			}
+		}
+
+		// Compile and execute a filtering function if one is not provided
+		// Provide `match` to avoid retokenization if we modified the selector above
+		( compiled || compile( selector, match ) )(
+			seed,
+			context,
+			!documentIsHTML,
+			results,
+			!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+		);
+		return results;
+	};
+
+	// One-time assignments
+
+	// Sort stability
+	support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+	// Support: Chrome 14-35+
+	// Always assume duplicates if they aren't passed to the comparison function
+	support.detectDuplicates = !!hasDuplicate;
+
+	// Initialize against the default document
+	setDocument();
+
+	// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+	// Detached nodes confoundingly follow *each other*
+	support.sortDetached = assert(function( div1 ) {
+		// Should return 1, but returns 4 (following)
+		return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+	});
+
+	// Support: IE<8
+	// Prevent attribute/property "interpolation"
+	// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+	if ( !assert(function( div ) {
+		div.innerHTML = "<a href='#'></a>";
+		return div.firstChild.getAttribute("href") === "#" ;
+	}) ) {
+		addHandle( "type|href|height|width", function( elem, name, isXML ) {
+			if ( !isXML ) {
+				return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+			}
+		});
+	}
+
+	// Support: IE<9
+	// Use defaultValue in place of getAttribute("value")
+	if ( !support.attributes || !assert(function( div ) {
+		div.innerHTML = "<input/>";
+		div.firstChild.setAttribute( "value", "" );
+		return div.firstChild.getAttribute( "value" ) === "";
+	}) ) {
+		addHandle( "value", function( elem, name, isXML ) {
+			if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+				return elem.defaultValue;
+			}
+		});
+	}
+
+	// Support: IE<9
+	// Use getAttributeNode to fetch booleans when getAttribute lies
+	if ( !assert(function( div ) {
+		return div.getAttribute("disabled") == null;
+	}) ) {
+		addHandle( booleans, function( elem, name, isXML ) {
+			var val;
+			if ( !isXML ) {
+				return elem[ name ] === true ? name.toLowerCase() :
+						(val = elem.getAttributeNode( name )) && val.specified ?
+						val.value :
+					null;
+			}
+		});
+	}
+
+	return Sizzle;
+
+	})( window );
+
+
+
+	jQuery.find = Sizzle;
+	jQuery.expr = Sizzle.selectors;
+	jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+	jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
+	jQuery.text = Sizzle.getText;
+	jQuery.isXMLDoc = Sizzle.isXML;
+	jQuery.contains = Sizzle.contains;
+
+
+
+	var dir = function( elem, dir, until ) {
+		var matched = [],
+			truncate = until !== undefined;
+
+		while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+			if ( elem.nodeType === 1 ) {
+				if ( truncate && jQuery( elem ).is( until ) ) {
+					break;
+				}
+				matched.push( elem );
+			}
+		}
+		return matched;
+	};
+
+
+	var siblings = function( n, elem ) {
+		var matched = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				matched.push( n );
+			}
+		}
+
+		return matched;
+	};
+
+
+	var rneedsContext = jQuery.expr.match.needsContext;
+
+	var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ );
+
+
+
+	var risSimple = /^.[^:#\[\.,]*$/;
+
+	// Implement the identical functionality for filter and not
+	function winnow( elements, qualifier, not ) {
+		if ( jQuery.isFunction( qualifier ) ) {
+			return jQuery.grep( elements, function( elem, i ) {
+				/* jshint -W018 */
+				return !!qualifier.call( elem, i, elem ) !== not;
+			} );
+
+		}
+
+		if ( qualifier.nodeType ) {
+			return jQuery.grep( elements, function( elem ) {
+				return ( elem === qualifier ) !== not;
+			} );
+
+		}
+
+		if ( typeof qualifier === "string" ) {
+			if ( risSimple.test( qualifier ) ) {
+				return jQuery.filter( qualifier, elements, not );
+			}
+
+			qualifier = jQuery.filter( qualifier, elements );
+		}
+
+		return jQuery.grep( elements, function( elem ) {
+			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+		} );
+	}
+
+	jQuery.filter = function( expr, elems, not ) {
+		var elem = elems[ 0 ];
+
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 && elem.nodeType === 1 ?
+			jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+			jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+				return elem.nodeType === 1;
+			} ) );
+	};
+
+	jQuery.fn.extend( {
+		find: function( selector ) {
+			var i,
+				len = this.length,
+				ret = [],
+				self = this;
+
+			if ( typeof selector !== "string" ) {
+				return this.pushStack( jQuery( selector ).filter( function() {
+					for ( i = 0; i < len; i++ ) {
+						if ( jQuery.contains( self[ i ], this ) ) {
+							return true;
+						}
+					}
+				} ) );
+			}
+
+			for ( i = 0; i < len; i++ ) {
+				jQuery.find( selector, self[ i ], ret );
+			}
+
+			// Needed because $( selector, context ) becomes $( context ).find( selector )
+			ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+			ret.selector = this.selector ? this.selector + " " + selector : selector;
+			return ret;
+		},
+		filter: function( selector ) {
+			return this.pushStack( winnow( this, selector || [], false ) );
+		},
+		not: function( selector ) {
+			return this.pushStack( winnow( this, selector || [], true ) );
+		},
+		is: function( selector ) {
+			return !!winnow(
+				this,
+
+				// If this is a positional/relative selector, check membership in the returned set
+				// so $("p:first").is("p:last") won't return true for a doc with two "p".
+				typeof selector === "string" && rneedsContext.test( selector ) ?
+					jQuery( selector ) :
+					selector || [],
+				false
+			).length;
+		}
+	} );
+
+
+	// Initialize a jQuery object
+
+
+	// A central reference to the root jQuery(document)
+	var rootjQuery,
+
+		// A simple way to check for HTML strings
+		// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+		// Strict HTML recognition (#11290: must start with <)
+		rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+		init = jQuery.fn.init = function( selector, context, root ) {
+			var match, elem;
+
+			// HANDLE: $(""), $(null), $(undefined), $(false)
+			if ( !selector ) {
+				return this;
+			}
+
+			// Method init() accepts an alternate rootjQuery
+			// so migrate can support jQuery.sub (gh-2101)
+			root = root || rootjQuery;
+
+			// Handle HTML strings
+			if ( typeof selector === "string" ) {
+				if ( selector[ 0 ] === "<" &&
+					selector[ selector.length - 1 ] === ">" &&
+					selector.length >= 3 ) {
+
+					// Assume that strings that start and end with <> are HTML and skip the regex check
+					match = [ null, selector, null ];
+
+				} else {
+					match = rquickExpr.exec( selector );
+				}
+
+				// Match html or make sure no context is specified for #id
+				if ( match && ( match[ 1 ] || !context ) ) {
+
+					// HANDLE: $(html) -> $(array)
+					if ( match[ 1 ] ) {
+						context = context instanceof jQuery ? context[ 0 ] : context;
+
+						// Option to run scripts is true for back-compat
+						// Intentionally let the error be thrown if parseHTML is not present
+						jQuery.merge( this, jQuery.parseHTML(
+							match[ 1 ],
+							context && context.nodeType ? context.ownerDocument || context : document,
+							true
+						) );
+
+						// HANDLE: $(html, props)
+						if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+							for ( match in context ) {
+
+								// Properties of context are called as methods if possible
+								if ( jQuery.isFunction( this[ match ] ) ) {
+									this[ match ]( context[ match ] );
+
+								// ...and otherwise set as attributes
+								} else {
+									this.attr( match, context[ match ] );
+								}
+							}
+						}
+
+						return this;
+
+					// HANDLE: $(#id)
+					} else {
+						elem = document.getElementById( match[ 2 ] );
+
+						// Support: Blackberry 4.6
+						// gEBID returns nodes no longer in the document (#6963)
+						if ( elem && elem.parentNode ) {
+
+							// Inject the element directly into the jQuery object
+							this.length = 1;
+							this[ 0 ] = elem;
+						}
+
+						this.context = document;
+						this.selector = selector;
+						return this;
+					}
+
+				// HANDLE: $(expr, $(...))
+				} else if ( !context || context.jquery ) {
+					return ( context || root ).find( selector );
+
+				// HANDLE: $(expr, context)
+				// (which is just equivalent to: $(context).find(expr)
+				} else {
+					return this.constructor( context ).find( selector );
+				}
+
+			// HANDLE: $(DOMElement)
+			} else if ( selector.nodeType ) {
+				this.context = this[ 0 ] = selector;
+				this.length = 1;
+				return this;
+
+			// HANDLE: $(function)
+			// Shortcut for document ready
+			} else if ( jQuery.isFunction( selector ) ) {
+				return root.ready !== undefined ?
+					root.ready( selector ) :
+
+					// Execute immediately if ready is not present
+					selector( jQuery );
+			}
+
+			if ( selector.selector !== undefined ) {
+				this.selector = selector.selector;
+				this.context = selector.context;
+			}
+
+			return jQuery.makeArray( selector, this );
+		};
+
+	// Give the init function the jQuery prototype for later instantiation
+	init.prototype = jQuery.fn;
+
+	// Initialize central reference
+	rootjQuery = jQuery( document );
+
+
+	var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+		// Methods guaranteed to produce a unique set when starting from a unique set
+		guaranteedUnique = {
+			children: true,
+			contents: true,
+			next: true,
+			prev: true
+		};
+
+	jQuery.fn.extend( {
+		has: function( target ) {
+			var targets = jQuery( target, this ),
+				l = targets.length;
+
+			return this.filter( function() {
+				var i = 0;
+				for ( ; i < l; i++ ) {
+					if ( jQuery.contains( this, targets[ i ] ) ) {
+						return true;
+					}
+				}
+			} );
+		},
+
+		closest: function( selectors, context ) {
+			var cur,
+				i = 0,
+				l = this.length,
+				matched = [],
+				pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+					jQuery( selectors, context || this.context ) :
+					0;
+
+			for ( ; i < l; i++ ) {
+				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+					// Always skip document fragments
+					if ( cur.nodeType < 11 && ( pos ?
+						pos.index( cur ) > -1 :
+
+						// Don't pass non-elements to Sizzle
+						cur.nodeType === 1 &&
+							jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+						matched.push( cur );
+						break;
+					}
+				}
+			}
+
+			return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+		},
+
+		// Determine the position of an element within the set
+		index: function( elem ) {
+
+			// No argument, return index in parent
+			if ( !elem ) {
+				return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+			}
+
+			// Index in selector
+			if ( typeof elem === "string" ) {
+				return indexOf.call( jQuery( elem ), this[ 0 ] );
+			}
+
+			// Locate the position of the desired element
+			return indexOf.call( this,
+
+				// If it receives a jQuery object, the first element is used
+				elem.jquery ? elem[ 0 ] : elem
+			);
+		},
+
+		add: function( selector, context ) {
+			return this.pushStack(
+				jQuery.uniqueSort(
+					jQuery.merge( this.get(), jQuery( selector, context ) )
+				)
+			);
+		},
+
+		addBack: function( selector ) {
+			return this.add( selector == null ?
+				this.prevObject : this.prevObject.filter( selector )
+			);
+		}
+	} );
+
+	function sibling( cur, dir ) {
+		while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+		return cur;
+	}
+
+	jQuery.each( {
+		parent: function( elem ) {
+			var parent = elem.parentNode;
+			return parent && parent.nodeType !== 11 ? parent : null;
+		},
+		parents: function( elem ) {
+			return dir( elem, "parentNode" );
+		},
+		parentsUntil: function( elem, i, until ) {
+			return dir( elem, "parentNode", until );
+		},
+		next: function( elem ) {
+			return sibling( elem, "nextSibling" );
+		},
+		prev: function( elem ) {
+			return sibling( elem, "previousSibling" );
+		},
+		nextAll: function( elem ) {
+			return dir( elem, "nextSibling" );
+		},
+		prevAll: function( elem ) {
+			return dir( elem, "previousSibling" );
+		},
+		nextUntil: function( elem, i, until ) {
+			return dir( elem, "nextSibling", until );
+		},
+		prevUntil: function( elem, i, until ) {
+			return dir( elem, "previousSibling", until );
+		},
+		siblings: function( elem ) {
+			return siblings( ( elem.parentNode || {} ).firstChild, elem );
+		},
+		children: function( elem ) {
+			return siblings( elem.firstChild );
+		},
+		contents: function( elem ) {
+			return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+		}
+	}, function( name, fn ) {
+		jQuery.fn[ name ] = function( until, selector ) {
+			var matched = jQuery.map( this, fn, until );
+
+			if ( name.slice( -5 ) !== "Until" ) {
+				selector = until;
+			}
+
+			if ( selector && typeof selector === "string" ) {
+				matched = jQuery.filter( selector, matched );
+			}
+
+			if ( this.length > 1 ) {
+
+				// Remove duplicates
+				if ( !guaranteedUnique[ name ] ) {
+					jQuery.uniqueSort( matched );
+				}
+
+				// Reverse order for parents* and prev-derivatives
+				if ( rparentsprev.test( name ) ) {
+					matched.reverse();
+				}
+			}
+
+			return this.pushStack( matched );
+		};
+	} );
+	var rnotwhite = ( /\S+/g );
+
+
+
+	// Convert String-formatted options into Object-formatted ones
+	function createOptions( options ) {
+		var object = {};
+		jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+			object[ flag ] = true;
+		} );
+		return object;
+	}
+
+	/*
+	 * Create a callback list using the following parameters:
+	 *
+	 *	options: an optional list of space-separated options that will change how
+	 *			the callback list behaves or a more traditional option object
+	 *
+	 * By default a callback list will act like an event callback list and can be
+	 * "fired" multiple times.
+	 *
+	 * Possible options:
+	 *
+	 *	once:			will ensure the callback list can only be fired once (like a Deferred)
+	 *
+	 *	memory:			will keep track of previous values and will call any callback added
+	 *					after the list has been fired right away with the latest "memorized"
+	 *					values (like a Deferred)
+	 *
+	 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+	 *
+	 *	stopOnFalse:	interrupt callings when a callback returns false
+	 *
+	 */
+	jQuery.Callbacks = function( options ) {
+
+		// Convert options from String-formatted to Object-formatted if needed
+		// (we check in cache first)
+		options = typeof options === "string" ?
+			createOptions( options ) :
+			jQuery.extend( {}, options );
+
+		var // Flag to know if list is currently firing
+			firing,
+
+			// Last fire value for non-forgettable lists
+			memory,
+
+			// Flag to know if list was already fired
+			fired,
+
+			// Flag to prevent firing
+			locked,
+
+			// Actual callback list
+			list = [],
+
+			// Queue of execution data for repeatable lists
+			queue = [],
+
+			// Index of currently firing callback (modified by add/remove as needed)
+			firingIndex = -1,
+
+			// Fire callbacks
+			fire = function() {
+
+				// Enforce single-firing
+				locked = options.once;
+
+				// Execute callbacks for all pending executions,
+				// respecting firingIndex overrides and runtime changes
+				fired = firing = true;
+				for ( ; queue.length; firingIndex = -1 ) {
+					memory = queue.shift();
+					while ( ++firingIndex < list.length ) {
+
+						// Run callback and check for early termination
+						if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+							options.stopOnFalse ) {
+
+							// Jump to end and forget the data so .add doesn't re-fire
+							firingIndex = list.length;
+							memory = false;
+						}
+					}
+				}
+
+				// Forget the data if we're done with it
+				if ( !options.memory ) {
+					memory = false;
+				}
+
+				firing = false;
+
+				// Clean up if we're done firing for good
+				if ( locked ) {
+
+					// Keep an empty list if we have data for future add calls
+					if ( memory ) {
+						list = [];
+
+					// Otherwise, this object is spent
+					} else {
+						list = "";
+					}
+				}
+			},
+
+			// Actual Callbacks object
+			self = {
+
+				// Add a callback or a collection of callbacks to the list
+				add: function() {
+					if ( list ) {
+
+						// If we have memory from a past run, we should fire after adding
+						if ( memory && !firing ) {
+							firingIndex = list.length - 1;
+							queue.push( memory );
+						}
+
+						( function add( args ) {
+							jQuery.each( args, function( _, arg ) {
+								if ( jQuery.isFunction( arg ) ) {
+									if ( !options.unique || !self.has( arg ) ) {
+										list.push( arg );
+									}
+								} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
+
+									// Inspect recursively
+									add( arg );
+								}
+							} );
+						} )( arguments );
+
+						if ( memory && !firing ) {
+							fire();
+						}
+					}
+					return this;
+				},
+
+				// Remove a callback from the list
+				remove: function() {
+					jQuery.each( arguments, function( _, arg ) {
+						var index;
+						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+							list.splice( index, 1 );
+
+							// Handle firing indexes
+							if ( index <= firingIndex ) {
+								firingIndex--;
+							}
+						}
+					} );
+					return this;
+				},
+
+				// Check if a given callback is in the list.
+				// If no argument is given, return whether or not list has callbacks attached.
+				has: function( fn ) {
+					return fn ?
+						jQuery.inArray( fn, list ) > -1 :
+						list.length > 0;
+				},
+
+				// Remove all callbacks from the list
+				empty: function() {
+					if ( list ) {
+						list = [];
+					}
+					return this;
+				},
+
+				// Disable .fire and .add
+				// Abort any current/pending executions
+				// Clear all callbacks and values
+				disable: function() {
+					locked = queue = [];
+					list = memory = "";
+					return this;
+				},
+				disabled: function() {
+					return !list;
+				},
+
+				// Disable .fire
+				// Also disable .add unless we have memory (since it would have no effect)
+				// Abort any pending executions
+				lock: function() {
+					locked = queue = [];
+					if ( !memory ) {
+						list = memory = "";
+					}
+					return this;
+				},
+				locked: function() {
+					return !!locked;
+				},
+
+				// Call all callbacks with the given context and arguments
+				fireWith: function( context, args ) {
+					if ( !locked ) {
+						args = args || [];
+						args = [ context, args.slice ? args.slice() : args ];
+						queue.push( args );
+						if ( !firing ) {
+							fire();
+						}
+					}
+					return this;
+				},
+
+				// Call all the callbacks with the given arguments
+				fire: function() {
+					self.fireWith( this, arguments );
+					return this;
+				},
+
+				// To know if the callbacks have already been called at least once
+				fired: function() {
+					return !!fired;
+				}
+			};
+
+		return self;
+	};
+
+
+	jQuery.extend( {
+
+		Deferred: function( func ) {
+			var tuples = [
+
+					// action, add listener, listener list, final state
+					[ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
+					[ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
+					[ "notify", "progress", jQuery.Callbacks( "memory" ) ]
+				],
+				state = "pending",
+				promise = {
+					state: function() {
+						return state;
+					},
+					always: function() {
+						deferred.done( arguments ).fail( arguments );
+						return this;
+					},
+					then: function( /* fnDone, fnFail, fnProgress */ ) {
+						var fns = arguments;
+						return jQuery.Deferred( function( newDefer ) {
+							jQuery.each( tuples, function( i, tuple ) {
+								var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+
+								// deferred[ done | fail | progress ] for forwarding actions to newDefer
+								deferred[ tuple[ 1 ] ]( function() {
+									var returned = fn && fn.apply( this, arguments );
+									if ( returned && jQuery.isFunction( returned.promise ) ) {
+										returned.promise()
+											.progress( newDefer.notify )
+											.done( newDefer.resolve )
+											.fail( newDefer.reject );
+									} else {
+										newDefer[ tuple[ 0 ] + "With" ](
+											this === promise ? newDefer.promise() : this,
+											fn ? [ returned ] : arguments
+										);
+									}
+								} );
+							} );
+							fns = null;
+						} ).promise();
+					},
+
+					// Get a promise for this deferred
+					// If obj is provided, the promise aspect is added to the object
+					promise: function( obj ) {
+						return obj != null ? jQuery.extend( obj, promise ) : promise;
+					}
+				},
+				deferred = {};
+
+			// Keep pipe for back-compat
+			promise.pipe = promise.then;
+
+			// Add list-specific methods
+			jQuery.each( tuples, function( i, tuple ) {
+				var list = tuple[ 2 ],
+					stateString = tuple[ 3 ];
+
+				// promise[ done | fail | progress ] = list.add
+				promise[ tuple[ 1 ] ] = list.add;
+
+				// Handle state
+				if ( stateString ) {
+					list.add( function() {
+
+						// state = [ resolved | rejected ]
+						state = stateString;
+
+					// [ reject_list | resolve_list ].disable; progress_list.lock
+					}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+				}
+
+				// deferred[ resolve | reject | notify ]
+				deferred[ tuple[ 0 ] ] = function() {
+					deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );
+					return this;
+				};
+				deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+			} );
+
+			// Make the deferred a promise
+			promise.promise( deferred );
+
+			// Call given func if any
+			if ( func ) {
+				func.call( deferred, deferred );
+			}
+
+			// All done!
+			return deferred;
+		},
+
+		// Deferred helper
+		when: function( subordinate /* , ..., subordinateN */ ) {
+			var i = 0,
+				resolveValues = slice.call( arguments ),
+				length = resolveValues.length,
+
+				// the count of uncompleted subordinates
+				remaining = length !== 1 ||
+					( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+				// the master Deferred.
+				// If resolveValues consist of only a single Deferred, just use that.
+				deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+				// Update function for both resolve and progress values
+				updateFunc = function( i, contexts, values ) {
+					return function( value ) {
+						contexts[ i ] = this;
+						values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+						if ( values === progressValues ) {
+							deferred.notifyWith( contexts, values );
+						} else if ( !( --remaining ) ) {
+							deferred.resolveWith( contexts, values );
+						}
+					};
+				},
+
+				progressValues, progressContexts, resolveContexts;
+
+			// Add listeners to Deferred subordinates; treat others as resolved
+			if ( length > 1 ) {
+				progressValues = new Array( length );
+				progressContexts = new Array( length );
+				resolveContexts = new Array( length );
+				for ( ; i < length; i++ ) {
+					if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+						resolveValues[ i ].promise()
+							.progress( updateFunc( i, progressContexts, progressValues ) )
+							.done( updateFunc( i, resolveContexts, resolveValues ) )
+							.fail( deferred.reject );
+					} else {
+						--remaining;
+					}
+				}
+			}
+
+			// If we're not waiting on anything, resolve the master
+			if ( !remaining ) {
+				deferred.resolveWith( resolveContexts, resolveValues );
+			}
+
+			return deferred.promise();
+		}
+	} );
+
+
+	// The deferred used on DOM ready
+	var readyList;
+
+	jQuery.fn.ready = function( fn ) {
+
+		// Add the callback
+		jQuery.ready.promise().done( fn );
+
+		return this;
+	};
+
+	jQuery.extend( {
+
+		// Is the DOM ready to be used? Set to true once it occurs.
+		isReady: false,
+
+		// A counter to track how many items to wait for before
+		// the ready event fires. See #6781
+		readyWait: 1,
+
+		// Hold (or release) the ready event
+		holdReady: function( hold ) {
+			if ( hold ) {
+				jQuery.readyWait++;
+			} else {
+				jQuery.ready( true );
+			}
+		},
+
+		// Handle when the DOM is ready
+		ready: function( wait ) {
+
+			// Abort if there are pending holds or we're already ready
+			if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+				return;
+			}
+
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If a normal DOM Ready event fired, decrement, and wait if need be
+			if ( wait !== true && --jQuery.readyWait > 0 ) {
+				return;
+			}
+
+			// If there are functions bound, to execute
+			readyList.resolveWith( document, [ jQuery ] );
+
+			// Trigger any bound ready events
+			if ( jQuery.fn.triggerHandler ) {
+				jQuery( document ).triggerHandler( "ready" );
+				jQuery( document ).off( "ready" );
+			}
+		}
+	} );
+
+	/**
+	 * The ready event handler and self cleanup method
+	 */
+	function completed() {
+		document.removeEventListener( "DOMContentLoaded", completed );
+		window.removeEventListener( "load", completed );
+		jQuery.ready();
+	}
+
+	jQuery.ready.promise = function( obj ) {
+		if ( !readyList ) {
+
+			readyList = jQuery.Deferred();
+
+			// Catch cases where $(document).ready() is called
+			// after the browser event has already occurred.
+			// Support: IE9-10 only
+			// Older IE sometimes signals "interactive" too soon
+			if ( document.readyState === "complete" ||
+				( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+				// Handle it asynchronously to allow scripts the opportunity to delay ready
+				window.setTimeout( jQuery.ready );
+
+			} else {
+
+				// Use the handy event callback
+				document.addEventListener( "DOMContentLoaded", completed );
+
+				// A fallback to window.onload, that will always work
+				window.addEventListener( "load", completed );
+			}
+		}
+		return readyList.promise( obj );
+	};
+
+	// Kick off the DOM ready check even if the user does not
+	jQuery.ready.promise();
+
+
+
+
+	// Multifunctional method to get and set values of a collection
+	// The value/s can optionally be executed if it's a function
+	var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+		var i = 0,
+			len = elems.length,
+			bulk = key == null;
+
+		// Sets many values
+		if ( jQuery.type( key ) === "object" ) {
+			chainable = true;
+			for ( i in key ) {
+				access( elems, fn, i, key[ i ], true, emptyGet, raw );
+			}
+
+		// Sets one value
+		} else if ( value !== undefined ) {
+			chainable = true;
+
+			if ( !jQuery.isFunction( value ) ) {
+				raw = true;
+			}
+
+			if ( bulk ) {
+
+				// Bulk operations run against the entire set
+				if ( raw ) {
+					fn.call( elems, value );
+					fn = null;
+
+				// ...except when executing function values
+				} else {
+					bulk = fn;
+					fn = function( elem, key, value ) {
+						return bulk.call( jQuery( elem ), value );
+					};
+				}
+			}
+
+			if ( fn ) {
+				for ( ; i < len; i++ ) {
+					fn(
+						elems[ i ], key, raw ?
+						value :
+						value.call( elems[ i ], i, fn( elems[ i ], key ) )
+					);
+				}
+			}
+		}
+
+		return chainable ?
+			elems :
+
+			// Gets
+			bulk ?
+				fn.call( elems ) :
+				len ? fn( elems[ 0 ], key ) : emptyGet;
+	};
+	var acceptData = function( owner ) {
+
+		// Accepts only:
+		//  - Node
+		//    - Node.ELEMENT_NODE
+		//    - Node.DOCUMENT_NODE
+		//  - Object
+		//    - Any
+		/* jshint -W018 */
+		return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+	};
+
+
+
+
+	function Data() {
+		this.expando = jQuery.expando + Data.uid++;
+	}
+
+	Data.uid = 1;
+
+	Data.prototype = {
+
+		register: function( owner, initial ) {
+			var value = initial || {};
+
+			// If it is a node unlikely to be stringify-ed or looped over
+			// use plain assignment
+			if ( owner.nodeType ) {
+				owner[ this.expando ] = value;
+
+			// Otherwise secure it in a non-enumerable, non-writable property
+			// configurability must be true to allow the property to be
+			// deleted with the delete operator
+			} else {
+				Object.defineProperty( owner, this.expando, {
+					value: value,
+					writable: true,
+					configurable: true
+				} );
+			}
+			return owner[ this.expando ];
+		},
+		cache: function( owner ) {
+
+			// We can accept data for non-element nodes in modern browsers,
+			// but we should not, see #8335.
+			// Always return an empty object.
+			if ( !acceptData( owner ) ) {
+				return {};
+			}
+
+			// Check if the owner object already has a cache
+			var value = owner[ this.expando ];
+
+			// If not, create one
+			if ( !value ) {
+				value = {};
+
+				// We can accept data for non-element nodes in modern browsers,
+				// but we should not, see #8335.
+				// Always return an empty object.
+				if ( acceptData( owner ) ) {
+
+					// If it is a node unlikely to be stringify-ed or looped over
+					// use plain assignment
+					if ( owner.nodeType ) {
+						owner[ this.expando ] = value;
+
+					// Otherwise secure it in a non-enumerable property
+					// configurable must be true to allow the property to be
+					// deleted when data is removed
+					} else {
+						Object.defineProperty( owner, this.expando, {
+							value: value,
+							configurable: true
+						} );
+					}
+				}
+			}
+
+			return value;
+		},
+		set: function( owner, data, value ) {
+			var prop,
+				cache = this.cache( owner );
+
+			// Handle: [ owner, key, value ] args
+			if ( typeof data === "string" ) {
+				cache[ data ] = value;
+
+			// Handle: [ owner, { properties } ] args
+			} else {
+
+				// Copy the properties one-by-one to the cache object
+				for ( prop in data ) {
+					cache[ prop ] = data[ prop ];
+				}
+			}
+			return cache;
+		},
+		get: function( owner, key ) {
+			return key === undefined ?
+				this.cache( owner ) :
+				owner[ this.expando ] && owner[ this.expando ][ key ];
+		},
+		access: function( owner, key, value ) {
+			var stored;
+
+			// In cases where either:
+			//
+			//   1. No key was specified
+			//   2. A string key was specified, but no value provided
+			//
+			// Take the "read" path and allow the get method to determine
+			// which value to return, respectively either:
+			//
+			//   1. The entire cache object
+			//   2. The data stored at the key
+			//
+			if ( key === undefined ||
+					( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+				stored = this.get( owner, key );
+
+				return stored !== undefined ?
+					stored : this.get( owner, jQuery.camelCase( key ) );
+			}
+
+			// When the key is not a string, or both a key and value
+			// are specified, set or extend (existing objects) with either:
+			//
+			//   1. An object of properties
+			//   2. A key and value
+			//
+			this.set( owner, key, value );
+
+			// Since the "set" path can have two possible entry points
+			// return the expected data based on which path was taken[*]
+			return value !== undefined ? value : key;
+		},
+		remove: function( owner, key ) {
+			var i, name, camel,
+				cache = owner[ this.expando ];
+
+			if ( cache === undefined ) {
+				return;
+			}
+
+			if ( key === undefined ) {
+				this.register( owner );
+
+			} else {
+
+				// Support array or space separated string of keys
+				if ( jQuery.isArray( key ) ) {
+
+					// If "name" is an array of keys...
+					// When data is initially created, via ("key", "val") signature,
+					// keys will be converted to camelCase.
+					// Since there is no way to tell _how_ a key was added, remove
+					// both plain key and camelCase key. #12786
+					// This will only penalize the array argument path.
+					name = key.concat( key.map( jQuery.camelCase ) );
+				} else {
+					camel = jQuery.camelCase( key );
+
+					// Try the string as a key before any manipulation
+					if ( key in cache ) {
+						name = [ key, camel ];
+					} else {
+
+						// If a key with the spaces exists, use it.
+						// Otherwise, create an array by matching non-whitespace
+						name = camel;
+						name = name in cache ?
+							[ name ] : ( name.match( rnotwhite ) || [] );
+					}
+				}
+
+				i = name.length;
+
+				while ( i-- ) {
+					delete cache[ name[ i ] ];
+				}
+			}
+
+			// Remove the expando if there's no more data
+			if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+				// Support: Chrome <= 35-45+
+				// Webkit & Blink performance suffers when deleting properties
+				// from DOM nodes, so set to undefined instead
+				// https://code.google.com/p/chromium/issues/detail?id=378607
+				if ( owner.nodeType ) {
+					owner[ this.expando ] = undefined;
+				} else {
+					delete owner[ this.expando ];
+				}
+			}
+		},
+		hasData: function( owner ) {
+			var cache = owner[ this.expando ];
+			return cache !== undefined && !jQuery.isEmptyObject( cache );
+		}
+	};
+	var dataPriv = new Data();
+
+	var dataUser = new Data();
+
+
+
+	//	Implementation Summary
+	//
+	//	1. Enforce API surface and semantic compatibility with 1.9.x branch
+	//	2. Improve the module's maintainability by reducing the storage
+	//		paths to a single mechanism.
+	//	3. Use the same single mechanism to support "private" and "user" data.
+	//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+	//	5. Avoid exposing implementation details on user objects (eg. expando properties)
+	//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+	var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+		rmultiDash = /[A-Z]/g;
+
+	function dataAttr( elem, key, data ) {
+		var name;
+
+		// If nothing was found internally, try to fetch any
+		// data from the HTML5 data-* attribute
+		if ( data === undefined && elem.nodeType === 1 ) {
+			name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+			data = elem.getAttribute( name );
+
+			if ( typeof data === "string" ) {
+				try {
+					data = data === "true" ? true :
+						data === "false" ? false :
+						data === "null" ? null :
+
+						// Only convert to a number if it doesn't change the string
+						+data + "" === data ? +data :
+						rbrace.test( data ) ? jQuery.parseJSON( data ) :
+						data;
+				} catch ( e ) {}
+
+				// Make sure we set the data so it isn't changed later
+				dataUser.set( elem, key, data );
+			} else {
+				data = undefined;
+			}
+		}
+		return data;
+	}
+
+	jQuery.extend( {
+		hasData: function( elem ) {
+			return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+		},
+
+		data: function( elem, name, data ) {
+			return dataUser.access( elem, name, data );
+		},
+
+		removeData: function( elem, name ) {
+			dataUser.remove( elem, name );
+		},
+
+		// TODO: Now that all calls to _data and _removeData have been replaced
+		// with direct calls to dataPriv methods, these can be deprecated.
+		_data: function( elem, name, data ) {
+			return dataPriv.access( elem, name, data );
+		},
+
+		_removeData: function( elem, name ) {
+			dataPriv.remove( elem, name );
+		}
+	} );
+
+	jQuery.fn.extend( {
+		data: function( key, value ) {
+			var i, name, data,
+				elem = this[ 0 ],
+				attrs = elem && elem.attributes;
+
+			// Gets all values
+			if ( key === undefined ) {
+				if ( this.length ) {
+					data = dataUser.get( elem );
+
+					if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+						i = attrs.length;
+						while ( i-- ) {
+
+							// Support: IE11+
+							// The attrs elements can be null (#14894)
+							if ( attrs[ i ] ) {
+								name = attrs[ i ].name;
+								if ( name.indexOf( "data-" ) === 0 ) {
+									name = jQuery.camelCase( name.slice( 5 ) );
+									dataAttr( elem, name, data[ name ] );
+								}
+							}
+						}
+						dataPriv.set( elem, "hasDataAttrs", true );
+					}
+				}
+
+				return data;
+			}
+
+			// Sets multiple values
+			if ( typeof key === "object" ) {
+				return this.each( function() {
+					dataUser.set( this, key );
+				} );
+			}
+
+			return access( this, function( value ) {
+				var data, camelKey;
+
+				// The calling jQuery object (element matches) is not empty
+				// (and therefore has an element appears at this[ 0 ]) and the
+				// `value` parameter was not undefined. An empty jQuery object
+				// will result in `undefined` for elem = this[ 0 ] which will
+				// throw an exception if an attempt to read a data cache is made.
+				if ( elem && value === undefined ) {
+
+					// Attempt to get data from the cache
+					// with the key as-is
+					data = dataUser.get( elem, key ) ||
+
+						// Try to find dashed key if it exists (gh-2779)
+						// This is for 2.2.x only
+						dataUser.get( elem, key.replace( rmultiDash, "-$&" ).toLowerCase() );
+
+					if ( data !== undefined ) {
+						return data;
+					}
+
+					camelKey = jQuery.camelCase( key );
+
+					// Attempt to get data from the cache
+					// with the key camelized
+					data = dataUser.get( elem, camelKey );
+					if ( data !== undefined ) {
+						return data;
+					}
+
+					// Attempt to "discover" the data in
+					// HTML5 custom data-* attrs
+					data = dataAttr( elem, camelKey, undefined );
+					if ( data !== undefined ) {
+						return data;
+					}
+
+					// We tried really hard, but the data doesn't exist.
+					return;
+				}
+
+				// Set the data...
+				camelKey = jQuery.camelCase( key );
+				this.each( function() {
+
+					// First, attempt to store a copy or reference of any
+					// data that might've been store with a camelCased key.
+					var data = dataUser.get( this, camelKey );
+
+					// For HTML5 data-* attribute interop, we have to
+					// store property names with dashes in a camelCase form.
+					// This might not apply to all properties...*
+					dataUser.set( this, camelKey, value );
+
+					// *... In the case of properties that might _actually_
+					// have dashes, we need to also store a copy of that
+					// unchanged property.
+					if ( key.indexOf( "-" ) > -1 && data !== undefined ) {
+						dataUser.set( this, key, value );
+					}
+				} );
+			}, null, value, arguments.length > 1, null, true );
+		},
+
+		removeData: function( key ) {
+			return this.each( function() {
+				dataUser.remove( this, key );
+			} );
+		}
+	} );
+
+
+	jQuery.extend( {
+		queue: function( elem, type, data ) {
+			var queue;
+
+			if ( elem ) {
+				type = ( type || "fx" ) + "queue";
+				queue = dataPriv.get( elem, type );
+
+				// Speed up dequeue by getting out quickly if this is just a lookup
+				if ( data ) {
+					if ( !queue || jQuery.isArray( data ) ) {
+						queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+					} else {
+						queue.push( data );
+					}
+				}
+				return queue || [];
+			}
+		},
+
+		dequeue: function( elem, type ) {
+			type = type || "fx";
+
+			var queue = jQuery.queue( elem, type ),
+				startLength = queue.length,
+				fn = queue.shift(),
+				hooks = jQuery._queueHooks( elem, type ),
+				next = function() {
+					jQuery.dequeue( elem, type );
+				};
+
+			// If the fx queue is dequeued, always remove the progress sentinel
+			if ( fn === "inprogress" ) {
+				fn = queue.shift();
+				startLength--;
+			}
+
+			if ( fn ) {
+
+				// Add a progress sentinel to prevent the fx queue from being
+				// automatically dequeued
+				if ( type === "fx" ) {
+					queue.unshift( "inprogress" );
+				}
+
+				// Clear up the last queue stop function
+				delete hooks.stop;
+				fn.call( elem, next, hooks );
+			}
+
+			if ( !startLength && hooks ) {
+				hooks.empty.fire();
+			}
+		},
+
+		// Not public - generate a queueHooks object, or return the current one
+		_queueHooks: function( elem, type ) {
+			var key = type + "queueHooks";
+			return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+				empty: jQuery.Callbacks( "once memory" ).add( function() {
+					dataPriv.remove( elem, [ type + "queue", key ] );
+				} )
+			} );
+		}
+	} );
+
+	jQuery.fn.extend( {
+		queue: function( type, data ) {
+			var setter = 2;
+
+			if ( typeof type !== "string" ) {
+				data = type;
+				type = "fx";
+				setter--;
+			}
+
+			if ( arguments.length < setter ) {
+				return jQuery.queue( this[ 0 ], type );
+			}
+
+			return data === undefined ?
+				this :
+				this.each( function() {
+					var queue = jQuery.queue( this, type, data );
+
+					// Ensure a hooks for this queue
+					jQuery._queueHooks( this, type );
+
+					if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+						jQuery.dequeue( this, type );
+					}
+				} );
+		},
+		dequeue: function( type ) {
+			return this.each( function() {
+				jQuery.dequeue( this, type );
+			} );
+		},
+		clearQueue: function( type ) {
+			return this.queue( type || "fx", [] );
+		},
+
+		// Get a promise resolved when queues of a certain type
+		// are emptied (fx is the type by default)
+		promise: function( type, obj ) {
+			var tmp,
+				count = 1,
+				defer = jQuery.Deferred(),
+				elements = this,
+				i = this.length,
+				resolve = function() {
+					if ( !( --count ) ) {
+						defer.resolveWith( elements, [ elements ] );
+					}
+				};
+
+			if ( typeof type !== "string" ) {
+				obj = type;
+				type = undefined;
+			}
+			type = type || "fx";
+
+			while ( i-- ) {
+				tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+				if ( tmp && tmp.empty ) {
+					count++;
+					tmp.empty.add( resolve );
+				}
+			}
+			resolve();
+			return defer.promise( obj );
+		}
+	} );
+	var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+	var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+	var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+	var isHidden = function( elem, el ) {
+
+			// isHidden might be called from jQuery#filter function;
+			// in that case, element will be second argument
+			elem = el || elem;
+			return jQuery.css( elem, "display" ) === "none" ||
+				!jQuery.contains( elem.ownerDocument, elem );
+		};
+
+
+
+	function adjustCSS( elem, prop, valueParts, tween ) {
+		var adjusted,
+			scale = 1,
+			maxIterations = 20,
+			currentValue = tween ?
+				function() { return tween.cur(); } :
+				function() { return jQuery.css( elem, prop, "" ); },
+			initial = currentValue(),
+			unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+			// Starting value computation is required for potential unit mismatches
+			initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+				rcssNum.exec( jQuery.css( elem, prop ) );
+
+		if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+			// Trust units reported by jQuery.css
+			unit = unit || initialInUnit[ 3 ];
+
+			// Make sure we update the tween properties later on
+			valueParts = valueParts || [];
+
+			// Iteratively approximate from a nonzero starting point
+			initialInUnit = +initial || 1;
+
+			do {
+
+				// If previous iteration zeroed out, double until we get *something*.
+				// Use string for doubling so we don't accidentally see scale as unchanged below
+				scale = scale || ".5";
+
+				// Adjust and apply
+				initialInUnit = initialInUnit / scale;
+				jQuery.style( elem, prop, initialInUnit + unit );
+
+			// Update scale, tolerating zero or NaN from tween.cur()
+			// Break the loop if scale is unchanged or perfect, or if we've just had enough.
+			} while (
+				scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
+			);
+		}
+
+		if ( valueParts ) {
+			initialInUnit = +initialInUnit || +initial || 0;
+
+			// Apply relative offset (+=/-=) if specified
+			adjusted = valueParts[ 1 ] ?
+				initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+				+valueParts[ 2 ];
+			if ( tween ) {
+				tween.unit = unit;
+				tween.start = initialInUnit;
+				tween.end = adjusted;
+			}
+		}
+		return adjusted;
+	}
+	var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+	var rtagName = ( /<([\w:-]+)/ );
+
+	var rscriptType = ( /^$|\/(?:java|ecma)script/i );
+
+
+
+	// We have to close these tags to support XHTML (#13200)
+	var wrapMap = {
+
+		// Support: IE9
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+
+		// XHTML parsers do not magically insert elements in the
+		// same way that tag soup parsers do. So we cannot shorten
+		// this by omitting <tbody> or other required elements.
+		thead: [ 1, "<table>", "</table>" ],
+		col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+		_default: [ 0, "", "" ]
+	};
+
+	// Support: IE9
+	wrapMap.optgroup = wrapMap.option;
+
+	wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+	wrapMap.th = wrapMap.td;
+
+
+	function getAll( context, tag ) {
+
+		// Support: IE9-11+
+		// Use typeof to avoid zero-argument method invocation on host objects (#15151)
+		var ret = typeof context.getElementsByTagName !== "undefined" ?
+				context.getElementsByTagName( tag || "*" ) :
+				typeof context.querySelectorAll !== "undefined" ?
+					context.querySelectorAll( tag || "*" ) :
+				[];
+
+		return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+			jQuery.merge( [ context ], ret ) :
+			ret;
+	}
+
+
+	// Mark scripts as having already been evaluated
+	function setGlobalEval( elems, refElements ) {
+		var i = 0,
+			l = elems.length;
+
+		for ( ; i < l; i++ ) {
+			dataPriv.set(
+				elems[ i ],
+				"globalEval",
+				!refElements || dataPriv.get( refElements[ i ], "globalEval" )
+			);
+		}
+	}
+
+
+	var rhtml = /<|&#?\w+;/;
+
+	function buildFragment( elems, context, scripts, selection, ignored ) {
+		var elem, tmp, tag, wrap, contains, j,
+			fragment = context.createDocumentFragment(),
+			nodes = [],
+			i = 0,
+			l = elems.length;
+
+		for ( ; i < l; i++ ) {
+			elem = elems[ i ];
+
+			if ( elem || elem === 0 ) {
+
+				// Add nodes directly
+				if ( jQuery.type( elem ) === "object" ) {
+
+					// Support: Android<4.1, PhantomJS<2
+					// push.apply(_, arraylike) throws on ancient WebKit
+					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+				// Convert non-html into a text node
+				} else if ( !rhtml.test( elem ) ) {
+					nodes.push( context.createTextNode( elem ) );
+
+				// Convert html into DOM nodes
+				} else {
+					tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+					// Deserialize a standard representation
+					tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+					wrap = wrapMap[ tag ] || wrapMap._default;
+					tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+					// Descend through wrappers to the right content
+					j = wrap[ 0 ];
+					while ( j-- ) {
+						tmp = tmp.lastChild;
+					}
+
+					// Support: Android<4.1, PhantomJS<2
+					// push.apply(_, arraylike) throws on ancient WebKit
+					jQuery.merge( nodes, tmp.childNodes );
+
+					// Remember the top-level container
+					tmp = fragment.firstChild;
+
+					// Ensure the created nodes are orphaned (#12392)
+					tmp.textContent = "";
+				}
+			}
+		}
+
+		// Remove wrapper from fragment
+		fragment.textContent = "";
+
+		i = 0;
+		while ( ( elem = nodes[ i++ ] ) ) {
+
+			// Skip elements already in the context collection (trac-4087)
+			if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+				if ( ignored ) {
+					ignored.push( elem );
+				}
+				continue;
+			}
+
+			contains = jQuery.contains( elem.ownerDocument, elem );
+
+			// Append to fragment
+			tmp = getAll( fragment.appendChild( elem ), "script" );
+
+			// Preserve script evaluation history
+			if ( contains ) {
+				setGlobalEval( tmp );
+			}
+
+			// Capture executables
+			if ( scripts ) {
+				j = 0;
+				while ( ( elem = tmp[ j++ ] ) ) {
+					if ( rscriptType.test( elem.type || "" ) ) {
+						scripts.push( elem );
+					}
+				}
+			}
+		}
+
+		return fragment;
+	}
+
+
+	( function() {
+		var fragment = document.createDocumentFragment(),
+			div = fragment.appendChild( document.createElement( "div" ) ),
+			input = document.createElement( "input" );
+
+		// Support: Android 4.0-4.3, Safari<=5.1
+		// Check state lost if the name is set (#11217)
+		// Support: Windows Web Apps (WWA)
+		// `name` and `type` must use .setAttribute for WWA (#14901)
+		input.setAttribute( "type", "radio" );
+		input.setAttribute( "checked", "checked" );
+		input.setAttribute( "name", "t" );
+
+		div.appendChild( input );
+
+		// Support: Safari<=5.1, Android<4.2
+		// Older WebKit doesn't clone checked state correctly in fragments
+		support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+		// Support: IE<=11+
+		// Make sure textarea (and checkbox) defaultValue is properly cloned
+		div.innerHTML = "<textarea>x</textarea>";
+		support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+	} )();
+
+
+	var
+		rkeyEvent = /^key/,
+		rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+		rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+	function returnTrue() {
+		return true;
+	}
+
+	function returnFalse() {
+		return false;
+	}
+
+	// Support: IE9
+	// See #13393 for more info
+	function safeActiveElement() {
+		try {
+			return document.activeElement;
+		} catch ( err ) { }
+	}
+
+	function on( elem, types, selector, data, fn, one ) {
+		var origFn, type;
+
+		// Types can be a map of types/handlers
+		if ( typeof types === "object" ) {
+
+			// ( types-Object, selector, data )
+			if ( typeof selector !== "string" ) {
+
+				// ( types-Object, data )
+				data = data || selector;
+				selector = undefined;
+			}
+			for ( type in types ) {
+				on( elem, type, selector, data, types[ type ], one );
+			}
+			return elem;
+		}
+
+		if ( data == null && fn == null ) {
+
+			// ( types, fn )
+			fn = selector;
+			data = selector = undefined;
+		} else if ( fn == null ) {
+			if ( typeof selector === "string" ) {
+
+				// ( types, selector, fn )
+				fn = data;
+				data = undefined;
+			} else {
+
+				// ( types, data, fn )
+				fn = data;
+				data = selector;
+				selector = undefined;
+			}
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		} else if ( !fn ) {
+			return elem;
+		}
+
+		if ( one === 1 ) {
+			origFn = fn;
+			fn = function( event ) {
+
+				// Can use an empty set, since event contains the info
+				jQuery().off( event );
+				return origFn.apply( this, arguments );
+			};
+
+			// Use same guid so caller can remove using origFn
+			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+		}
+		return elem.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		} );
+	}
+
+	/*
+	 * Helper functions for managing events -- not part of the public interface.
+	 * Props to Dean Edwards' addEvent library for many of the ideas.
+	 */
+	jQuery.event = {
+
+		global: {},
+
+		add: function( elem, types, handler, data, selector ) {
+
+			var handleObjIn, eventHandle, tmp,
+				events, t, handleObj,
+				special, handlers, type, namespaces, origType,
+				elemData = dataPriv.get( elem );
+
+			// Don't attach events to noData or text/comment nodes (but allow plain objects)
+			if ( !elemData ) {
+				return;
+			}
+
+			// Caller can pass in an object of custom data in lieu of the handler
+			if ( handler.handler ) {
+				handleObjIn = handler;
+				handler = handleObjIn.handler;
+				selector = handleObjIn.selector;
+			}
+
+			// Make sure that the handler has a unique ID, used to find/remove it later
+			if ( !handler.guid ) {
+				handler.guid = jQuery.guid++;
+			}
+
+			// Init the element's event structure and main handler, if this is the first
+			if ( !( events = elemData.events ) ) {
+				events = elemData.events = {};
+			}
+			if ( !( eventHandle = elemData.handle ) ) {
+				eventHandle = elemData.handle = function( e ) {
+
+					// Discard the second event of a jQuery.event.trigger() and
+					// when an event is called after a page has unloaded
+					return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+						jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+				};
+			}
+
+			// Handle multiple events separated by a space
+			types = ( types || "" ).match( rnotwhite ) || [ "" ];
+			t = types.length;
+			while ( t-- ) {
+				tmp = rtypenamespace.exec( types[ t ] ) || [];
+				type = origType = tmp[ 1 ];
+				namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+				// There *must* be a type, no attaching namespace-only handlers
+				if ( !type ) {
+					continue;
+				}
+
+				// If event changes its type, use the special event handlers for the changed type
+				special = jQuery.event.special[ type ] || {};
+
+				// If selector defined, determine special event api type, otherwise given type
+				type = ( selector ? special.delegateType : special.bindType ) || type;
+
+				// Update special based on newly reset type
+				special = jQuery.event.special[ type ] || {};
+
+				// handleObj is passed to all event handlers
+				handleObj = jQuery.extend( {
+					type: type,
+					origType: origType,
+					data: data,
+					handler: handler,
+					guid: handler.guid,
+					selector: selector,
+					needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+					namespace: namespaces.join( "." )
+				}, handleObjIn );
+
+				// Init the event handler queue if we're the first
+				if ( !( handlers = events[ type ] ) ) {
+					handlers = events[ type ] = [];
+					handlers.delegateCount = 0;
+
+					// Only use addEventListener if the special events handler returns false
+					if ( !special.setup ||
+						special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+						if ( elem.addEventListener ) {
+							elem.addEventListener( type, eventHandle );
+						}
+					}
+				}
+
+				if ( special.add ) {
+					special.add.call( elem, handleObj );
+
+					if ( !handleObj.handler.guid ) {
+						handleObj.handler.guid = handler.guid;
+					}
+				}
+
+				// Add to the element's handler list, delegates in front
+				if ( selector ) {
+					handlers.splice( handlers.delegateCount++, 0, handleObj );
+				} else {
+					handlers.push( handleObj );
+				}
+
+				// Keep track of which events have ever been used, for event optimization
+				jQuery.event.global[ type ] = true;
+			}
+
+		},
+
+		// Detach an event or set of events from an element
+		remove: function( elem, types, handler, selector, mappedTypes ) {
+
+			var j, origCount, tmp,
+				events, t, handleObj,
+				special, handlers, type, namespaces, origType,
+				elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+			if ( !elemData || !( events = elemData.events ) ) {
+				return;
+			}
+
+			// Once for each type.namespace in types; type may be omitted
+			types = ( types || "" ).match( rnotwhite ) || [ "" ];
+			t = types.length;
+			while ( t-- ) {
+				tmp = rtypenamespace.exec( types[ t ] ) || [];
+				type = origType = tmp[ 1 ];
+				namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+				// Unbind all events (on this namespace, if provided) for the element
+				if ( !type ) {
+					for ( type in events ) {
+						jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+					}
+					continue;
+				}
+
+				special = jQuery.event.special[ type ] || {};
+				type = ( selector ? special.delegateType : special.bindType ) || type;
+				handlers = events[ type ] || [];
+				tmp = tmp[ 2 ] &&
+					new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+				// Remove matching events
+				origCount = j = handlers.length;
+				while ( j-- ) {
+					handleObj = handlers[ j ];
+
+					if ( ( mappedTypes || origType === handleObj.origType ) &&
+						( !handler || handler.guid === handleObj.guid ) &&
+						( !tmp || tmp.test( handleObj.namespace ) ) &&
+						( !selector || selector === handleObj.selector ||
+							selector === "**" && handleObj.selector ) ) {
+						handlers.splice( j, 1 );
+
+						if ( handleObj.selector ) {
+							handlers.delegateCount--;
+						}
+						if ( special.remove ) {
+							special.remove.call( elem, handleObj );
+						}
+					}
+				}
+
+				// Remove generic event handler if we removed something and no more handlers exist
+				// (avoids potential for endless recursion during removal of special event handlers)
+				if ( origCount && !handlers.length ) {
+					if ( !special.teardown ||
+						special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+						jQuery.removeEvent( elem, type, elemData.handle );
+					}
+
+					delete events[ type ];
+				}
+			}
+
+			// Remove data and the expando if it's no longer used
+			if ( jQuery.isEmptyObject( events ) ) {
+				dataPriv.remove( elem, "handle events" );
+			}
+		},
+
+		dispatch: function( event ) {
+
+			// Make a writable jQuery.Event from the native event object
+			event = jQuery.event.fix( event );
+
+			var i, j, ret, matched, handleObj,
+				handlerQueue = [],
+				args = slice.call( arguments ),
+				handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
+				special = jQuery.event.special[ event.type ] || {};
+
+			// Use the fix-ed jQuery.Event rather than the (read-only) native event
+			args[ 0 ] = event;
+			event.delegateTarget = this;
+
+			// Call the preDispatch hook for the mapped type, and let it bail if desired
+			if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+				return;
+			}
+
+			// Determine handlers
+			handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+			// Run delegates first; they may want to stop propagation beneath us
+			i = 0;
+			while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+				event.currentTarget = matched.elem;
+
+				j = 0;
+				while ( ( handleObj = matched.handlers[ j++ ] ) &&
+					!event.isImmediatePropagationStopped() ) {
+
+					// Triggered event must either 1) have no namespace, or 2) have namespace(s)
+					// a subset or equal to those in the bound event (both can have no namespace).
+					if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
+
+						event.handleObj = handleObj;
+						event.data = handleObj.data;
+
+						ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+							handleObj.handler ).apply( matched.elem, args );
+
+						if ( ret !== undefined ) {
+							if ( ( event.result = ret ) === false ) {
+								event.preventDefault();
+								event.stopPropagation();
+							}
+						}
+					}
+				}
+			}
+
+			// Call the postDispatch hook for the mapped type
+			if ( special.postDispatch ) {
+				special.postDispatch.call( this, event );
+			}
+
+			return event.result;
+		},
+
+		handlers: function( event, handlers ) {
+			var i, matches, sel, handleObj,
+				handlerQueue = [],
+				delegateCount = handlers.delegateCount,
+				cur = event.target;
+
+			// Support (at least): Chrome, IE9
+			// Find delegate handlers
+			// Black-hole SVG <use> instance trees (#13180)
+			//
+			// Support: Firefox<=42+
+			// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
+			if ( delegateCount && cur.nodeType &&
+				( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {
+
+				for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+					// Don't check non-elements (#13208)
+					// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+					if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
+						matches = [];
+						for ( i = 0; i < delegateCount; i++ ) {
+							handleObj = handlers[ i ];
+
+							// Don't conflict with Object.prototype properties (#13203)
+							sel = handleObj.selector + " ";
+
+							if ( matches[ sel ] === undefined ) {
+								matches[ sel ] = handleObj.needsContext ?
+									jQuery( sel, this ).index( cur ) > -1 :
+									jQuery.find( sel, this, null, [ cur ] ).length;
+							}
+							if ( matches[ sel ] ) {
+								matches.push( handleObj );
+							}
+						}
+						if ( matches.length ) {
+							handlerQueue.push( { elem: cur, handlers: matches } );
+						}
+					}
+				}
+			}
+
+			// Add the remaining (directly-bound) handlers
+			if ( delegateCount < handlers.length ) {
+				handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
+			}
+
+			return handlerQueue;
+		},
+
+		// Includes some event props shared by KeyEvent and MouseEvent
+		props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
+			"metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ),
+
+		fixHooks: {},
+
+		keyHooks: {
+			props: "char charCode key keyCode".split( " " ),
+			filter: function( event, original ) {
+
+				// Add which for key events
+				if ( event.which == null ) {
+					event.which = original.charCode != null ? original.charCode : original.keyCode;
+				}
+
+				return event;
+			}
+		},
+
+		mouseHooks: {
+			props: ( "button buttons clientX clientY offsetX offsetY pageX pageY " +
+				"screenX screenY toElement" ).split( " " ),
+			filter: function( event, original ) {
+				var eventDoc, doc, body,
+					button = original.button;
+
+				// Calculate pageX/Y if missing and clientX/Y available
+				if ( event.pageX == null && original.clientX != null ) {
+					eventDoc = event.target.ownerDocument || document;
+					doc = eventDoc.documentElement;
+					body = eventDoc.body;
+
+					event.pageX = original.clientX +
+						( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
+						( doc && doc.clientLeft || body && body.clientLeft || 0 );
+					event.pageY = original.clientY +
+						( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
+						( doc && doc.clientTop  || body && body.clientTop  || 0 );
+				}
+
+				// Add which for click: 1 === left; 2 === middle; 3 === right
+				// Note: button is not normalized, so don't use it
+				if ( !event.which && button !== undefined ) {
+					event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+				}
+
+				return event;
+			}
+		},
+
+		fix: function( event ) {
+			if ( event[ jQuery.expando ] ) {
+				return event;
+			}
+
+			// Create a writable copy of the event object and normalize some properties
+			var i, prop, copy,
+				type = event.type,
+				originalEvent = event,
+				fixHook = this.fixHooks[ type ];
+
+			if ( !fixHook ) {
+				this.fixHooks[ type ] = fixHook =
+					rmouseEvent.test( type ) ? this.mouseHooks :
+					rkeyEvent.test( type ) ? this.keyHooks :
+					{};
+			}
+			copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+			event = new jQuery.Event( originalEvent );
+
+			i = copy.length;
+			while ( i-- ) {
+				prop = copy[ i ];
+				event[ prop ] = originalEvent[ prop ];
+			}
+
+			// Support: Cordova 2.5 (WebKit) (#13255)
+			// All events should have a target; Cordova deviceready doesn't
+			if ( !event.target ) {
+				event.target = document;
+			}
+
+			// Support: Safari 6.0+, Chrome<28
+			// Target should not be a text node (#504, #13143)
+			if ( event.target.nodeType === 3 ) {
+				event.target = event.target.parentNode;
+			}
+
+			return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+		},
+
+		special: {
+			load: {
+
+				// Prevent triggered image.load events from bubbling to window.load
+				noBubble: true
+			},
+			focus: {
+
+				// Fire native event if possible so blur/focus sequence is correct
+				trigger: function() {
+					if ( this !== safeActiveElement() && this.focus ) {
+						this.focus();
+						return false;
+					}
+				},
+				delegateType: "focusin"
+			},
+			blur: {
+				trigger: function() {
+					if ( this === safeActiveElement() && this.blur ) {
+						this.blur();
+						return false;
+					}
+				},
+				delegateType: "focusout"
+			},
+			click: {
+
+				// For checkbox, fire native event so checked state will be right
+				trigger: function() {
+					if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+						this.click();
+						return false;
+					}
+				},
+
+				// For cross-browser consistency, don't fire native .click() on links
+				_default: function( event ) {
+					return jQuery.nodeName( event.target, "a" );
+				}
+			},
+
+			beforeunload: {
+				postDispatch: function( event ) {
+
+					// Support: Firefox 20+
+					// Firefox doesn't alert if the returnValue field is not set.
+					if ( event.result !== undefined && event.originalEvent ) {
+						event.originalEvent.returnValue = event.result;
+					}
+				}
+			}
+		}
+	};
+
+	jQuery.removeEvent = function( elem, type, handle ) {
+
+		// This "if" is needed for plain objects
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle );
+		}
+	};
+
+	jQuery.Event = function( src, props ) {
+
+		// Allow instantiation without the 'new' keyword
+		if ( !( this instanceof jQuery.Event ) ) {
+			return new jQuery.Event( src, props );
+		}
+
+		// Event object
+		if ( src && src.type ) {
+			this.originalEvent = src;
+			this.type = src.type;
+
+			// Events bubbling up the document may have been marked as prevented
+			// by a handler lower down the tree; reflect the correct value.
+			this.isDefaultPrevented = src.defaultPrevented ||
+					src.defaultPrevented === undefined &&
+
+					// Support: Android<4.0
+					src.returnValue === false ?
+				returnTrue :
+				returnFalse;
+
+		// Event type
+		} else {
+			this.type = src;
+		}
+
+		// Put explicitly provided properties onto the event object
+		if ( props ) {
+			jQuery.extend( this, props );
+		}
+
+		// Create a timestamp if incoming event doesn't have one
+		this.timeStamp = src && src.timeStamp || jQuery.now();
+
+		// Mark it as fixed
+		this[ jQuery.expando ] = true;
+	};
+
+	// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+	// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+	jQuery.Event.prototype = {
+		constructor: jQuery.Event,
+		isDefaultPrevented: returnFalse,
+		isPropagationStopped: returnFalse,
+		isImmediatePropagationStopped: returnFalse,
+		isSimulated: false,
+
+		preventDefault: function() {
+			var e = this.originalEvent;
+
+			this.isDefaultPrevented = returnTrue;
+
+			if ( e && !this.isSimulated ) {
+				e.preventDefault();
+			}
+		},
+		stopPropagation: function() {
+			var e = this.originalEvent;
+
+			this.isPropagationStopped = returnTrue;
+
+			if ( e && !this.isSimulated ) {
+				e.stopPropagation();
+			}
+		},
+		stopImmediatePropagation: function() {
+			var e = this.originalEvent;
+
+			this.isImmediatePropagationStopped = returnTrue;
+
+			if ( e && !this.isSimulated ) {
+				e.stopImmediatePropagation();
+			}
+
+			this.stopPropagation();
+		}
+	};
+
+	// Create mouseenter/leave events using mouseover/out and event-time checks
+	// so that event delegation works in jQuery.
+	// Do the same for pointerenter/pointerleave and pointerover/pointerout
+	//
+	// Support: Safari 7 only
+	// Safari sends mouseenter too often; see:
+	// https://code.google.com/p/chromium/issues/detail?id=470258
+	// for the description of the bug (it existed in older Chrome versions as well).
+	jQuery.each( {
+		mouseenter: "mouseover",
+		mouseleave: "mouseout",
+		pointerenter: "pointerover",
+		pointerleave: "pointerout"
+	}, function( orig, fix ) {
+		jQuery.event.special[ orig ] = {
+			delegateType: fix,
+			bindType: fix,
+
+			handle: function( event ) {
+				var ret,
+					target = this,
+					related = event.relatedTarget,
+					handleObj = event.handleObj;
+
+				// For mouseenter/leave call the handler if related is outside the target.
+				// NB: No relatedTarget if the mouse left/entered the browser window
+				if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+					event.type = handleObj.origType;
+					ret = handleObj.handler.apply( this, arguments );
+					event.type = fix;
+				}
+				return ret;
+			}
+		};
+	} );
+
+	jQuery.fn.extend( {
+		on: function( types, selector, data, fn ) {
+			return on( this, types, selector, data, fn );
+		},
+		one: function( types, selector, data, fn ) {
+			return on( this, types, selector, data, fn, 1 );
+		},
+		off: function( types, selector, fn ) {
+			var handleObj, type;
+			if ( types && types.preventDefault && types.handleObj ) {
+
+				// ( event )  dispatched jQuery.Event
+				handleObj = types.handleObj;
+				jQuery( types.delegateTarget ).off(
+					handleObj.namespace ?
+						handleObj.origType + "." + handleObj.namespace :
+						handleObj.origType,
+					handleObj.selector,
+					handleObj.handler
+				);
+				return this;
+			}
+			if ( typeof types === "object" ) {
+
+				// ( types-object [, selector] )
+				for ( type in types ) {
+					this.off( type, selector, types[ type ] );
+				}
+				return this;
+			}
+			if ( selector === false || typeof selector === "function" ) {
+
+				// ( types [, fn] )
+				fn = selector;
+				selector = undefined;
+			}
+			if ( fn === false ) {
+				fn = returnFalse;
+			}
+			return this.each( function() {
+				jQuery.event.remove( this, types, fn, selector );
+			} );
+		}
+	} );
+
+
+	var
+		rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
+
+		// Support: IE 10-11, Edge 10240+
+		// In IE/Edge using regex groups here causes severe slowdowns.
+		// See https://connect.microsoft.com/IE/feedback/details/1736512/
+		rnoInnerhtml = /<script|<style|<link/i,
+
+		// checked="checked" or checked
+		rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+		rscriptTypeMasked = /^true\/(.*)/,
+		rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
+
+	// Manipulating tables requires a tbody
+	function manipulationTarget( elem, content ) {
+		return jQuery.nodeName( elem, "table" ) &&
+			jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
+
+			elem.getElementsByTagName( "tbody" )[ 0 ] ||
+				elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) :
+			elem;
+	}
+
+	// Replace/restore the type attribute of script elements for safe DOM manipulation
+	function disableScript( elem ) {
+		elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
+		return elem;
+	}
+	function restoreScript( elem ) {
+		var match = rscriptTypeMasked.exec( elem.type );
+
+		if ( match ) {
+			elem.type = match[ 1 ];
+		} else {
+			elem.removeAttribute( "type" );
+		}
+
+		return elem;
+	}
+
+	function cloneCopyEvent( src, dest ) {
+		var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
+
+		if ( dest.nodeType !== 1 ) {
+			return;
+		}
+
+		// 1. Copy private data: events, handlers, etc.
+		if ( dataPriv.hasData( src ) ) {
+			pdataOld = dataPriv.access( src );
+			pdataCur = dataPriv.set( dest, pdataOld );
+			events = pdataOld.events;
+
+			if ( events ) {
+				delete pdataCur.handle;
+				pdataCur.events = {};
+
+				for ( type in events ) {
+					for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+						jQuery.event.add( dest, type, events[ type ][ i ] );
+					}
+				}
+			}
+		}
+
+		// 2. Copy user data
+		if ( dataUser.hasData( src ) ) {
+			udataOld = dataUser.access( src );
+			udataCur = jQuery.extend( {}, udataOld );
+
+			dataUser.set( dest, udataCur );
+		}
+	}
+
+	// Fix IE bugs, see support tests
+	function fixInput( src, dest ) {
+		var nodeName = dest.nodeName.toLowerCase();
+
+		// Fails to persist the checked state of a cloned checkbox or radio button.
+		if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+			dest.checked = src.checked;
+
+		// Fails to return the selected option to the default selected state when cloning options
+		} else if ( nodeName === "input" || nodeName === "textarea" ) {
+			dest.defaultValue = src.defaultValue;
+		}
+	}
+
+	function domManip( collection, args, callback, ignored ) {
+
+		// Flatten any nested arrays
+		args = concat.apply( [], args );
+
+		var fragment, first, scripts, hasScripts, node, doc,
+			i = 0,
+			l = collection.length,
+			iNoClone = l - 1,
+			value = args[ 0 ],
+			isFunction = jQuery.isFunction( value );
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( isFunction ||
+				( l > 1 && typeof value === "string" &&
+					!support.checkClone && rchecked.test( value ) ) ) {
+			return collection.each( function( index ) {
+				var self = collection.eq( index );
+				if ( isFunction ) {
+					args[ 0 ] = value.call( this, index, self.html() );
+				}
+				domManip( self, args, callback, ignored );
+			} );
+		}
+
+		if ( l ) {
+			fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
+			first = fragment.firstChild;
+
+			if ( fragment.childNodes.length === 1 ) {
+				fragment = first;
+			}
+
+			// Require either new content or an interest in ignored elements to invoke the callback
+			if ( first || ignored ) {
+				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+				hasScripts = scripts.length;
+
+				// Use the original fragment for the last item
+				// instead of the first because it can end up
+				// being emptied incorrectly in certain situations (#8070).
+				for ( ; i < l; i++ ) {
+					node = fragment;
+
+					if ( i !== iNoClone ) {
+						node = jQuery.clone( node, true, true );
+
+						// Keep references to cloned scripts for later restoration
+						if ( hasScripts ) {
+
+							// Support: Android<4.1, PhantomJS<2
+							// push.apply(_, arraylike) throws on ancient WebKit
+							jQuery.merge( scripts, getAll( node, "script" ) );
+						}
+					}
+
+					callback.call( collection[ i ], node, i );
+				}
+
+				if ( hasScripts ) {
+					doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+					// Reenable scripts
+					jQuery.map( scripts, restoreScript );
+
+					// Evaluate executable scripts on first document insertion
+					for ( i = 0; i < hasScripts; i++ ) {
+						node = scripts[ i ];
+						if ( rscriptType.test( node.type || "" ) &&
+							!dataPriv.access( node, "globalEval" ) &&
+							jQuery.contains( doc, node ) ) {
+
+							if ( node.src ) {
+
+								// Optional AJAX dependency, but won't run scripts if not present
+								if ( jQuery._evalUrl ) {
+									jQuery._evalUrl( node.src );
+								}
+							} else {
+								jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return collection;
+	}
+
+	function remove( elem, selector, keepData ) {
+		var node,
+			nodes = selector ? jQuery.filter( selector, elem ) : elem,
+			i = 0;
+
+		for ( ; ( node = nodes[ i ] ) != null; i++ ) {
+			if ( !keepData && node.nodeType === 1 ) {
+				jQuery.cleanData( getAll( node ) );
+			}
+
+			if ( node.parentNode ) {
+				if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
+					setGlobalEval( getAll( node, "script" ) );
+				}
+				node.parentNode.removeChild( node );
+			}
+		}
+
+		return elem;
+	}
+
+	jQuery.extend( {
+		htmlPrefilter: function( html ) {
+			return html.replace( rxhtmlTag, "<$1></$2>" );
+		},
+
+		clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+			var i, l, srcElements, destElements,
+				clone = elem.cloneNode( true ),
+				inPage = jQuery.contains( elem.ownerDocument, elem );
+
+			// Fix IE cloning issues
+			if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+					!jQuery.isXMLDoc( elem ) ) {
+
+				// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+				destElements = getAll( clone );
+				srcElements = getAll( elem );
+
+				for ( i = 0, l = srcElements.length; i < l; i++ ) {
+					fixInput( srcElements[ i ], destElements[ i ] );
+				}
+			}
+
+			// Copy the events from the original to the clone
+			if ( dataAndEvents ) {
+				if ( deepDataAndEvents ) {
+					srcElements = srcElements || getAll( elem );
+					destElements = destElements || getAll( clone );
+
+					for ( i = 0, l = srcElements.length; i < l; i++ ) {
+						cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+					}
+				} else {
+					cloneCopyEvent( elem, clone );
+				}
+			}
+
+			// Preserve script evaluation history
+			destElements = getAll( clone, "script" );
+			if ( destElements.length > 0 ) {
+				setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+			}
+
+			// Return the cloned set
+			return clone;
+		},
+
+		cleanData: function( elems ) {
+			var data, elem, type,
+				special = jQuery.event.special,
+				i = 0;
+
+			for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
+				if ( acceptData( elem ) ) {
+					if ( ( data = elem[ dataPriv.expando ] ) ) {
+						if ( data.events ) {
+							for ( type in data.events ) {
+								if ( special[ type ] ) {
+									jQuery.event.remove( elem, type );
+
+								// This is a shortcut to avoid jQuery.event.remove's overhead
+								} else {
+									jQuery.removeEvent( elem, type, data.handle );
+								}
+							}
+						}
+
+						// Support: Chrome <= 35-45+
+						// Assign undefined instead of using delete, see Data#remove
+						elem[ dataPriv.expando ] = undefined;
+					}
+					if ( elem[ dataUser.expando ] ) {
+
+						// Support: Chrome <= 35-45+
+						// Assign undefined instead of using delete, see Data#remove
+						elem[ dataUser.expando ] = undefined;
+					}
+				}
+			}
+		}
+	} );
+
+	jQuery.fn.extend( {
+
+		// Keep domManip exposed until 3.0 (gh-2225)
+		domManip: domManip,
+
+		detach: function( selector ) {
+			return remove( this, selector, true );
+		},
+
+		remove: function( selector ) {
+			return remove( this, selector );
+		},
+
+		text: function( value ) {
+			return access( this, function( value ) {
+				return value === undefined ?
+					jQuery.text( this ) :
+					this.empty().each( function() {
+						if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+							this.textContent = value;
+						}
+					} );
+			}, null, value, arguments.length );
+		},
+
+		append: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+					var target = manipulationTarget( this, elem );
+					target.appendChild( elem );
+				}
+			} );
+		},
+
+		prepend: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+					var target = manipulationTarget( this, elem );
+					target.insertBefore( elem, target.firstChild );
+				}
+			} );
+		},
+
+		before: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.parentNode ) {
+					this.parentNode.insertBefore( elem, this );
+				}
+			} );
+		},
+
+		after: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.parentNode ) {
+					this.parentNode.insertBefore( elem, this.nextSibling );
+				}
+			} );
+		},
+
+		empty: function() {
+			var elem,
+				i = 0;
+
+			for ( ; ( elem = this[ i ] ) != null; i++ ) {
+				if ( elem.nodeType === 1 ) {
+
+					// Prevent memory leaks
+					jQuery.cleanData( getAll( elem, false ) );
+
+					// Remove any remaining nodes
+					elem.textContent = "";
+				}
+			}
+
+			return this;
+		},
+
+		clone: function( dataAndEvents, deepDataAndEvents ) {
+			dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+			deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+			return this.map( function() {
+				return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+			} );
+		},
+
+		html: function( value ) {
+			return access( this, function( value ) {
+				var elem = this[ 0 ] || {},
+					i = 0,
+					l = this.length;
+
+				if ( value === undefined && elem.nodeType === 1 ) {
+					return elem.innerHTML;
+				}
+
+				// See if we can take a shortcut and just use innerHTML
+				if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+					!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+					value = jQuery.htmlPrefilter( value );
+
+					try {
+						for ( ; i < l; i++ ) {
+							elem = this[ i ] || {};
+
+							// Remove element nodes and prevent memory leaks
+							if ( elem.nodeType === 1 ) {
+								jQuery.cleanData( getAll( elem, false ) );
+								elem.innerHTML = value;
+							}
+						}
+
+						elem = 0;
+
+					// If using innerHTML throws an exception, use the fallback method
+					} catch ( e ) {}
+				}
+
+				if ( elem ) {
+					this.empty().append( value );
+				}
+			}, null, value, arguments.length );
+		},
+
+		replaceWith: function() {
+			var ignored = [];
+
+			// Make the changes, replacing each non-ignored context element with the new content
+			return domManip( this, arguments, function( elem ) {
+				var parent = this.parentNode;
+
+				if ( jQuery.inArray( this, ignored ) < 0 ) {
+					jQuery.cleanData( getAll( this ) );
+					if ( parent ) {
+						parent.replaceChild( elem, this );
+					}
+				}
+
+			// Force callback invocation
+			}, ignored );
+		}
+	} );
+
+	jQuery.each( {
+		appendTo: "append",
+		prependTo: "prepend",
+		insertBefore: "before",
+		insertAfter: "after",
+		replaceAll: "replaceWith"
+	}, function( name, original ) {
+		jQuery.fn[ name ] = function( selector ) {
+			var elems,
+				ret = [],
+				insert = jQuery( selector ),
+				last = insert.length - 1,
+				i = 0;
+
+			for ( ; i <= last; i++ ) {
+				elems = i === last ? this : this.clone( true );
+				jQuery( insert[ i ] )[ original ]( elems );
+
+				// Support: QtWebKit
+				// .get() because push.apply(_, arraylike) throws
+				push.apply( ret, elems.get() );
+			}
+
+			return this.pushStack( ret );
+		};
+	} );
+
+
+	var iframe,
+		elemdisplay = {
+
+			// Support: Firefox
+			// We have to pre-define these values for FF (#10227)
+			HTML: "block",
+			BODY: "block"
+		};
+
+	/**
+	 * Retrieve the actual display of a element
+	 * @param {String} name nodeName of the element
+	 * @param {Object} doc Document object
+	 */
+
+	// Called only from within defaultDisplay
+	function actualDisplay( name, doc ) {
+		var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+
+			display = jQuery.css( elem[ 0 ], "display" );
+
+		// We don't have any data stored on the element,
+		// so use "detach" method as fast way to get rid of the element
+		elem.detach();
+
+		return display;
+	}
+
+	/**
+	 * Try to determine the default display value of an element
+	 * @param {String} nodeName
+	 */
+	function defaultDisplay( nodeName ) {
+		var doc = document,
+			display = elemdisplay[ nodeName ];
+
+		if ( !display ) {
+			display = actualDisplay( nodeName, doc );
+
+			// If the simple way fails, read from inside an iframe
+			if ( display === "none" || !display ) {
+
+				// Use the already-created iframe if possible
+				iframe = ( iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" ) )
+					.appendTo( doc.documentElement );
+
+				// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+				doc = iframe[ 0 ].contentDocument;
+
+				// Support: IE
+				doc.write();
+				doc.close();
+
+				display = actualDisplay( nodeName, doc );
+				iframe.detach();
+			}
+
+			// Store the correct default display
+			elemdisplay[ nodeName ] = display;
+		}
+
+		return display;
+	}
+	var rmargin = ( /^margin/ );
+
+	var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+	var getStyles = function( elem ) {
+
+			// Support: IE<=11+, Firefox<=30+ (#15098, #14150)
+			// IE throws on elements created in popups
+			// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+			var view = elem.ownerDocument.defaultView;
+
+			if ( !view || !view.opener ) {
+				view = window;
+			}
+
+			return view.getComputedStyle( elem );
+		};
+
+	var swap = function( elem, options, callback, args ) {
+		var ret, name,
+			old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		ret = callback.apply( elem, args || [] );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+
+		return ret;
+	};
+
+
+	var documentElement = document.documentElement;
+
+
+
+	( function() {
+		var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal,
+			container = document.createElement( "div" ),
+			div = document.createElement( "div" );
+
+		// Finish early in limited (non-browser) environments
+		if ( !div.style ) {
+			return;
+		}
+
+		// Support: IE9-11+
+		// Style of cloned element affects source element cloned (#8908)
+		div.style.backgroundClip = "content-box";
+		div.cloneNode( true ).style.backgroundClip = "";
+		support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+		container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
+			"padding:0;margin-top:1px;position:absolute";
+		container.appendChild( div );
+
+		// Executing both pixelPosition & boxSizingReliable tests require only one layout
+		// so they're executed at the same time to save the second computation.
+		function computeStyleTests() {
+			div.style.cssText =
+
+				// Support: Firefox<29, Android 2.3
+				// Vendor-prefix box-sizing
+				"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;" +
+				"position:relative;display:block;" +
+				"margin:auto;border:1px;padding:1px;" +
+				"top:1%;width:50%";
+			div.innerHTML = "";
+			documentElement.appendChild( container );
+
+			var divStyle = window.getComputedStyle( div );
+			pixelPositionVal = divStyle.top !== "1%";
+			reliableMarginLeftVal = divStyle.marginLeft === "2px";
+			boxSizingReliableVal = divStyle.width === "4px";
+
+			// Support: Android 4.0 - 4.3 only
+			// Some styles come back with percentage values, even though they shouldn't
+			div.style.marginRight = "50%";
+			pixelMarginRightVal = divStyle.marginRight === "4px";
+
+			documentElement.removeChild( container );
+		}
+
+		jQuery.extend( support, {
+			pixelPosition: function() {
+
+				// This test is executed only once but we still do memoizing
+				// since we can use the boxSizingReliable pre-computing.
+				// No need to check if the test was already performed, though.
+				computeStyleTests();
+				return pixelPositionVal;
+			},
+			boxSizingReliable: function() {
+				if ( boxSizingReliableVal == null ) {
+					computeStyleTests();
+				}
+				return boxSizingReliableVal;
+			},
+			pixelMarginRight: function() {
+
+				// Support: Android 4.0-4.3
+				// We're checking for boxSizingReliableVal here instead of pixelMarginRightVal
+				// since that compresses better and they're computed together anyway.
+				if ( boxSizingReliableVal == null ) {
+					computeStyleTests();
+				}
+				return pixelMarginRightVal;
+			},
+			reliableMarginLeft: function() {
+
+				// Support: IE <=8 only, Android 4.0 - 4.3 only, Firefox <=3 - 37
+				if ( boxSizingReliableVal == null ) {
+					computeStyleTests();
+				}
+				return reliableMarginLeftVal;
+			},
+			reliableMarginRight: function() {
+
+				// Support: Android 2.3
+				// Check if div with explicit width and no margin-right incorrectly
+				// gets computed margin-right based on width of container. (#3333)
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// This support function is only executed once so no memoizing is needed.
+				var ret,
+					marginDiv = div.appendChild( document.createElement( "div" ) );
+
+				// Reset CSS: box-sizing; display; margin; border; padding
+				marginDiv.style.cssText = div.style.cssText =
+
+					// Support: Android 2.3
+					// Vendor-prefix box-sizing
+					"-webkit-box-sizing:content-box;box-sizing:content-box;" +
+					"display:block;margin:0;border:0;padding:0";
+				marginDiv.style.marginRight = marginDiv.style.width = "0";
+				div.style.width = "1px";
+				documentElement.appendChild( container );
+
+				ret = !parseFloat( window.getComputedStyle( marginDiv ).marginRight );
+
+				documentElement.removeChild( container );
+				div.removeChild( marginDiv );
+
+				return ret;
+			}
+		} );
+	} )();
+
+
+	function curCSS( elem, name, computed ) {
+		var width, minWidth, maxWidth, ret,
+			style = elem.style;
+
+		computed = computed || getStyles( elem );
+		ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;
+
+		// Support: Opera 12.1x only
+		// Fall back to style even without computed
+		// computed is undefined for elems on document fragments
+		if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) {
+			ret = jQuery.style( elem, name );
+		}
+
+		// Support: IE9
+		// getPropertyValue is only needed for .css('filter') (#12537)
+		if ( computed ) {
+
+			// A tribute to the "awesome hack by Dean Edwards"
+			// Android Browser returns percentage for some values,
+			// but width seems to be reliably pixels.
+			// This is against the CSSOM draft spec:
+			// http://dev.w3.org/csswg/cssom/#resolved-values
+			if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+				// Remember the original values
+				width = style.width;
+				minWidth = style.minWidth;
+				maxWidth = style.maxWidth;
+
+				// Put in the new values to get a computed value out
+				style.minWidth = style.maxWidth = style.width = ret;
+				ret = computed.width;
+
+				// Revert the changed values
+				style.width = width;
+				style.minWidth = minWidth;
+				style.maxWidth = maxWidth;
+			}
+		}
+
+		return ret !== undefined ?
+
+			// Support: IE9-11+
+			// IE returns zIndex value as an integer.
+			ret + "" :
+			ret;
+	}
+
+
+	function addGetHookIf( conditionFn, hookFn ) {
+
+		// Define the hook, we'll check on the first run if it's really needed.
+		return {
+			get: function() {
+				if ( conditionFn() ) {
+
+					// Hook not needed (or it's not possible to use it due
+					// to missing dependency), remove it.
+					delete this.get;
+					return;
+				}
+
+				// Hook needed; redefine it so that the support test is not executed again.
+				return ( this.get = hookFn ).apply( this, arguments );
+			}
+		};
+	}
+
+
+	var
+
+		// Swappable if display is none or starts with table
+		// except "table", "table-cell", or "table-caption"
+		// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+		rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+
+		cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+		cssNormalTransform = {
+			letterSpacing: "0",
+			fontWeight: "400"
+		},
+
+		cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+		emptyStyle = document.createElement( "div" ).style;
+
+	// Return a css property mapped to a potentially vendor prefixed property
+	function vendorPropName( name ) {
+
+		// Shortcut for names that are not vendor prefixed
+		if ( name in emptyStyle ) {
+			return name;
+		}
+
+		// Check for vendor prefixed names
+		var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
+			i = cssPrefixes.length;
+
+		while ( i-- ) {
+			name = cssPrefixes[ i ] + capName;
+			if ( name in emptyStyle ) {
+				return name;
+			}
+		}
+	}
+
+	function setPositiveNumber( elem, value, subtract ) {
+
+		// Any relative (+/-) values have already been
+		// normalized at this point
+		var matches = rcssNum.exec( value );
+		return matches ?
+
+			// Guard against undefined "subtract", e.g., when used as in cssHooks
+			Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
+			value;
+	}
+
+	function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+		var i = extra === ( isBorderBox ? "border" : "content" ) ?
+
+			// If we already have the right measurement, avoid augmentation
+			4 :
+
+			// Otherwise initialize for horizontal or vertical properties
+			name === "width" ? 1 : 0,
+
+			val = 0;
+
+		for ( ; i < 4; i += 2 ) {
+
+			// Both box models exclude margin, so add it if we want it
+			if ( extra === "margin" ) {
+				val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+			}
+
+			if ( isBorderBox ) {
+
+				// border-box includes padding, so remove it if we want content
+				if ( extra === "content" ) {
+					val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+				}
+
+				// At this point, extra isn't border nor margin, so remove border
+				if ( extra !== "margin" ) {
+					val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+				}
+			} else {
+
+				// At this point, extra isn't content, so add padding
+				val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+				// At this point, extra isn't content nor padding, so add border
+				if ( extra !== "padding" ) {
+					val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+				}
+			}
+		}
+
+		return val;
+	}
+
+	function getWidthOrHeight( elem, name, extra ) {
+
+		// Start with offset property, which is equivalent to the border-box value
+		var valueIsBorderBox = true,
+			val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+			styles = getStyles( elem ),
+			isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+		// Some non-html elements return undefined for offsetWidth, so check for null/undefined
+		// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+		// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+		if ( val <= 0 || val == null ) {
+
+			// Fall back to computed then uncomputed css if necessary
+			val = curCSS( elem, name, styles );
+			if ( val < 0 || val == null ) {
+				val = elem.style[ name ];
+			}
+
+			// Computed unit is not pixels. Stop here and return.
+			if ( rnumnonpx.test( val ) ) {
+				return val;
+			}
+
+			// Check for style in case a browser which returns unreliable values
+			// for getComputedStyle silently falls back to the reliable elem.style
+			valueIsBorderBox = isBorderBox &&
+				( support.boxSizingReliable() || val === elem.style[ name ] );
+
+			// Normalize "", auto, and prepare for extra
+			val = parseFloat( val ) || 0;
+		}
+
+		// Use the active box-sizing model to add/subtract irrelevant styles
+		return ( val +
+			augmentWidthOrHeight(
+				elem,
+				name,
+				extra || ( isBorderBox ? "border" : "content" ),
+				valueIsBorderBox,
+				styles
+			)
+		) + "px";
+	}
+
+	function showHide( elements, show ) {
+		var display, elem, hidden,
+			values = [],
+			index = 0,
+			length = elements.length;
+
+		for ( ; index < length; index++ ) {
+			elem = elements[ index ];
+			if ( !elem.style ) {
+				continue;
+			}
+
+			values[ index ] = dataPriv.get( elem, "olddisplay" );
+			display = elem.style.display;
+			if ( show ) {
+
+				// Reset the inline display of this element to learn if it is
+				// being hidden by cascaded rules or not
+				if ( !values[ index ] && display === "none" ) {
+					elem.style.display = "";
+				}
+
+				// Set elements which have been overridden with display: none
+				// in a stylesheet to whatever the default browser style is
+				// for such an element
+				if ( elem.style.display === "" && isHidden( elem ) ) {
+					values[ index ] = dataPriv.access(
+						elem,
+						"olddisplay",
+						defaultDisplay( elem.nodeName )
+					);
+				}
+			} else {
+				hidden = isHidden( elem );
+
+				if ( display !== "none" || !hidden ) {
+					dataPriv.set(
+						elem,
+						"olddisplay",
+						hidden ? display : jQuery.css( elem, "display" )
+					);
+				}
+			}
+		}
+
+		// Set the display of most of the elements in a second loop
+		// to avoid the constant reflow
+		for ( index = 0; index < length; index++ ) {
+			elem = elements[ index ];
+			if ( !elem.style ) {
+				continue;
+			}
+			if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+				elem.style.display = show ? values[ index ] || "" : "none";
+			}
+		}
+
+		return elements;
+	}
+
+	jQuery.extend( {
+
+		// Add in style property hooks for overriding the default
+		// behavior of getting and setting a style property
+		cssHooks: {
+			opacity: {
+				get: function( elem, computed ) {
+					if ( computed ) {
+
+						// We should always get a number back from opacity
+						var ret = curCSS( elem, "opacity" );
+						return ret === "" ? "1" : ret;
+					}
+				}
+			}
+		},
+
+		// Don't automatically add "px" to these possibly-unitless properties
+		cssNumber: {
+			"animationIterationCount": true,
+			"columnCount": true,
+			"fillOpacity": true,
+			"flexGrow": true,
+			"flexShrink": true,
+			"fontWeight": true,
+			"lineHeight": true,
+			"opacity": true,
+			"order": true,
+			"orphans": true,
+			"widows": true,
+			"zIndex": true,
+			"zoom": true
+		},
+
+		// Add in properties whose names you wish to fix before
+		// setting or getting the value
+		cssProps: {
+			"float": "cssFloat"
+		},
+
+		// Get and set the style property on a DOM Node
+		style: function( elem, name, value, extra ) {
+
+			// Don't set styles on text and comment nodes
+			if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+				return;
+			}
+
+			// Make sure that we're working with the right name
+			var ret, type, hooks,
+				origName = jQuery.camelCase( name ),
+				style = elem.style;
+
+			name = jQuery.cssProps[ origName ] ||
+				( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+
+			// Gets hook for the prefixed version, then unprefixed version
+			hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+			// Check if we're setting a value
+			if ( value !== undefined ) {
+				type = typeof value;
+
+				// Convert "+=" or "-=" to relative numbers (#7345)
+				if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
+					value = adjustCSS( elem, name, ret );
+
+					// Fixes bug #9237
+					type = "number";
+				}
+
+				// Make sure that null and NaN values aren't set (#7116)
+				if ( value == null || value !== value ) {
+					return;
+				}
+
+				// If a number was passed in, add the unit (except for certain CSS properties)
+				if ( type === "number" ) {
+					value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
+				}
+
+				// Support: IE9-11+
+				// background-* props affect original clone's values
+				if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+					style[ name ] = "inherit";
+				}
+
+				// If a hook was provided, use that value, otherwise just set the specified value
+				if ( !hooks || !( "set" in hooks ) ||
+					( value = hooks.set( elem, value, extra ) ) !== undefined ) {
+
+					style[ name ] = value;
+				}
+
+			} else {
+
+				// If a hook was provided get the non-computed value from there
+				if ( hooks && "get" in hooks &&
+					( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
+
+					return ret;
+				}
+
+				// Otherwise just get the value from the style object
+				return style[ name ];
+			}
+		},
+
+		css: function( elem, name, extra, styles ) {
+			var val, num, hooks,
+				origName = jQuery.camelCase( name );
+
+			// Make sure that we're working with the right name
+			name = jQuery.cssProps[ origName ] ||
+				( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+
+			// Try prefixed name followed by the unprefixed name
+			hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+			// If a hook was provided get the computed value from there
+			if ( hooks && "get" in hooks ) {
+				val = hooks.get( elem, true, extra );
+			}
+
+			// Otherwise, if a way to get the computed value exists, use that
+			if ( val === undefined ) {
+				val = curCSS( elem, name, styles );
+			}
+
+			// Convert "normal" to computed value
+			if ( val === "normal" && name in cssNormalTransform ) {
+				val = cssNormalTransform[ name ];
+			}
+
+			// Make numeric if forced or a qualifier was provided and val looks numeric
+			if ( extra === "" || extra ) {
+				num = parseFloat( val );
+				return extra === true || isFinite( num ) ? num || 0 : val;
+			}
+			return val;
+		}
+	} );
+
+	jQuery.each( [ "height", "width" ], function( i, name ) {
+		jQuery.cssHooks[ name ] = {
+			get: function( elem, computed, extra ) {
+				if ( computed ) {
+
+					// Certain elements can have dimension info if we invisibly show them
+					// but it must have a current display style that would benefit
+					return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
+						elem.offsetWidth === 0 ?
+							swap( elem, cssShow, function() {
+								return getWidthOrHeight( elem, name, extra );
+							} ) :
+							getWidthOrHeight( elem, name, extra );
+				}
+			},
+
+			set: function( elem, value, extra ) {
+				var matches,
+					styles = extra && getStyles( elem ),
+					subtract = extra && augmentWidthOrHeight(
+						elem,
+						name,
+						extra,
+						jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+						styles
+					);
+
+				// Convert to pixels if value adjustment is needed
+				if ( subtract && ( matches = rcssNum.exec( value ) ) &&
+					( matches[ 3 ] || "px" ) !== "px" ) {
+
+					elem.style[ name ] = value;
+					value = jQuery.css( elem, name );
+				}
+
+				return setPositiveNumber( elem, value, subtract );
+			}
+		};
+	} );
+
+	jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
+		function( elem, computed ) {
+			if ( computed ) {
+				return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
+					elem.getBoundingClientRect().left -
+						swap( elem, { marginLeft: 0 }, function() {
+							return elem.getBoundingClientRect().left;
+						} )
+					) + "px";
+			}
+		}
+	);
+
+	// Support: Android 2.3
+	jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
+		function( elem, computed ) {
+			if ( computed ) {
+				return swap( elem, { "display": "inline-block" },
+					curCSS, [ elem, "marginRight" ] );
+			}
+		}
+	);
+
+	// These hooks are used by animate to expand properties
+	jQuery.each( {
+		margin: "",
+		padding: "",
+		border: "Width"
+	}, function( prefix, suffix ) {
+		jQuery.cssHooks[ prefix + suffix ] = {
+			expand: function( value ) {
+				var i = 0,
+					expanded = {},
+
+					// Assumes a single number if not a string
+					parts = typeof value === "string" ? value.split( " " ) : [ value ];
+
+				for ( ; i < 4; i++ ) {
+					expanded[ prefix + cssExpand[ i ] + suffix ] =
+						parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+				}
+
+				return expanded;
+			}
+		};
+
+		if ( !rmargin.test( prefix ) ) {
+			jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+		}
+	} );
+
+	jQuery.fn.extend( {
+		css: function( name, value ) {
+			return access( this, function( elem, name, value ) {
+				var styles, len,
+					map = {},
+					i = 0;
+
+				if ( jQuery.isArray( name ) ) {
+					styles = getStyles( elem );
+					len = name.length;
+
+					for ( ; i < len; i++ ) {
+						map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+					}
+
+					return map;
+				}
+
+				return value !== undefined ?
+					jQuery.style( elem, name, value ) :
+					jQuery.css( elem, name );
+			}, name, value, arguments.length > 1 );
+		},
+		show: function() {
+			return showHide( this, true );
+		},
+		hide: function() {
+			return showHide( this );
+		},
+		toggle: function( state ) {
+			if ( typeof state === "boolean" ) {
+				return state ? this.show() : this.hide();
+			}
+
+			return this.each( function() {
+				if ( isHidden( this ) ) {
+					jQuery( this ).show();
+				} else {
+					jQuery( this ).hide();
+				}
+			} );
+		}
+	} );
+
+
+	function Tween( elem, options, prop, end, easing ) {
+		return new Tween.prototype.init( elem, options, prop, end, easing );
+	}
+	jQuery.Tween = Tween;
+
+	Tween.prototype = {
+		constructor: Tween,
+		init: function( elem, options, prop, end, easing, unit ) {
+			this.elem = elem;
+			this.prop = prop;
+			this.easing = easing || jQuery.easing._default;
+			this.options = options;
+			this.start = this.now = this.cur();
+			this.end = end;
+			this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+		},
+		cur: function() {
+			var hooks = Tween.propHooks[ this.prop ];
+
+			return hooks && hooks.get ?
+				hooks.get( this ) :
+				Tween.propHooks._default.get( this );
+		},
+		run: function( percent ) {
+			var eased,
+				hooks = Tween.propHooks[ this.prop ];
+
+			if ( this.options.duration ) {
+				this.pos = eased = jQuery.easing[ this.easing ](
+					percent, this.options.duration * percent, 0, 1, this.options.duration
+				);
+			} else {
+				this.pos = eased = percent;
+			}
+			this.now = ( this.end - this.start ) * eased + this.start;
+
+			if ( this.options.step ) {
+				this.options.step.call( this.elem, this.now, this );
+			}
+
+			if ( hooks && hooks.set ) {
+				hooks.set( this );
+			} else {
+				Tween.propHooks._default.set( this );
+			}
+			return this;
+		}
+	};
+
+	Tween.prototype.init.prototype = Tween.prototype;
+
+	Tween.propHooks = {
+		_default: {
+			get: function( tween ) {
+				var result;
+
+				// Use a property on the element directly when it is not a DOM element,
+				// or when there is no matching style property that exists.
+				if ( tween.elem.nodeType !== 1 ||
+					tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
+					return tween.elem[ tween.prop ];
+				}
+
+				// Passing an empty string as a 3rd parameter to .css will automatically
+				// attempt a parseFloat and fallback to a string if the parse fails.
+				// Simple values such as "10px" are parsed to Float;
+				// complex values such as "rotate(1rad)" are returned as-is.
+				result = jQuery.css( tween.elem, tween.prop, "" );
+
+				// Empty strings, null, undefined and "auto" are converted to 0.
+				return !result || result === "auto" ? 0 : result;
+			},
+			set: function( tween ) {
+
+				// Use step hook for back compat.
+				// Use cssHook if its there.
+				// Use .style if available and use plain properties where available.
+				if ( jQuery.fx.step[ tween.prop ] ) {
+					jQuery.fx.step[ tween.prop ]( tween );
+				} else if ( tween.elem.nodeType === 1 &&
+					( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
+						jQuery.cssHooks[ tween.prop ] ) ) {
+					jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+				} else {
+					tween.elem[ tween.prop ] = tween.now;
+				}
+			}
+		}
+	};
+
+	// Support: IE9
+	// Panic based approach to setting things on disconnected nodes
+	Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+		set: function( tween ) {
+			if ( tween.elem.nodeType && tween.elem.parentNode ) {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	};
+
+	jQuery.easing = {
+		linear: function( p ) {
+			return p;
+		},
+		swing: function( p ) {
+			return 0.5 - Math.cos( p * Math.PI ) / 2;
+		},
+		_default: "swing"
+	};
+
+	jQuery.fx = Tween.prototype.init;
+
+	// Back Compat <1.8 extension point
+	jQuery.fx.step = {};
+
+
+
+
+	var
+		fxNow, timerId,
+		rfxtypes = /^(?:toggle|show|hide)$/,
+		rrun = /queueHooks$/;
+
+	// Animations created synchronously will run synchronously
+	function createFxNow() {
+		window.setTimeout( function() {
+			fxNow = undefined;
+		} );
+		return ( fxNow = jQuery.now() );
+	}
+
+	// Generate parameters to create a standard animation
+	function genFx( type, includeWidth ) {
+		var which,
+			i = 0,
+			attrs = { height: type };
+
+		// If we include width, step value is 1 to do all cssExpand values,
+		// otherwise step value is 2 to skip over Left and Right
+		includeWidth = includeWidth ? 1 : 0;
+		for ( ; i < 4 ; i += 2 - includeWidth ) {
+			which = cssExpand[ i ];
+			attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+		}
+
+		if ( includeWidth ) {
+			attrs.opacity = attrs.width = type;
+		}
+
+		return attrs;
+	}
+
+	function createTween( value, prop, animation ) {
+		var tween,
+			collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
+			index = 0,
+			length = collection.length;
+		for ( ; index < length; index++ ) {
+			if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
+
+				// We're done with this property
+				return tween;
+			}
+		}
+	}
+
+	function defaultPrefilter( elem, props, opts ) {
+		/* jshint validthis: true */
+		var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
+			anim = this,
+			orig = {},
+			style = elem.style,
+			hidden = elem.nodeType && isHidden( elem ),
+			dataShow = dataPriv.get( elem, "fxshow" );
+
+		// Handle queue: false promises
+		if ( !opts.queue ) {
+			hooks = jQuery._queueHooks( elem, "fx" );
+			if ( hooks.unqueued == null ) {
+				hooks.unqueued = 0;
+				oldfire = hooks.empty.fire;
+				hooks.empty.fire = function() {
+					if ( !hooks.unqueued ) {
+						oldfire();
+					}
+				};
+			}
+			hooks.unqueued++;
+
+			anim.always( function() {
+
+				// Ensure the complete handler is called before this completes
+				anim.always( function() {
+					hooks.unqueued--;
+					if ( !jQuery.queue( elem, "fx" ).length ) {
+						hooks.empty.fire();
+					}
+				} );
+			} );
+		}
+
+		// Height/width overflow pass
+		if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+
+			// Make sure that nothing sneaks out
+			// Record all 3 overflow attributes because IE9-10 do not
+			// change the overflow attribute when overflowX and
+			// overflowY are set to the same value
+			opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+			// Set display property to inline-block for height/width
+			// animations on inline elements that are having width/height animated
+			display = jQuery.css( elem, "display" );
+
+			// Test default display if display is currently "none"
+			checkDisplay = display === "none" ?
+				dataPriv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
+
+			if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
+				style.display = "inline-block";
+			}
+		}
+
+		if ( opts.overflow ) {
+			style.overflow = "hidden";
+			anim.always( function() {
+				style.overflow = opts.overflow[ 0 ];
+				style.overflowX = opts.overflow[ 1 ];
+				style.overflowY = opts.overflow[ 2 ];
+			} );
+		}
+
+		// show/hide pass
+		for ( prop in props ) {
+			value = props[ prop ];
+			if ( rfxtypes.exec( value ) ) {
+				delete props[ prop ];
+				toggle = toggle || value === "toggle";
+				if ( value === ( hidden ? "hide" : "show" ) ) {
+
+					// If there is dataShow left over from a stopped hide or show
+					// and we are going to proceed with show, we should pretend to be hidden
+					if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+						hidden = true;
+					} else {
+						continue;
+					}
+				}
+				orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+
+			// Any non-fx value stops us from restoring the original display value
+			} else {
+				display = undefined;
+			}
+		}
+
+		if ( !jQuery.isEmptyObject( orig ) ) {
+			if ( dataShow ) {
+				if ( "hidden" in dataShow ) {
+					hidden = dataShow.hidden;
+				}
+			} else {
+				dataShow = dataPriv.access( elem, "fxshow", {} );
+			}
+
+			// Store state if its toggle - enables .stop().toggle() to "reverse"
+			if ( toggle ) {
+				dataShow.hidden = !hidden;
+			}
+			if ( hidden ) {
+				jQuery( elem ).show();
+			} else {
+				anim.done( function() {
+					jQuery( elem ).hide();
+				} );
+			}
+			anim.done( function() {
+				var prop;
+
+				dataPriv.remove( elem, "fxshow" );
+				for ( prop in orig ) {
+					jQuery.style( elem, prop, orig[ prop ] );
+				}
+			} );
+			for ( prop in orig ) {
+				tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+				if ( !( prop in dataShow ) ) {
+					dataShow[ prop ] = tween.start;
+					if ( hidden ) {
+						tween.end = tween.start;
+						tween.start = prop === "width" || prop === "height" ? 1 : 0;
+					}
+				}
+			}
+
+		// If this is a noop like .hide().hide(), restore an overwritten display value
+		} else if ( ( display === "none" ? defaultDisplay( elem.nodeName ) : display ) === "inline" ) {
+			style.display = display;
+		}
+	}
+
+	function propFilter( props, specialEasing ) {
+		var index, name, easing, value, hooks;
+
+		// camelCase, specialEasing and expand cssHook pass
+		for ( index in props ) {
+			name = jQuery.camelCase( index );
+			easing = specialEasing[ name ];
+			value = props[ index ];
+			if ( jQuery.isArray( value ) ) {
+				easing = value[ 1 ];
+				value = props[ index ] = value[ 0 ];
+			}
+
+			if ( index !== name ) {
+				props[ name ] = value;
+				delete props[ index ];
+			}
+
+			hooks = jQuery.cssHooks[ name ];
+			if ( hooks && "expand" in hooks ) {
+				value = hooks.expand( value );
+				delete props[ name ];
+
+				// Not quite $.extend, this won't overwrite existing keys.
+				// Reusing 'index' because we have the correct "name"
+				for ( index in value ) {
+					if ( !( index in props ) ) {
+						props[ index ] = value[ index ];
+						specialEasing[ index ] = easing;
+					}
+				}
+			} else {
+				specialEasing[ name ] = easing;
+			}
+		}
+	}
+
+	function Animation( elem, properties, options ) {
+		var result,
+			stopped,
+			index = 0,
+			length = Animation.prefilters.length,
+			deferred = jQuery.Deferred().always( function() {
+
+				// Don't match elem in the :animated selector
+				delete tick.elem;
+			} ),
+			tick = function() {
+				if ( stopped ) {
+					return false;
+				}
+				var currentTime = fxNow || createFxNow(),
+					remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+
+					// Support: Android 2.3
+					// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
+					temp = remaining / animation.duration || 0,
+					percent = 1 - temp,
+					index = 0,
+					length = animation.tweens.length;
+
+				for ( ; index < length ; index++ ) {
+					animation.tweens[ index ].run( percent );
+				}
+
+				deferred.notifyWith( elem, [ animation, percent, remaining ] );
+
+				if ( percent < 1 && length ) {
+					return remaining;
+				} else {
+					deferred.resolveWith( elem, [ animation ] );
+					return false;
+				}
+			},
+			animation = deferred.promise( {
+				elem: elem,
+				props: jQuery.extend( {}, properties ),
+				opts: jQuery.extend( true, {
+					specialEasing: {},
+					easing: jQuery.easing._default
+				}, options ),
+				originalProperties: properties,
+				originalOptions: options,
+				startTime: fxNow || createFxNow(),
+				duration: options.duration,
+				tweens: [],
+				createTween: function( prop, end ) {
+					var tween = jQuery.Tween( elem, animation.opts, prop, end,
+							animation.opts.specialEasing[ prop ] || animation.opts.easing );
+					animation.tweens.push( tween );
+					return tween;
+				},
+				stop: function( gotoEnd ) {
+					var index = 0,
+
+						// If we are going to the end, we want to run all the tweens
+						// otherwise we skip this part
+						length = gotoEnd ? animation.tweens.length : 0;
+					if ( stopped ) {
+						return this;
+					}
+					stopped = true;
+					for ( ; index < length ; index++ ) {
+						animation.tweens[ index ].run( 1 );
+					}
+
+					// Resolve when we played the last frame; otherwise, reject
+					if ( gotoEnd ) {
+						deferred.notifyWith( elem, [ animation, 1, 0 ] );
+						deferred.resolveWith( elem, [ animation, gotoEnd ] );
+					} else {
+						deferred.rejectWith( elem, [ animation, gotoEnd ] );
+					}
+					return this;
+				}
+			} ),
+			props = animation.props;
+
+		propFilter( props, animation.opts.specialEasing );
+
+		for ( ; index < length ; index++ ) {
+			result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
+			if ( result ) {
+				if ( jQuery.isFunction( result.stop ) ) {
+					jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
+						jQuery.proxy( result.stop, result );
+				}
+				return result;
+			}
+		}
+
+		jQuery.map( props, createTween, animation );
+
+		if ( jQuery.isFunction( animation.opts.start ) ) {
+			animation.opts.start.call( elem, animation );
+		}
+
+		jQuery.fx.timer(
+			jQuery.extend( tick, {
+				elem: elem,
+				anim: animation,
+				queue: animation.opts.queue
+			} )
+		);
+
+		// attach callbacks from options
+		return animation.progress( animation.opts.progress )
+			.done( animation.opts.done, animation.opts.complete )
+			.fail( animation.opts.fail )
+			.always( animation.opts.always );
+	}
+
+	jQuery.Animation = jQuery.extend( Animation, {
+		tweeners: {
+			"*": [ function( prop, value ) {
+				var tween = this.createTween( prop, value );
+				adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
+				return tween;
+			} ]
+		},
+
+		tweener: function( props, callback ) {
+			if ( jQuery.isFunction( props ) ) {
+				callback = props;
+				props = [ "*" ];
+			} else {
+				props = props.match( rnotwhite );
+			}
+
+			var prop,
+				index = 0,
+				length = props.length;
+
+			for ( ; index < length ; index++ ) {
+				prop = props[ index ];
+				Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
+				Animation.tweeners[ prop ].unshift( callback );
+			}
+		},
+
+		prefilters: [ defaultPrefilter ],
+
+		prefilter: function( callback, prepend ) {
+			if ( prepend ) {
+				Animation.prefilters.unshift( callback );
+			} else {
+				Animation.prefilters.push( callback );
+			}
+		}
+	} );
+
+	jQuery.speed = function( speed, easing, fn ) {
+		var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+		};
+
+		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ?
+			opt.duration : opt.duration in jQuery.fx.speeds ?
+				jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+		// Normalize opt.queue - true/undefined/null -> "fx"
+		if ( opt.queue == null || opt.queue === true ) {
+			opt.queue = "fx";
+		}
+
+		// Queueing
+		opt.old = opt.complete;
+
+		opt.complete = function() {
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+
+			if ( opt.queue ) {
+				jQuery.dequeue( this, opt.queue );
+			}
+		};
+
+		return opt;
+	};
+
+	jQuery.fn.extend( {
+		fadeTo: function( speed, to, easing, callback ) {
+
+			// Show any hidden elements after setting opacity to 0
+			return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+				// Animate to the value specified
+				.end().animate( { opacity: to }, speed, easing, callback );
+		},
+		animate: function( prop, speed, easing, callback ) {
+			var empty = jQuery.isEmptyObject( prop ),
+				optall = jQuery.speed( speed, easing, callback ),
+				doAnimation = function() {
+
+					// Operate on a copy of prop so per-property easing won't be lost
+					var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+					// Empty animations, or finishing resolves immediately
+					if ( empty || dataPriv.get( this, "finish" ) ) {
+						anim.stop( true );
+					}
+				};
+				doAnimation.finish = doAnimation;
+
+			return empty || optall.queue === false ?
+				this.each( doAnimation ) :
+				this.queue( optall.queue, doAnimation );
+		},
+		stop: function( type, clearQueue, gotoEnd ) {
+			var stopQueue = function( hooks ) {
+				var stop = hooks.stop;
+				delete hooks.stop;
+				stop( gotoEnd );
+			};
+
+			if ( typeof type !== "string" ) {
+				gotoEnd = clearQueue;
+				clearQueue = type;
+				type = undefined;
+			}
+			if ( clearQueue && type !== false ) {
+				this.queue( type || "fx", [] );
+			}
+
+			return this.each( function() {
+				var dequeue = true,
+					index = type != null && type + "queueHooks",
+					timers = jQuery.timers,
+					data = dataPriv.get( this );
+
+				if ( index ) {
+					if ( data[ index ] && data[ index ].stop ) {
+						stopQueue( data[ index ] );
+					}
+				} else {
+					for ( index in data ) {
+						if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+							stopQueue( data[ index ] );
+						}
+					}
+				}
+
+				for ( index = timers.length; index--; ) {
+					if ( timers[ index ].elem === this &&
+						( type == null || timers[ index ].queue === type ) ) {
+
+						timers[ index ].anim.stop( gotoEnd );
+						dequeue = false;
+						timers.splice( index, 1 );
+					}
+				}
+
+				// Start the next in the queue if the last step wasn't forced.
+				// Timers currently will call their complete callbacks, which
+				// will dequeue but only if they were gotoEnd.
+				if ( dequeue || !gotoEnd ) {
+					jQuery.dequeue( this, type );
+				}
+			} );
+		},
+		finish: function( type ) {
+			if ( type !== false ) {
+				type = type || "fx";
+			}
+			return this.each( function() {
+				var index,
+					data = dataPriv.get( this ),
+					queue = data[ type + "queue" ],
+					hooks = data[ type + "queueHooks" ],
+					timers = jQuery.timers,
+					length = queue ? queue.length : 0;
+
+				// Enable finishing flag on private data
+				data.finish = true;
+
+				// Empty the queue first
+				jQuery.queue( this, type, [] );
+
+				if ( hooks && hooks.stop ) {
+					hooks.stop.call( this, true );
+				}
+
+				// Look for any active animations, and finish them
+				for ( index = timers.length; index--; ) {
+					if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+						timers[ index ].anim.stop( true );
+						timers.splice( index, 1 );
+					}
+				}
+
+				// Look for any animations in the old queue and finish them
+				for ( index = 0; index < length; index++ ) {
+					if ( queue[ index ] && queue[ index ].finish ) {
+						queue[ index ].finish.call( this );
+					}
+				}
+
+				// Turn off finishing flag
+				delete data.finish;
+			} );
+		}
+	} );
+
+	jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
+		var cssFn = jQuery.fn[ name ];
+		jQuery.fn[ name ] = function( speed, easing, callback ) {
+			return speed == null || typeof speed === "boolean" ?
+				cssFn.apply( this, arguments ) :
+				this.animate( genFx( name, true ), speed, easing, callback );
+		};
+	} );
+
+	// Generate shortcuts for custom animations
+	jQuery.each( {
+		slideDown: genFx( "show" ),
+		slideUp: genFx( "hide" ),
+		slideToggle: genFx( "toggle" ),
+		fadeIn: { opacity: "show" },
+		fadeOut: { opacity: "hide" },
+		fadeToggle: { opacity: "toggle" }
+	}, function( name, props ) {
+		jQuery.fn[ name ] = function( speed, easing, callback ) {
+			return this.animate( props, speed, easing, callback );
+		};
+	} );
+
+	jQuery.timers = [];
+	jQuery.fx.tick = function() {
+		var timer,
+			i = 0,
+			timers = jQuery.timers;
+
+		fxNow = jQuery.now();
+
+		for ( ; i < timers.length; i++ ) {
+			timer = timers[ i ];
+
+			// Checks the timer has not already been removed
+			if ( !timer() && timers[ i ] === timer ) {
+				timers.splice( i--, 1 );
+			}
+		}
+
+		if ( !timers.length ) {
+			jQuery.fx.stop();
+		}
+		fxNow = undefined;
+	};
+
+	jQuery.fx.timer = function( timer ) {
+		jQuery.timers.push( timer );
+		if ( timer() ) {
+			jQuery.fx.start();
+		} else {
+			jQuery.timers.pop();
+		}
+	};
+
+	jQuery.fx.interval = 13;
+	jQuery.fx.start = function() {
+		if ( !timerId ) {
+			timerId = window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
+		}
+	};
+
+	jQuery.fx.stop = function() {
+		window.clearInterval( timerId );
+
+		timerId = null;
+	};
+
+	jQuery.fx.speeds = {
+		slow: 600,
+		fast: 200,
+
+		// Default speed
+		_default: 400
+	};
+
+
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
+	jQuery.fn.delay = function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function( next, hooks ) {
+			var timeout = window.setTimeout( next, time );
+			hooks.stop = function() {
+				window.clearTimeout( timeout );
+			};
+		} );
+	};
+
+
+	( function() {
+		var input = document.createElement( "input" ),
+			select = document.createElement( "select" ),
+			opt = select.appendChild( document.createElement( "option" ) );
+
+		input.type = "checkbox";
+
+		// Support: iOS<=5.1, Android<=4.2+
+		// Default value for a checkbox should be "on"
+		support.checkOn = input.value !== "";
+
+		// Support: IE<=11+
+		// Must access selectedIndex to make default options select
+		support.optSelected = opt.selected;
+
+		// Support: Android<=2.3
+		// Options inside disabled selects are incorrectly marked as disabled
+		select.disabled = true;
+		support.optDisabled = !opt.disabled;
+
+		// Support: IE<=11+
+		// An input loses its value after becoming a radio
+		input = document.createElement( "input" );
+		input.value = "t";
+		input.type = "radio";
+		support.radioValue = input.value === "t";
+	} )();
+
+
+	var boolHook,
+		attrHandle = jQuery.expr.attrHandle;
+
+	jQuery.fn.extend( {
+		attr: function( name, value ) {
+			return access( this, jQuery.attr, name, value, arguments.length > 1 );
+		},
+
+		removeAttr: function( name ) {
+			return this.each( function() {
+				jQuery.removeAttr( this, name );
+			} );
+		}
+	} );
+
+	jQuery.extend( {
+		attr: function( elem, name, value ) {
+			var ret, hooks,
+				nType = elem.nodeType;
+
+			// Don't get/set attributes on text, comment and attribute nodes
+			if ( nType === 3 || nType === 8 || nType === 2 ) {
+				return;
+			}
+
+			// Fallback to prop when attributes are not supported
+			if ( typeof elem.getAttribute === "undefined" ) {
+				return jQuery.prop( elem, name, value );
+			}
+
+			// All attributes are lowercase
+			// Grab necessary hook if one is defined
+			if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+				name = name.toLowerCase();
+				hooks = jQuery.attrHooks[ name ] ||
+					( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
+			}
+
+			if ( value !== undefined ) {
+				if ( value === null ) {
+					jQuery.removeAttr( elem, name );
+					return;
+				}
+
+				if ( hooks && "set" in hooks &&
+					( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+					return ret;
+				}
+
+				elem.setAttribute( name, value + "" );
+				return value;
+			}
+
+			if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+				return ret;
+			}
+
+			ret = jQuery.find.attr( elem, name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret == null ? undefined : ret;
+		},
+
+		attrHooks: {
+			type: {
+				set: function( elem, value ) {
+					if ( !support.radioValue && value === "radio" &&
+						jQuery.nodeName( elem, "input" ) ) {
+						var val = elem.value;
+						elem.setAttribute( "type", value );
+						if ( val ) {
+							elem.value = val;
+						}
+						return value;
+					}
+				}
+			}
+		},
+
+		removeAttr: function( elem, value ) {
+			var name, propName,
+				i = 0,
+				attrNames = value && value.match( rnotwhite );
+
+			if ( attrNames && elem.nodeType === 1 ) {
+				while ( ( name = attrNames[ i++ ] ) ) {
+					propName = jQuery.propFix[ name ] || name;
+
+					// Boolean attributes get special treatment (#10870)
+					if ( jQuery.expr.match.bool.test( name ) ) {
+
+						// Set corresponding property to false
+						elem[ propName ] = false;
+					}
+
+					elem.removeAttribute( name );
+				}
+			}
+		}
+	} );
+
+	// Hooks for boolean attributes
+	boolHook = {
+		set: function( elem, value, name ) {
+			if ( value === false ) {
+
+				// Remove boolean attributes when set to false
+				jQuery.removeAttr( elem, name );
+			} else {
+				elem.setAttribute( name, name );
+			}
+			return name;
+		}
+	};
+	jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+		var getter = attrHandle[ name ] || jQuery.find.attr;
+
+		attrHandle[ name ] = function( elem, name, isXML ) {
+			var ret, handle;
+			if ( !isXML ) {
+
+				// Avoid an infinite loop by temporarily removing this function from the getter
+				handle = attrHandle[ name ];
+				attrHandle[ name ] = ret;
+				ret = getter( elem, name, isXML ) != null ?
+					name.toLowerCase() :
+					null;
+				attrHandle[ name ] = handle;
+			}
+			return ret;
+		};
+	} );
+
+
+
+
+	var rfocusable = /^(?:input|select|textarea|button)$/i,
+		rclickable = /^(?:a|area)$/i;
+
+	jQuery.fn.extend( {
+		prop: function( name, value ) {
+			return access( this, jQuery.prop, name, value, arguments.length > 1 );
+		},
+
+		removeProp: function( name ) {
+			return this.each( function() {
+				delete this[ jQuery.propFix[ name ] || name ];
+			} );
+		}
+	} );
+
+	jQuery.extend( {
+		prop: function( elem, name, value ) {
+			var ret, hooks,
+				nType = elem.nodeType;
+
+			// Don't get/set properties on text, comment and attribute nodes
+			if ( nType === 3 || nType === 8 || nType === 2 ) {
+				return;
+			}
+
+			if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+
+				// Fix name and attach hooks
+				name = jQuery.propFix[ name ] || name;
+				hooks = jQuery.propHooks[ name ];
+			}
+
+			if ( value !== undefined ) {
+				if ( hooks && "set" in hooks &&
+					( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+					return ret;
+				}
+
+				return ( elem[ name ] = value );
+			}
+
+			if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+				return ret;
+			}
+
+			return elem[ name ];
+		},
+
+		propHooks: {
+			tabIndex: {
+				get: function( elem ) {
+
+					// elem.tabIndex doesn't always return the
+					// correct value when it hasn't been explicitly set
+					// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+					// Use proper attribute retrieval(#12072)
+					var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+					return tabindex ?
+						parseInt( tabindex, 10 ) :
+						rfocusable.test( elem.nodeName ) ||
+							rclickable.test( elem.nodeName ) && elem.href ?
+								0 :
+								-1;
+				}
+			}
+		},
+
+		propFix: {
+			"for": "htmlFor",
+			"class": "className"
+		}
+	} );
+
+	// Support: IE <=11 only
+	// Accessing the selectedIndex property
+	// forces the browser to respect setting selected
+	// on the option
+	// The getter ensures a default option is selected
+	// when in an optgroup
+	if ( !support.optSelected ) {
+		jQuery.propHooks.selected = {
+			get: function( elem ) {
+				var parent = elem.parentNode;
+				if ( parent && parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+				return null;
+			},
+			set: function( elem ) {
+				var parent = elem.parentNode;
+				if ( parent ) {
+					parent.selectedIndex;
+
+					if ( parent.parentNode ) {
+						parent.parentNode.selectedIndex;
+					}
+				}
+			}
+		};
+	}
+
+	jQuery.each( [
+		"tabIndex",
+		"readOnly",
+		"maxLength",
+		"cellSpacing",
+		"cellPadding",
+		"rowSpan",
+		"colSpan",
+		"useMap",
+		"frameBorder",
+		"contentEditable"
+	], function() {
+		jQuery.propFix[ this.toLowerCase() ] = this;
+	} );
+
+
+
+
+	var rclass = /[\t\r\n\f]/g;
+
+	function getClass( elem ) {
+		return elem.getAttribute && elem.getAttribute( "class" ) || "";
+	}
+
+	jQuery.fn.extend( {
+		addClass: function( value ) {
+			var classes, elem, cur, curValue, clazz, j, finalValue,
+				i = 0;
+
+			if ( jQuery.isFunction( value ) ) {
+				return this.each( function( j ) {
+					jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
+				} );
+			}
+
+			if ( typeof value === "string" && value ) {
+				classes = value.match( rnotwhite ) || [];
+
+				while ( ( elem = this[ i++ ] ) ) {
+					curValue = getClass( elem );
+					cur = elem.nodeType === 1 &&
+						( " " + curValue + " " ).replace( rclass, " " );
+
+					if ( cur ) {
+						j = 0;
+						while ( ( clazz = classes[ j++ ] ) ) {
+							if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+								cur += clazz + " ";
+							}
+						}
+
+						// Only assign if different to avoid unneeded rendering.
+						finalValue = jQuery.trim( cur );
+						if ( curValue !== finalValue ) {
+							elem.setAttribute( "class", finalValue );
+						}
+					}
+				}
+			}
+
+			return this;
+		},
+
+		removeClass: function( value ) {
+			var classes, elem, cur, curValue, clazz, j, finalValue,
+				i = 0;
+
+			if ( jQuery.isFunction( value ) ) {
+				return this.each( function( j ) {
+					jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
+				} );
+			}
+
+			if ( !arguments.length ) {
+				return this.attr( "class", "" );
+			}
+
+			if ( typeof value === "string" && value ) {
+				classes = value.match( rnotwhite ) || [];
+
+				while ( ( elem = this[ i++ ] ) ) {
+					curValue = getClass( elem );
+
+					// This expression is here for better compressibility (see addClass)
+					cur = elem.nodeType === 1 &&
+						( " " + curValue + " " ).replace( rclass, " " );
+
+					if ( cur ) {
+						j = 0;
+						while ( ( clazz = classes[ j++ ] ) ) {
+
+							// Remove *all* instances
+							while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
+								cur = cur.replace( " " + clazz + " ", " " );
+							}
+						}
+
+						// Only assign if different to avoid unneeded rendering.
+						finalValue = jQuery.trim( cur );
+						if ( curValue !== finalValue ) {
+							elem.setAttribute( "class", finalValue );
+						}
+					}
+				}
+			}
+
+			return this;
+		},
+
+		toggleClass: function( value, stateVal ) {
+			var type = typeof value;
+
+			if ( typeof stateVal === "boolean" && type === "string" ) {
+				return stateVal ? this.addClass( value ) : this.removeClass( value );
+			}
+
+			if ( jQuery.isFunction( value ) ) {
+				return this.each( function( i ) {
+					jQuery( this ).toggleClass(
+						value.call( this, i, getClass( this ), stateVal ),
+						stateVal
+					);
+				} );
+			}
+
+			return this.each( function() {
+				var className, i, self, classNames;
+
+				if ( type === "string" ) {
+
+					// Toggle individual class names
+					i = 0;
+					self = jQuery( this );
+					classNames = value.match( rnotwhite ) || [];
+
+					while ( ( className = classNames[ i++ ] ) ) {
+
+						// Check each className given, space separated list
+						if ( self.hasClass( className ) ) {
+							self.removeClass( className );
+						} else {
+							self.addClass( className );
+						}
+					}
+
+				// Toggle whole class name
+				} else if ( value === undefined || type === "boolean" ) {
+					className = getClass( this );
+					if ( className ) {
+
+						// Store className if set
+						dataPriv.set( this, "__className__", className );
+					}
+
+					// If the element has a class name or if we're passed `false`,
+					// then remove the whole classname (if there was one, the above saved it).
+					// Otherwise bring back whatever was previously saved (if anything),
+					// falling back to the empty string if nothing was stored.
+					if ( this.setAttribute ) {
+						this.setAttribute( "class",
+							className || value === false ?
+							"" :
+							dataPriv.get( this, "__className__" ) || ""
+						);
+					}
+				}
+			} );
+		},
+
+		hasClass: function( selector ) {
+			var className, elem,
+				i = 0;
+
+			className = " " + selector + " ";
+			while ( ( elem = this[ i++ ] ) ) {
+				if ( elem.nodeType === 1 &&
+					( " " + getClass( elem ) + " " ).replace( rclass, " " )
+						.indexOf( className ) > -1
+				) {
+					return true;
+				}
+			}
+
+			return false;
+		}
+	} );
+
+
+
+
+	var rreturn = /\r/g,
+		rspaces = /[\x20\t\r\n\f]+/g;
+
+	jQuery.fn.extend( {
+		val: function( value ) {
+			var hooks, ret, isFunction,
+				elem = this[ 0 ];
+
+			if ( !arguments.length ) {
+				if ( elem ) {
+					hooks = jQuery.valHooks[ elem.type ] ||
+						jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+					if ( hooks &&
+						"get" in hooks &&
+						( ret = hooks.get( elem, "value" ) ) !== undefined
+					) {
+						return ret;
+					}
+
+					ret = elem.value;
+
+					return typeof ret === "string" ?
+
+						// Handle most common string cases
+						ret.replace( rreturn, "" ) :
+
+						// Handle cases where value is null/undef or number
+						ret == null ? "" : ret;
+				}
+
+				return;
+			}
+
+			isFunction = jQuery.isFunction( value );
+
+			return this.each( function( i ) {
+				var val;
+
+				if ( this.nodeType !== 1 ) {
+					return;
+				}
+
+				if ( isFunction ) {
+					val = value.call( this, i, jQuery( this ).val() );
+				} else {
+					val = value;
+				}
+
+				// Treat null/undefined as ""; convert numbers to string
+				if ( val == null ) {
+					val = "";
+
+				} else if ( typeof val === "number" ) {
+					val += "";
+
+				} else if ( jQuery.isArray( val ) ) {
+					val = jQuery.map( val, function( value ) {
+						return value == null ? "" : value + "";
+					} );
+				}
+
+				hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+				// If set returns undefined, fall back to normal setting
+				if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
+					this.value = val;
+				}
+			} );
+		}
+	} );
+
+	jQuery.extend( {
+		valHooks: {
+			option: {
+				get: function( elem ) {
+
+					var val = jQuery.find.attr( elem, "value" );
+					return val != null ?
+						val :
+
+						// Support: IE10-11+
+						// option.text throws exceptions (#14686, #14858)
+						// Strip and collapse whitespace
+						// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
+						jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " );
+				}
+			},
+			select: {
+				get: function( elem ) {
+					var value, option,
+						options = elem.options,
+						index = elem.selectedIndex,
+						one = elem.type === "select-one" || index < 0,
+						values = one ? null : [],
+						max = one ? index + 1 : options.length,
+						i = index < 0 ?
+							max :
+							one ? index : 0;
+
+					// Loop through all the selected options
+					for ( ; i < max; i++ ) {
+						option = options[ i ];
+
+						// IE8-9 doesn't update selected after form reset (#2551)
+						if ( ( option.selected || i === index ) &&
+
+								// Don't return options that are disabled or in a disabled optgroup
+								( support.optDisabled ?
+									!option.disabled : option.getAttribute( "disabled" ) === null ) &&
+								( !option.parentNode.disabled ||
+									!jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+							// Get the specific value for the option
+							value = jQuery( option ).val();
+
+							// We don't need an array for one selects
+							if ( one ) {
+								return value;
+							}
+
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+
+					return values;
+				},
+
+				set: function( elem, value ) {
+					var optionSet, option,
+						options = elem.options,
+						values = jQuery.makeArray( value ),
+						i = options.length;
+
+					while ( i-- ) {
+						option = options[ i ];
+						if ( option.selected =
+							jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
+						) {
+							optionSet = true;
+						}
+					}
+
+					// Force browsers to behave consistently when non-matching value is set
+					if ( !optionSet ) {
+						elem.selectedIndex = -1;
+					}
+					return values;
+				}
+			}
+		}
+	} );
+
+	// Radios and checkboxes getter/setter
+	jQuery.each( [ "radio", "checkbox" ], function() {
+		jQuery.valHooks[ this ] = {
+			set: function( elem, value ) {
+				if ( jQuery.isArray( value ) ) {
+					return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
+				}
+			}
+		};
+		if ( !support.checkOn ) {
+			jQuery.valHooks[ this ].get = function( elem ) {
+				return elem.getAttribute( "value" ) === null ? "on" : elem.value;
+			};
+		}
+	} );
+
+
+
+
+	// Return jQuery for attributes-only inclusion
+
+
+	var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/;
+
+	jQuery.extend( jQuery.event, {
+
+		trigger: function( event, data, elem, onlyHandlers ) {
+
+			var i, cur, tmp, bubbleType, ontype, handle, special,
+				eventPath = [ elem || document ],
+				type = hasOwn.call( event, "type" ) ? event.type : event,
+				namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
+
+			cur = tmp = elem = elem || document;
+
+			// Don't do events on text and comment nodes
+			if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+				return;
+			}
+
+			// focus/blur morphs to focusin/out; ensure we're not firing them right now
+			if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+				return;
+			}
+
+			if ( type.indexOf( "." ) > -1 ) {
+
+				// Namespaced trigger; create a regexp to match event type in handle()
+				namespaces = type.split( "." );
+				type = namespaces.shift();
+				namespaces.sort();
+			}
+			ontype = type.indexOf( ":" ) < 0 && "on" + type;
+
+			// Caller can pass in a jQuery.Event object, Object, or just an event type string
+			event = event[ jQuery.expando ] ?
+				event :
+				new jQuery.Event( type, typeof event === "object" && event );
+
+			// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+			event.isTrigger = onlyHandlers ? 2 : 3;
+			event.namespace = namespaces.join( "." );
+			event.rnamespace = event.namespace ?
+				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
+				null;
+
+			// Clean up the event in case it is being reused
+			event.result = undefined;
+			if ( !event.target ) {
+				event.target = elem;
+			}
+
+			// Clone any incoming data and prepend the event, creating the handler arg list
+			data = data == null ?
+				[ event ] :
+				jQuery.makeArray( data, [ event ] );
+
+			// Allow special events to draw outside the lines
+			special = jQuery.event.special[ type ] || {};
+			if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+				return;
+			}
+
+			// Determine event propagation path in advance, per W3C events spec (#9951)
+			// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+			if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+				bubbleType = special.delegateType || type;
+				if ( !rfocusMorph.test( bubbleType + type ) ) {
+					cur = cur.parentNode;
+				}
+				for ( ; cur; cur = cur.parentNode ) {
+					eventPath.push( cur );
+					tmp = cur;
+				}
+
+				// Only add window if we got to document (e.g., not plain obj or detached DOM)
+				if ( tmp === ( elem.ownerDocument || document ) ) {
+					eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+				}
+			}
+
+			// Fire handlers on the event path
+			i = 0;
+			while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
+
+				event.type = i > 1 ?
+					bubbleType :
+					special.bindType || type;
+
+				// jQuery handler
+				handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
+					dataPriv.get( cur, "handle" );
+				if ( handle ) {
+					handle.apply( cur, data );
+				}
+
+				// Native handler
+				handle = ontype && cur[ ontype ];
+				if ( handle && handle.apply && acceptData( cur ) ) {
+					event.result = handle.apply( cur, data );
+					if ( event.result === false ) {
+						event.preventDefault();
+					}
+				}
+			}
+			event.type = type;
+
+			// If nobody prevented the default action, do it now
+			if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+				if ( ( !special._default ||
+					special._default.apply( eventPath.pop(), data ) === false ) &&
+					acceptData( elem ) ) {
+
+					// Call a native DOM method on the target with the same name name as the event.
+					// Don't do default actions on window, that's where global variables be (#6170)
+					if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+						// Don't re-trigger an onFOO event when we call its FOO() method
+						tmp = elem[ ontype ];
+
+						if ( tmp ) {
+							elem[ ontype ] = null;
+						}
+
+						// Prevent re-triggering of the same event, since we already bubbled it above
+						jQuery.event.triggered = type;
+						elem[ type ]();
+						jQuery.event.triggered = undefined;
+
+						if ( tmp ) {
+							elem[ ontype ] = tmp;
+						}
+					}
+				}
+			}
+
+			return event.result;
+		},
+
+		// Piggyback on a donor event to simulate a different one
+		// Used only for `focus(in | out)` events
+		simulate: function( type, elem, event ) {
+			var e = jQuery.extend(
+				new jQuery.Event(),
+				event,
+				{
+					type: type,
+					isSimulated: true
+				}
+			);
+
+			jQuery.event.trigger( e, null, elem );
+		}
+
+	} );
+
+	jQuery.fn.extend( {
+
+		trigger: function( type, data ) {
+			return this.each( function() {
+				jQuery.event.trigger( type, data, this );
+			} );
+		},
+		triggerHandler: function( type, data ) {
+			var elem = this[ 0 ];
+			if ( elem ) {
+				return jQuery.event.trigger( type, data, elem, true );
+			}
+		}
+	} );
+
+
+	jQuery.each( ( "blur focus focusin focusout load resize scroll unload click dblclick " +
+		"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+		"change select submit keydown keypress keyup error contextmenu" ).split( " " ),
+		function( i, name ) {
+
+		// Handle event binding
+		jQuery.fn[ name ] = function( data, fn ) {
+			return arguments.length > 0 ?
+				this.on( name, null, data, fn ) :
+				this.trigger( name );
+		};
+	} );
+
+	jQuery.fn.extend( {
+		hover: function( fnOver, fnOut ) {
+			return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+		}
+	} );
+
+
+
+
+	support.focusin = "onfocusin" in window;
+
+
+	// Support: Firefox
+	// Firefox doesn't have focus(in | out) events
+	// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+	//
+	// Support: Chrome, Safari
+	// focus(in | out) events fire after focus & blur events,
+	// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
+	// Related ticket - https://code.google.com/p/chromium/issues/detail?id=449857
+	if ( !support.focusin ) {
+		jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+			// Attach a single capturing handler on the document while someone wants focusin/focusout
+			var handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
+			};
+
+			jQuery.event.special[ fix ] = {
+				setup: function() {
+					var doc = this.ownerDocument || this,
+						attaches = dataPriv.access( doc, fix );
+
+					if ( !attaches ) {
+						doc.addEventListener( orig, handler, true );
+					}
+					dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
+				},
+				teardown: function() {
+					var doc = this.ownerDocument || this,
+						attaches = dataPriv.access( doc, fix ) - 1;
+
+					if ( !attaches ) {
+						doc.removeEventListener( orig, handler, true );
+						dataPriv.remove( doc, fix );
+
+					} else {
+						dataPriv.access( doc, fix, attaches );
+					}
+				}
+			};
+		} );
+	}
+	var location = window.location;
+
+	var nonce = jQuery.now();
+
+	var rquery = ( /\?/ );
+
+
+
+	// Support: Android 2.3
+	// Workaround failure to string-cast null input
+	jQuery.parseJSON = function( data ) {
+		return JSON.parse( data + "" );
+	};
+
+
+	// Cross-browser xml parsing
+	jQuery.parseXML = function( data ) {
+		var xml;
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+
+		// Support: IE9
+		try {
+			xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
+		} catch ( e ) {
+			xml = undefined;
+		}
+
+		if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	};
+
+
+	var
+		rhash = /#.*$/,
+		rts = /([?&])_=[^&]*/,
+		rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+
+		// #7653, #8125, #8152: local protocol detection
+		rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+		rnoContent = /^(?:GET|HEAD)$/,
+		rprotocol = /^\/\//,
+
+		/* Prefilters
+		 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+		 * 2) These are called:
+		 *    - BEFORE asking for a transport
+		 *    - AFTER param serialization (s.data is a string if s.processData is true)
+		 * 3) key is the dataType
+		 * 4) the catchall symbol "*" can be used
+		 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+		 */
+		prefilters = {},
+
+		/* Transports bindings
+		 * 1) key is the dataType
+		 * 2) the catchall symbol "*" can be used
+		 * 3) selection will start with transport dataType and THEN go to "*" if needed
+		 */
+		transports = {},
+
+		// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+		allTypes = "*/".concat( "*" ),
+
+		// Anchor tag for parsing the document origin
+		originAnchor = document.createElement( "a" );
+		originAnchor.href = location.href;
+
+	// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+	function addToPrefiltersOrTransports( structure ) {
+
+		// dataTypeExpression is optional and defaults to "*"
+		return function( dataTypeExpression, func ) {
+
+			if ( typeof dataTypeExpression !== "string" ) {
+				func = dataTypeExpression;
+				dataTypeExpression = "*";
+			}
+
+			var dataType,
+				i = 0,
+				dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
+
+			if ( jQuery.isFunction( func ) ) {
+
+				// For each dataType in the dataTypeExpression
+				while ( ( dataType = dataTypes[ i++ ] ) ) {
+
+					// Prepend if requested
+					if ( dataType[ 0 ] === "+" ) {
+						dataType = dataType.slice( 1 ) || "*";
+						( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
+
+					// Otherwise append
+					} else {
+						( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
+					}
+				}
+			}
+		};
+	}
+
+	// Base inspection function for prefilters and transports
+	function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+		var inspected = {},
+			seekingTransport = ( structure === transports );
+
+		function inspect( dataType ) {
+			var selected;
+			inspected[ dataType ] = true;
+			jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+				var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+				if ( typeof dataTypeOrTransport === "string" &&
+					!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+
+					options.dataTypes.unshift( dataTypeOrTransport );
+					inspect( dataTypeOrTransport );
+					return false;
+				} else if ( seekingTransport ) {
+					return !( selected = dataTypeOrTransport );
+				}
+			} );
+			return selected;
+		}
+
+		return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+	}
+
+	// A special extend for ajax options
+	// that takes "flat" options (not to be deep extended)
+	// Fixes #9887
+	function ajaxExtend( target, src ) {
+		var key, deep,
+			flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+		for ( key in src ) {
+			if ( src[ key ] !== undefined ) {
+				( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+			}
+		}
+		if ( deep ) {
+			jQuery.extend( true, target, deep );
+		}
+
+		return target;
+	}
+
+	/* Handles responses to an ajax request:
+	 * - finds the right dataType (mediates between content-type and expected dataType)
+	 * - returns the corresponding response
+	 */
+	function ajaxHandleResponses( s, jqXHR, responses ) {
+
+		var ct, type, finalDataType, firstDataType,
+			contents = s.contents,
+			dataTypes = s.dataTypes;
+
+		// Remove auto dataType and get content-type in the process
+		while ( dataTypes[ 0 ] === "*" ) {
+			dataTypes.shift();
+			if ( ct === undefined ) {
+				ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
+			}
+		}
+
+		// Check if we're dealing with a known content-type
+		if ( ct ) {
+			for ( type in contents ) {
+				if ( contents[ type ] && contents[ type ].test( ct ) ) {
+					dataTypes.unshift( type );
+					break;
+				}
+			}
+		}
+
+		// Check to see if we have a response for the expected dataType
+		if ( dataTypes[ 0 ] in responses ) {
+			finalDataType = dataTypes[ 0 ];
+		} else {
+
+			// Try convertible dataTypes
+			for ( type in responses ) {
+				if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
+					finalDataType = type;
+					break;
+				}
+				if ( !firstDataType ) {
+					firstDataType = type;
+				}
+			}
+
+			// Or just use first one
+			finalDataType = finalDataType || firstDataType;
+		}
+
+		// If we found a dataType
+		// We add the dataType to the list if needed
+		// and return the corresponding response
+		if ( finalDataType ) {
+			if ( finalDataType !== dataTypes[ 0 ] ) {
+				dataTypes.unshift( finalDataType );
+			}
+			return responses[ finalDataType ];
+		}
+	}
+
+	/* Chain conversions given the request and the original response
+	 * Also sets the responseXXX fields on the jqXHR instance
+	 */
+	function ajaxConvert( s, response, jqXHR, isSuccess ) {
+		var conv2, current, conv, tmp, prev,
+			converters = {},
+
+			// Work with a copy of dataTypes in case we need to modify it for conversion
+			dataTypes = s.dataTypes.slice();
+
+		// Create converters map with lowercased keys
+		if ( dataTypes[ 1 ] ) {
+			for ( conv in s.converters ) {
+				converters[ conv.toLowerCase() ] = s.converters[ conv ];
+			}
+		}
+
+		current = dataTypes.shift();
+
+		// Convert to each sequential dataType
+		while ( current ) {
+
+			if ( s.responseFields[ current ] ) {
+				jqXHR[ s.responseFields[ current ] ] = response;
+			}
+
+			// Apply the dataFilter if provided
+			if ( !prev && isSuccess && s.dataFilter ) {
+				response = s.dataFilter( response, s.dataType );
+			}
+
+			prev = current;
+			current = dataTypes.shift();
+
+			if ( current ) {
+
+			// There's only work to do if current dataType is non-auto
+				if ( current === "*" ) {
+
+					current = prev;
+
+				// Convert response if prev dataType is non-auto and differs from current
+				} else if ( prev !== "*" && prev !== current ) {
+
+					// Seek a direct converter
+					conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+					// If none found, seek a pair
+					if ( !conv ) {
+						for ( conv2 in converters ) {
+
+							// If conv2 outputs current
+							tmp = conv2.split( " " );
+							if ( tmp[ 1 ] === current ) {
+
+								// If prev can be converted to accepted input
+								conv = converters[ prev + " " + tmp[ 0 ] ] ||
+									converters[ "* " + tmp[ 0 ] ];
+								if ( conv ) {
+
+									// Condense equivalence converters
+									if ( conv === true ) {
+										conv = converters[ conv2 ];
+
+									// Otherwise, insert the intermediate dataType
+									} else if ( converters[ conv2 ] !== true ) {
+										current = tmp[ 0 ];
+										dataTypes.unshift( tmp[ 1 ] );
+									}
+									break;
+								}
+							}
+						}
+					}
+
+					// Apply converter (if not an equivalence)
+					if ( conv !== true ) {
+
+						// Unless errors are allowed to bubble, catch and return them
+						if ( conv && s.throws ) {
+							response = conv( response );
+						} else {
+							try {
+								response = conv( response );
+							} catch ( e ) {
+								return {
+									state: "parsererror",
+									error: conv ? e : "No conversion from " + prev + " to " + current
+								};
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return { state: "success", data: response };
+	}
+
+	jQuery.extend( {
+
+		// Counter for holding the number of active queries
+		active: 0,
+
+		// Last-Modified header cache for next request
+		lastModified: {},
+		etag: {},
+
+		ajaxSettings: {
+			url: location.href,
+			type: "GET",
+			isLocal: rlocalProtocol.test( location.protocol ),
+			global: true,
+			processData: true,
+			async: true,
+			contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+			/*
+			timeout: 0,
+			data: null,
+			dataType: null,
+			username: null,
+			password: null,
+			cache: null,
+			throws: false,
+			traditional: false,
+			headers: {},
+			*/
+
+			accepts: {
+				"*": allTypes,
+				text: "text/plain",
+				html: "text/html",
+				xml: "application/xml, text/xml",
+				json: "application/json, text/javascript"
+			},
+
+			contents: {
+				xml: /\bxml\b/,
+				html: /\bhtml/,
+				json: /\bjson\b/
+			},
+
+			responseFields: {
+				xml: "responseXML",
+				text: "responseText",
+				json: "responseJSON"
+			},
+
+			// Data converters
+			// Keys separate source (or catchall "*") and destination types with a single space
+			converters: {
+
+				// Convert anything to text
+				"* text": String,
+
+				// Text to html (true = no transformation)
+				"text html": true,
+
+				// Evaluate text as a json expression
+				"text json": jQuery.parseJSON,
+
+				// Parse text as xml
+				"text xml": jQuery.parseXML
+			},
+
+			// For options that shouldn't be deep extended:
+			// you can add your own custom options here if
+			// and when you create one that shouldn't be
+			// deep extended (see ajaxExtend)
+			flatOptions: {
+				url: true,
+				context: true
+			}
+		},
+
+		// Creates a full fledged settings object into target
+		// with both ajaxSettings and settings fields.
+		// If target is omitted, writes into ajaxSettings.
+		ajaxSetup: function( target, settings ) {
+			return settings ?
+
+				// Building a settings object
+				ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+				// Extending ajaxSettings
+				ajaxExtend( jQuery.ajaxSettings, target );
+		},
+
+		ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+		ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+		// Main method
+		ajax: function( url, options ) {
+
+			// If url is an object, simulate pre-1.5 signature
+			if ( typeof url === "object" ) {
+				options = url;
+				url = undefined;
+			}
+
+			// Force options to be an object
+			options = options || {};
+
+			var transport,
+
+				// URL without anti-cache param
+				cacheURL,
+
+				// Response headers
+				responseHeadersString,
+				responseHeaders,
+
+				// timeout handle
+				timeoutTimer,
+
+				// Url cleanup var
+				urlAnchor,
+
+				// To know if global events are to be dispatched
+				fireGlobals,
+
+				// Loop variable
+				i,
+
+				// Create the final options object
+				s = jQuery.ajaxSetup( {}, options ),
+
+				// Callbacks context
+				callbackContext = s.context || s,
+
+				// Context for global events is callbackContext if it is a DOM node or jQuery collection
+				globalEventContext = s.context &&
+					( callbackContext.nodeType || callbackContext.jquery ) ?
+						jQuery( callbackContext ) :
+						jQuery.event,
+
+				// Deferreds
+				deferred = jQuery.Deferred(),
+				completeDeferred = jQuery.Callbacks( "once memory" ),
+
+				// Status-dependent callbacks
+				statusCode = s.statusCode || {},
+
+				// Headers (they are sent all at once)
+				requestHeaders = {},
+				requestHeadersNames = {},
+
+				// The jqXHR state
+				state = 0,
+
+				// Default abort message
+				strAbort = "canceled",
+
+				// Fake xhr
+				jqXHR = {
+					readyState: 0,
+
+					// Builds headers hashtable if needed
+					getResponseHeader: function( key ) {
+						var match;
+						if ( state === 2 ) {
+							if ( !responseHeaders ) {
+								responseHeaders = {};
+								while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
+									responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
+								}
+							}
+							match = responseHeaders[ key.toLowerCase() ];
+						}
+						return match == null ? null : match;
+					},
+
+					// Raw string
+					getAllResponseHeaders: function() {
+						return state === 2 ? responseHeadersString : null;
+					},
+
+					// Caches the header
+					setRequestHeader: function( name, value ) {
+						var lname = name.toLowerCase();
+						if ( !state ) {
+							name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+							requestHeaders[ name ] = value;
+						}
+						return this;
+					},
+
+					// Overrides response content-type header
+					overrideMimeType: function( type ) {
+						if ( !state ) {
+							s.mimeType = type;
+						}
+						return this;
+					},
+
+					// Status-dependent callbacks
+					statusCode: function( map ) {
+						var code;
+						if ( map ) {
+							if ( state < 2 ) {
+								for ( code in map ) {
+
+									// Lazy-add the new callback in a way that preserves old ones
+									statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+								}
+							} else {
+
+								// Execute the appropriate callbacks
+								jqXHR.always( map[ jqXHR.status ] );
+							}
+						}
+						return this;
+					},
+
+					// Cancel the request
+					abort: function( statusText ) {
+						var finalText = statusText || strAbort;
+						if ( transport ) {
+							transport.abort( finalText );
+						}
+						done( 0, finalText );
+						return this;
+					}
+				};
+
+			// Attach deferreds
+			deferred.promise( jqXHR ).complete = completeDeferred.add;
+			jqXHR.success = jqXHR.done;
+			jqXHR.error = jqXHR.fail;
+
+			// Remove hash character (#7531: and string promotion)
+			// Add protocol if not provided (prefilters might expect it)
+			// Handle falsy url in the settings object (#10093: consistency with old signature)
+			// We also use the url parameter if available
+			s.url = ( ( url || s.url || location.href ) + "" ).replace( rhash, "" )
+				.replace( rprotocol, location.protocol + "//" );
+
+			// Alias method option to type as per ticket #12004
+			s.type = options.method || options.type || s.method || s.type;
+
+			// Extract dataTypes list
+			s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
+
+			// A cross-domain request is in order when the origin doesn't match the current origin.
+			if ( s.crossDomain == null ) {
+				urlAnchor = document.createElement( "a" );
+
+				// Support: IE8-11+
+				// IE throws exception if url is malformed, e.g. http://example.com:80x/
+				try {
+					urlAnchor.href = s.url;
+
+					// Support: IE8-11+
+					// Anchor's host property isn't correctly set when s.url is relative
+					urlAnchor.href = urlAnchor.href;
+					s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
+						urlAnchor.protocol + "//" + urlAnchor.host;
+				} catch ( e ) {
+
+					// If there is an error parsing the URL, assume it is crossDomain,
+					// it can be rejected by the transport if it is invalid
+					s.crossDomain = true;
+				}
+			}
+
+			// Convert data if not already a string
+			if ( s.data && s.processData && typeof s.data !== "string" ) {
+				s.data = jQuery.param( s.data, s.traditional );
+			}
+
+			// Apply prefilters
+			inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+			// If request was aborted inside a prefilter, stop there
+			if ( state === 2 ) {
+				return jqXHR;
+			}
+
+			// We can fire global events as of now if asked to
+			// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+			fireGlobals = jQuery.event && s.global;
+
+			// Watch for a new set of requests
+			if ( fireGlobals && jQuery.active++ === 0 ) {
+				jQuery.event.trigger( "ajaxStart" );
+			}
+
+			// Uppercase the type
+			s.type = s.type.toUpperCase();
+
+			// Determine if request has content
+			s.hasContent = !rnoContent.test( s.type );
+
+			// Save the URL in case we're toying with the If-Modified-Since
+			// and/or If-None-Match header later on
+			cacheURL = s.url;
+
+			// More options handling for requests with no content
+			if ( !s.hasContent ) {
+
+				// If data is available, append data to url
+				if ( s.data ) {
+					cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+
+					// #9682: remove data so that it's not used in an eventual retry
+					delete s.data;
+				}
+
+				// Add anti-cache in url if needed
+				if ( s.cache === false ) {
+					s.url = rts.test( cacheURL ) ?
+
+						// If there is already a '_' parameter, set its value
+						cacheURL.replace( rts, "$1_=" + nonce++ ) :
+
+						// Otherwise add one to the end
+						cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
+				}
+			}
+
+			// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+			if ( s.ifModified ) {
+				if ( jQuery.lastModified[ cacheURL ] ) {
+					jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+				}
+				if ( jQuery.etag[ cacheURL ] ) {
+					jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+				}
+			}
+
+			// Set the correct header, if data is being sent
+			if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+				jqXHR.setRequestHeader( "Content-Type", s.contentType );
+			}
+
+			// Set the Accepts header for the server, depending on the dataType
+			jqXHR.setRequestHeader(
+				"Accept",
+				s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
+					s.accepts[ s.dataTypes[ 0 ] ] +
+						( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+					s.accepts[ "*" ]
+			);
+
+			// Check for headers option
+			for ( i in s.headers ) {
+				jqXHR.setRequestHeader( i, s.headers[ i ] );
+			}
+
+			// Allow custom headers/mimetypes and early abort
+			if ( s.beforeSend &&
+				( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+
+				// Abort if not done already and return
+				return jqXHR.abort();
+			}
+
+			// Aborting is no longer a cancellation
+			strAbort = "abort";
+
+			// Install callbacks on deferreds
+			for ( i in { success: 1, error: 1, complete: 1 } ) {
+				jqXHR[ i ]( s[ i ] );
+			}
+
+			// Get transport
+			transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+			// If no transport, we auto-abort
+			if ( !transport ) {
+				done( -1, "No Transport" );
+			} else {
+				jqXHR.readyState = 1;
+
+				// Send global event
+				if ( fireGlobals ) {
+					globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+				}
+
+				// If request was aborted inside ajaxSend, stop there
+				if ( state === 2 ) {
+					return jqXHR;
+				}
+
+				// Timeout
+				if ( s.async && s.timeout > 0 ) {
+					timeoutTimer = window.setTimeout( function() {
+						jqXHR.abort( "timeout" );
+					}, s.timeout );
+				}
+
+				try {
+					state = 1;
+					transport.send( requestHeaders, done );
+				} catch ( e ) {
+
+					// Propagate exception as error if not done
+					if ( state < 2 ) {
+						done( -1, e );
+
+					// Simply rethrow otherwise
+					} else {
+						throw e;
+					}
+				}
+			}
+
+			// Callback for when everything is done
+			function done( status, nativeStatusText, responses, headers ) {
+				var isSuccess, success, error, response, modified,
+					statusText = nativeStatusText;
+
+				// Called once
+				if ( state === 2 ) {
+					return;
+				}
+
+				// State is "done" now
+				state = 2;
+
+				// Clear timeout if it exists
+				if ( timeoutTimer ) {
+					window.clearTimeout( timeoutTimer );
+				}
+
+				// Dereference transport for early garbage collection
+				// (no matter how long the jqXHR object will be used)
+				transport = undefined;
+
+				// Cache response headers
+				responseHeadersString = headers || "";
+
+				// Set readyState
+				jqXHR.readyState = status > 0 ? 4 : 0;
+
+				// Determine if successful
+				isSuccess = status >= 200 && status < 300 || status === 304;
+
+				// Get response data
+				if ( responses ) {
+					response = ajaxHandleResponses( s, jqXHR, responses );
+				}
+
+				// Convert no matter what (that way responseXXX fields are always set)
+				response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+				// If successful, handle type chaining
+				if ( isSuccess ) {
+
+					// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+					if ( s.ifModified ) {
+						modified = jqXHR.getResponseHeader( "Last-Modified" );
+						if ( modified ) {
+							jQuery.lastModified[ cacheURL ] = modified;
+						}
+						modified = jqXHR.getResponseHeader( "etag" );
+						if ( modified ) {
+							jQuery.etag[ cacheURL ] = modified;
+						}
+					}
+
+					// if no content
+					if ( status === 204 || s.type === "HEAD" ) {
+						statusText = "nocontent";
+
+					// if not modified
+					} else if ( status === 304 ) {
+						statusText = "notmodified";
+
+					// If we have data, let's convert it
+					} else {
+						statusText = response.state;
+						success = response.data;
+						error = response.error;
+						isSuccess = !error;
+					}
+				} else {
+
+					// Extract error from statusText and normalize for non-aborts
+					error = statusText;
+					if ( status || !statusText ) {
+						statusText = "error";
+						if ( status < 0 ) {
+							status = 0;
+						}
+					}
+				}
+
+				// Set data for the fake xhr object
+				jqXHR.status = status;
+				jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+				// Success/Error
+				if ( isSuccess ) {
+					deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+				} else {
+					deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+				}
+
+				// Status-dependent callbacks
+				jqXHR.statusCode( statusCode );
+				statusCode = undefined;
+
+				if ( fireGlobals ) {
+					globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+						[ jqXHR, s, isSuccess ? success : error ] );
+				}
+
+				// Complete
+				completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+				if ( fireGlobals ) {
+					globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+
+					// Handle the global AJAX counter
+					if ( !( --jQuery.active ) ) {
+						jQuery.event.trigger( "ajaxStop" );
+					}
+				}
+			}
+
+			return jqXHR;
+		},
+
+		getJSON: function( url, data, callback ) {
+			return jQuery.get( url, data, callback, "json" );
+		},
+
+		getScript: function( url, callback ) {
+			return jQuery.get( url, undefined, callback, "script" );
+		}
+	} );
+
+	jQuery.each( [ "get", "post" ], function( i, method ) {
+		jQuery[ method ] = function( url, data, callback, type ) {
+
+			// Shift arguments if data argument was omitted
+			if ( jQuery.isFunction( data ) ) {
+				type = type || callback;
+				callback = data;
+				data = undefined;
+			}
+
+			// The url can be an options object (which then must have .url)
+			return jQuery.ajax( jQuery.extend( {
+				url: url,
+				type: method,
+				dataType: type,
+				data: data,
+				success: callback
+			}, jQuery.isPlainObject( url ) && url ) );
+		};
+	} );
+
+
+	jQuery._evalUrl = function( url ) {
+		return jQuery.ajax( {
+			url: url,
+
+			// Make this explicit, since user can override this through ajaxSetup (#11264)
+			type: "GET",
+			dataType: "script",
+			async: false,
+			global: false,
+			"throws": true
+		} );
+	};
+
+
+	jQuery.fn.extend( {
+		wrapAll: function( html ) {
+			var wrap;
+
+			if ( jQuery.isFunction( html ) ) {
+				return this.each( function( i ) {
+					jQuery( this ).wrapAll( html.call( this, i ) );
+				} );
+			}
+
+			if ( this[ 0 ] ) {
+
+				// The elements to wrap the target around
+				wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+				if ( this[ 0 ].parentNode ) {
+					wrap.insertBefore( this[ 0 ] );
+				}
+
+				wrap.map( function() {
+					var elem = this;
+
+					while ( elem.firstElementChild ) {
+						elem = elem.firstElementChild;
+					}
+
+					return elem;
+				} ).append( this );
+			}
+
+			return this;
+		},
+
+		wrapInner: function( html ) {
+			if ( jQuery.isFunction( html ) ) {
+				return this.each( function( i ) {
+					jQuery( this ).wrapInner( html.call( this, i ) );
+				} );
+			}
+
+			return this.each( function() {
+				var self = jQuery( this ),
+					contents = self.contents();
+
+				if ( contents.length ) {
+					contents.wrapAll( html );
+
+				} else {
+					self.append( html );
+				}
+			} );
+		},
+
+		wrap: function( html ) {
+			var isFunction = jQuery.isFunction( html );
+
+			return this.each( function( i ) {
+				jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
+			} );
+		},
+
+		unwrap: function() {
+			return this.parent().each( function() {
+				if ( !jQuery.nodeName( this, "body" ) ) {
+					jQuery( this ).replaceWith( this.childNodes );
+				}
+			} ).end();
+		}
+	} );
+
+
+	jQuery.expr.filters.hidden = function( elem ) {
+		return !jQuery.expr.filters.visible( elem );
+	};
+	jQuery.expr.filters.visible = function( elem ) {
+
+		// Support: Opera <= 12.12
+		// Opera reports offsetWidths and offsetHeights less than zero on some elements
+		// Use OR instead of AND as the element is not visible if either is true
+		// See tickets #10406 and #13132
+		return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem.getClientRects().length > 0;
+	};
+
+
+
+
+	var r20 = /%20/g,
+		rbracket = /\[\]$/,
+		rCRLF = /\r?\n/g,
+		rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+		rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+	function buildParams( prefix, obj, traditional, add ) {
+		var name;
+
+		if ( jQuery.isArray( obj ) ) {
+
+			// Serialize array item.
+			jQuery.each( obj, function( i, v ) {
+				if ( traditional || rbracket.test( prefix ) ) {
+
+					// Treat each array item as a scalar.
+					add( prefix, v );
+
+				} else {
+
+					// Item is non-scalar (array or object), encode its numeric index.
+					buildParams(
+						prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
+						v,
+						traditional,
+						add
+					);
+				}
+			} );
+
+		} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+
+			// Serialize object item.
+			for ( name in obj ) {
+				buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+			}
+
+		} else {
+
+			// Serialize scalar item.
+			add( prefix, obj );
+		}
+	}
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	jQuery.param = function( a, traditional ) {
+		var prefix,
+			s = [],
+			add = function( key, value ) {
+
+				// If value is a function, invoke it and return its value
+				value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+				s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+			};
+
+		// Set traditional to true for jQuery <= 1.3.2 behavior.
+		if ( traditional === undefined ) {
+			traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+		}
+
+		// If an array was passed in, assume that it is an array of form elements.
+		if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			} );
+
+		} else {
+
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( prefix in a ) {
+				buildParams( prefix, a[ prefix ], traditional, add );
+			}
+		}
+
+		// Return the resulting serialization
+		return s.join( "&" ).replace( r20, "+" );
+	};
+
+	jQuery.fn.extend( {
+		serialize: function() {
+			return jQuery.param( this.serializeArray() );
+		},
+		serializeArray: function() {
+			return this.map( function() {
+
+				// Can add propHook for "elements" to filter or add form elements
+				var elements = jQuery.prop( this, "elements" );
+				return elements ? jQuery.makeArray( elements ) : this;
+			} )
+			.filter( function() {
+				var type = this.type;
+
+				// Use .is( ":disabled" ) so that fieldset[disabled] works
+				return this.name && !jQuery( this ).is( ":disabled" ) &&
+					rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+					( this.checked || !rcheckableType.test( type ) );
+			} )
+			.map( function( i, elem ) {
+				var val = jQuery( this ).val();
+
+				return val == null ?
+					null :
+					jQuery.isArray( val ) ?
+						jQuery.map( val, function( val ) {
+							return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+						} ) :
+						{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+			} ).get();
+		}
+	} );
+
+
+	jQuery.ajaxSettings.xhr = function() {
+		try {
+			return new window.XMLHttpRequest();
+		} catch ( e ) {}
+	};
+
+	var xhrSuccessStatus = {
+
+			// File protocol always yields status code 0, assume 200
+			0: 200,
+
+			// Support: IE9
+			// #1450: sometimes IE returns 1223 when it should be 204
+			1223: 204
+		},
+		xhrSupported = jQuery.ajaxSettings.xhr();
+
+	support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+	support.ajax = xhrSupported = !!xhrSupported;
+
+	jQuery.ajaxTransport( function( options ) {
+		var callback, errorCallback;
+
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( support.cors || xhrSupported && !options.crossDomain ) {
+			return {
+				send: function( headers, complete ) {
+					var i,
+						xhr = options.xhr();
+
+					xhr.open(
+						options.type,
+						options.url,
+						options.async,
+						options.username,
+						options.password
+					);
+
+					// Apply custom fields if provided
+					if ( options.xhrFields ) {
+						for ( i in options.xhrFields ) {
+							xhr[ i ] = options.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( options.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( options.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Set headers
+					for ( i in headers ) {
+						xhr.setRequestHeader( i, headers[ i ] );
+					}
+
+					// Callback
+					callback = function( type ) {
+						return function() {
+							if ( callback ) {
+								callback = errorCallback = xhr.onload =
+									xhr.onerror = xhr.onabort = xhr.onreadystatechange = null;
+
+								if ( type === "abort" ) {
+									xhr.abort();
+								} else if ( type === "error" ) {
+
+									// Support: IE9
+									// On a manual native abort, IE9 throws
+									// errors on any property access that is not readyState
+									if ( typeof xhr.status !== "number" ) {
+										complete( 0, "error" );
+									} else {
+										complete(
+
+											// File: protocol always yields status 0; see #8605, #14207
+											xhr.status,
+											xhr.statusText
+										);
+									}
+								} else {
+									complete(
+										xhrSuccessStatus[ xhr.status ] || xhr.status,
+										xhr.statusText,
+
+										// Support: IE9 only
+										// IE9 has no XHR2 but throws on binary (trac-11426)
+										// For XHR2 non-text, let the caller handle it (gh-2498)
+										( xhr.responseType || "text" ) !== "text"  ||
+										typeof xhr.responseText !== "string" ?
+											{ binary: xhr.response } :
+											{ text: xhr.responseText },
+										xhr.getAllResponseHeaders()
+									);
+								}
+							}
+						};
+					};
+
+					// Listen to events
+					xhr.onload = callback();
+					errorCallback = xhr.onerror = callback( "error" );
+
+					// Support: IE9
+					// Use onreadystatechange to replace onabort
+					// to handle uncaught aborts
+					if ( xhr.onabort !== undefined ) {
+						xhr.onabort = errorCallback;
+					} else {
+						xhr.onreadystatechange = function() {
+
+							// Check readyState before timeout as it changes
+							if ( xhr.readyState === 4 ) {
+
+								// Allow onerror to be called first,
+								// but that will not handle a native abort
+								// Also, save errorCallback to a variable
+								// as xhr.onerror cannot be accessed
+								window.setTimeout( function() {
+									if ( callback ) {
+										errorCallback();
+									}
+								} );
+							}
+						};
+					}
+
+					// Create the abort callback
+					callback = callback( "abort" );
+
+					try {
+
+						// Do send the request (this may raise an exception)
+						xhr.send( options.hasContent && options.data || null );
+					} catch ( e ) {
+
+						// #14683: Only rethrow if this hasn't been notified as an error yet
+						if ( callback ) {
+							throw e;
+						}
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback();
+					}
+				}
+			};
+		}
+	} );
+
+
+
+
+	// Install script dataType
+	jQuery.ajaxSetup( {
+		accepts: {
+			script: "text/javascript, application/javascript, " +
+				"application/ecmascript, application/x-ecmascript"
+		},
+		contents: {
+			script: /\b(?:java|ecma)script\b/
+		},
+		converters: {
+			"text script": function( text ) {
+				jQuery.globalEval( text );
+				return text;
+			}
+		}
+	} );
+
+	// Handle cache's special case and crossDomain
+	jQuery.ajaxPrefilter( "script", function( s ) {
+		if ( s.cache === undefined ) {
+			s.cache = false;
+		}
+		if ( s.crossDomain ) {
+			s.type = "GET";
+		}
+	} );
+
+	// Bind script tag hack transport
+	jQuery.ajaxTransport( "script", function( s ) {
+
+		// This transport only deals with cross domain requests
+		if ( s.crossDomain ) {
+			var script, callback;
+			return {
+				send: function( _, complete ) {
+					script = jQuery( "<script>" ).prop( {
+						charset: s.scriptCharset,
+						src: s.url
+					} ).on(
+						"load error",
+						callback = function( evt ) {
+							script.remove();
+							callback = null;
+							if ( evt ) {
+								complete( evt.type === "error" ? 404 : 200, evt.type );
+							}
+						}
+					);
+
+					// Use native DOM manipulation to avoid our domManip AJAX trickery
+					document.head.appendChild( script[ 0 ] );
+				},
+				abort: function() {
+					if ( callback ) {
+						callback();
+					}
+				}
+			};
+		}
+	} );
+
+
+
+
+	var oldCallbacks = [],
+		rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+	// Default jsonp settings
+	jQuery.ajaxSetup( {
+		jsonp: "callback",
+		jsonpCallback: function() {
+			var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+			this[ callback ] = true;
+			return callback;
+		}
+	} );
+
+	// Detect, normalize options and install callbacks for jsonp requests
+	jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+		var callbackName, overwritten, responseContainer,
+			jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+				"url" :
+				typeof s.data === "string" &&
+					( s.contentType || "" )
+						.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
+					rjsonp.test( s.data ) && "data"
+			);
+
+		// Handle iff the expected data type is "jsonp" or we have a parameter to set
+		if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+			// Get callback name, remembering preexisting value associated with it
+			callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+				s.jsonpCallback() :
+				s.jsonpCallback;
+
+			// Insert callback into url or form data
+			if ( jsonProp ) {
+				s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+			} else if ( s.jsonp !== false ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+			}
+
+			// Use data converter to retrieve json after script execution
+			s.converters[ "script json" ] = function() {
+				if ( !responseContainer ) {
+					jQuery.error( callbackName + " was not called" );
+				}
+				return responseContainer[ 0 ];
+			};
+
+			// Force json dataType
+			s.dataTypes[ 0 ] = "json";
+
+			// Install callback
+			overwritten = window[ callbackName ];
+			window[ callbackName ] = function() {
+				responseContainer = arguments;
+			};
+
+			// Clean-up function (fires after converters)
+			jqXHR.always( function() {
+
+				// If previous value didn't exist - remove it
+				if ( overwritten === undefined ) {
+					jQuery( window ).removeProp( callbackName );
+
+				// Otherwise restore preexisting value
+				} else {
+					window[ callbackName ] = overwritten;
+				}
+
+				// Save back as free
+				if ( s[ callbackName ] ) {
+
+					// Make sure that re-using the options doesn't screw things around
+					s.jsonpCallback = originalSettings.jsonpCallback;
+
+					// Save the callback name for future use
+					oldCallbacks.push( callbackName );
+				}
+
+				// Call if it was a function and we have a response
+				if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+					overwritten( responseContainer[ 0 ] );
+				}
+
+				responseContainer = overwritten = undefined;
+			} );
+
+			// Delegate to script
+			return "script";
+		}
+	} );
+
+
+
+
+	// Argument "data" should be string of html
+	// context (optional): If specified, the fragment will be created in this context,
+	// defaults to document
+	// keepScripts (optional): If true, will include scripts passed in the html string
+	jQuery.parseHTML = function( data, context, keepScripts ) {
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+		if ( typeof context === "boolean" ) {
+			keepScripts = context;
+			context = false;
+		}
+		context = context || document;
+
+		var parsed = rsingleTag.exec( data ),
+			scripts = !keepScripts && [];
+
+		// Single tag
+		if ( parsed ) {
+			return [ context.createElement( parsed[ 1 ] ) ];
+		}
+
+		parsed = buildFragment( [ data ], context, scripts );
+
+		if ( scripts && scripts.length ) {
+			jQuery( scripts ).remove();
+		}
+
+		return jQuery.merge( [], parsed.childNodes );
+	};
+
+
+	// Keep a copy of the old load method
+	var _load = jQuery.fn.load;
+
+	/**
+	 * Load a url into a page
+	 */
+	jQuery.fn.load = function( url, params, callback ) {
+		if ( typeof url !== "string" && _load ) {
+			return _load.apply( this, arguments );
+		}
+
+		var selector, type, response,
+			self = this,
+			off = url.indexOf( " " );
+
+		if ( off > -1 ) {
+			selector = jQuery.trim( url.slice( off ) );
+			url = url.slice( 0, off );
+		}
+
+		// If it's a function
+		if ( jQuery.isFunction( params ) ) {
+
+			// We assume that it's the callback
+			callback = params;
+			params = undefined;
+
+		// Otherwise, build a param string
+		} else if ( params && typeof params === "object" ) {
+			type = "POST";
+		}
+
+		// If we have elements to modify, make the request
+		if ( self.length > 0 ) {
+			jQuery.ajax( {
+				url: url,
+
+				// If "type" variable is undefined, then "GET" method will be used.
+				// Make value of this field explicit since
+				// user can override it through ajaxSetup method
+				type: type || "GET",
+				dataType: "html",
+				data: params
+			} ).done( function( responseText ) {
+
+				// Save response for use in complete callback
+				response = arguments;
+
+				self.html( selector ?
+
+					// If a selector was specified, locate the right elements in a dummy div
+					// Exclude scripts to avoid IE 'Permission Denied' errors
+					jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+					// Otherwise use the full result
+					responseText );
+
+			// If the request succeeds, this function gets "data", "status", "jqXHR"
+			// but they are ignored because response was set above.
+			// If it fails, this function gets "jqXHR", "status", "error"
+			} ).always( callback && function( jqXHR, status ) {
+				self.each( function() {
+					callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
+				} );
+			} );
+		}
+
+		return this;
+	};
+
+
+
+
+	// Attach a bunch of functions for handling common AJAX events
+	jQuery.each( [
+		"ajaxStart",
+		"ajaxStop",
+		"ajaxComplete",
+		"ajaxError",
+		"ajaxSuccess",
+		"ajaxSend"
+	], function( i, type ) {
+		jQuery.fn[ type ] = function( fn ) {
+			return this.on( type, fn );
+		};
+	} );
+
+
+
+
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep( jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		} ).length;
+	};
+
+
+
+
+	/**
+	 * Gets a window from an element
+	 */
+	function getWindow( elem ) {
+		return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
+	}
+
+	jQuery.offset = {
+		setOffset: function( elem, options, i ) {
+			var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+				position = jQuery.css( elem, "position" ),
+				curElem = jQuery( elem ),
+				props = {};
+
+			// Set position first, in-case top/left are set even on static elem
+			if ( position === "static" ) {
+				elem.style.position = "relative";
+			}
+
+			curOffset = curElem.offset();
+			curCSSTop = jQuery.css( elem, "top" );
+			curCSSLeft = jQuery.css( elem, "left" );
+			calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+				( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
+
+			// Need to be able to calculate position if either
+			// top or left is auto and position is either absolute or fixed
+			if ( calculatePosition ) {
+				curPosition = curElem.position();
+				curTop = curPosition.top;
+				curLeft = curPosition.left;
+
+			} else {
+				curTop = parseFloat( curCSSTop ) || 0;
+				curLeft = parseFloat( curCSSLeft ) || 0;
+			}
+
+			if ( jQuery.isFunction( options ) ) {
+
+				// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
+				options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
+			}
+
+			if ( options.top != null ) {
+				props.top = ( options.top - curOffset.top ) + curTop;
+			}
+			if ( options.left != null ) {
+				props.left = ( options.left - curOffset.left ) + curLeft;
+			}
+
+			if ( "using" in options ) {
+				options.using.call( elem, props );
+
+			} else {
+				curElem.css( props );
+			}
+		}
+	};
+
+	jQuery.fn.extend( {
+		offset: function( options ) {
+			if ( arguments.length ) {
+				return options === undefined ?
+					this :
+					this.each( function( i ) {
+						jQuery.offset.setOffset( this, options, i );
+					} );
+			}
+
+			var docElem, win,
+				elem = this[ 0 ],
+				box = { top: 0, left: 0 },
+				doc = elem && elem.ownerDocument;
+
+			if ( !doc ) {
+				return;
+			}
+
+			docElem = doc.documentElement;
+
+			// Make sure it's not a disconnected DOM node
+			if ( !jQuery.contains( docElem, elem ) ) {
+				return box;
+			}
+
+			box = elem.getBoundingClientRect();
+			win = getWindow( doc );
+			return {
+				top: box.top + win.pageYOffset - docElem.clientTop,
+				left: box.left + win.pageXOffset - docElem.clientLeft
+			};
+		},
+
+		position: function() {
+			if ( !this[ 0 ] ) {
+				return;
+			}
+
+			var offsetParent, offset,
+				elem = this[ 0 ],
+				parentOffset = { top: 0, left: 0 };
+
+			// Fixed elements are offset from window (parentOffset = {top:0, left: 0},
+			// because it is its only offset parent
+			if ( jQuery.css( elem, "position" ) === "fixed" ) {
+
+				// Assume getBoundingClientRect is there when computed position is fixed
+				offset = elem.getBoundingClientRect();
+
+			} else {
+
+				// Get *real* offsetParent
+				offsetParent = this.offsetParent();
+
+				// Get correct offsets
+				offset = this.offset();
+				if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+					parentOffset = offsetParent.offset();
+				}
+
+				// Add offsetParent borders
+				parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+				parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+			}
+
+			// Subtract parent offsets and element margins
+			return {
+				top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+				left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+			};
+		},
+
+		// This method will return documentElement in the following cases:
+		// 1) For the element inside the iframe without offsetParent, this method will return
+		//    documentElement of the parent window
+		// 2) For the hidden or detached element
+		// 3) For body or html element, i.e. in case of the html node - it will return itself
+		//
+		// but those exceptions were never presented as a real life use-cases
+		// and might be considered as more preferable results.
+		//
+		// This logic, however, is not guaranteed and can change at any point in the future
+		offsetParent: function() {
+			return this.map( function() {
+				var offsetParent = this.offsetParent;
+
+				while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
+					offsetParent = offsetParent.offsetParent;
+				}
+
+				return offsetParent || documentElement;
+			} );
+		}
+	} );
+
+	// Create scrollLeft and scrollTop methods
+	jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+		var top = "pageYOffset" === prop;
+
+		jQuery.fn[ method ] = function( val ) {
+			return access( this, function( elem, method, val ) {
+				var win = getWindow( elem );
+
+				if ( val === undefined ) {
+					return win ? win[ prop ] : elem[ method ];
+				}
+
+				if ( win ) {
+					win.scrollTo(
+						!top ? val : win.pageXOffset,
+						top ? val : win.pageYOffset
+					);
+
+				} else {
+					elem[ method ] = val;
+				}
+			}, method, val, arguments.length );
+		};
+	} );
+
+	// Support: Safari<7-8+, Chrome<37-44+
+	// Add the top/left cssHooks using jQuery.fn.position
+	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+	// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280
+	// getComputedStyle returns percent when specified for top/left/bottom/right;
+	// rather than make the css module depend on the offset module, just check for it here
+	jQuery.each( [ "top", "left" ], function( i, prop ) {
+		jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+			function( elem, computed ) {
+				if ( computed ) {
+					computed = curCSS( elem, prop );
+
+					// If curCSS returns percentage, fallback to offset
+					return rnumnonpx.test( computed ) ?
+						jQuery( elem ).position()[ prop ] + "px" :
+						computed;
+				}
+			}
+		);
+	} );
+
+
+	// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+	jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+		jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
+			function( defaultExtra, funcName ) {
+
+			// Margin is only for outerHeight, outerWidth
+			jQuery.fn[ funcName ] = function( margin, value ) {
+				var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+					extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+				return access( this, function( elem, type, value ) {
+					var doc;
+
+					if ( jQuery.isWindow( elem ) ) {
+
+						// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+						// isn't a whole lot we can do. See pull request at this URL for discussion:
+						// https://github.com/jquery/jquery/pull/764
+						return elem.document.documentElement[ "client" + name ];
+					}
+
+					// Get document width or height
+					if ( elem.nodeType === 9 ) {
+						doc = elem.documentElement;
+
+						// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+						// whichever is greatest
+						return Math.max(
+							elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+							elem.body[ "offset" + name ], doc[ "offset" + name ],
+							doc[ "client" + name ]
+						);
+					}
+
+					return value === undefined ?
+
+						// Get width or height on the element, requesting but not forcing parseFloat
+						jQuery.css( elem, type, extra ) :
+
+						// Set width or height on the element
+						jQuery.style( elem, type, value, extra );
+				}, type, chainable ? margin : undefined, chainable, null );
+			};
+		} );
+	} );
+
+
+	jQuery.fn.extend( {
+
+		bind: function( types, data, fn ) {
+			return this.on( types, null, data, fn );
+		},
+		unbind: function( types, fn ) {
+			return this.off( types, null, fn );
+		},
+
+		delegate: function( selector, types, data, fn ) {
+			return this.on( types, selector, data, fn );
+		},
+		undelegate: function( selector, types, fn ) {
+
+			// ( namespace ) or ( selector, types [, fn] )
+			return arguments.length === 1 ?
+				this.off( selector, "**" ) :
+				this.off( types, selector || "**", fn );
+		},
+		size: function() {
+			return this.length;
+		}
+	} );
+
+	jQuery.fn.andSelf = jQuery.fn.addBack;
+
+
+
+
+	// Register as a named AMD module, since jQuery can be concatenated with other
+	// files that may use define, but not via a proper concatenation script that
+	// understands anonymous AMD modules. A named AMD is safest and most robust
+	// way to register. Lowercase jquery is used because AMD module names are
+	// derived from file names, and jQuery is normally delivered in a lowercase
+	// file name. Do this after creating the global so that if an AMD module wants
+	// to call noConflict to hide this version of jQuery, it will work.
+
+	// Note that for maximum portability, libraries that are not jQuery should
+	// declare themselves as anonymous modules, and avoid setting a global if an
+	// AMD loader is present. jQuery is a special case. For more information, see
+	// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+	if ( true ) {
+		!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {
+			return jQuery;
+		}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	}
+
+
+
+	var
+
+		// Map over jQuery in case of overwrite
+		_jQuery = window.jQuery,
+
+		// Map over the $ in case of overwrite
+		_$ = window.$;
+
+	jQuery.noConflict = function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	};
+
+	// Expose jQuery and $ identifiers, even in AMD
+	// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+	// and CommonJS for browser emulators (#13566)
+	if ( !noGlobal ) {
+		window.jQuery = window.$ = jQuery;
+	}
+
+	return jQuery;
+	}));
+
+
+/***/ }
+/******/ ]);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/static/iconolab/js/main.js	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,17 @@
+var cutout = require('./components/cutout');
+
+/* expose jQuery */
+var test = require('expose?jQuery!jquery');
+
+var api = {
+	initCutoutComponent: function (config) {
+		return cutout.init(config);
+	},
+	testComponent: function () {}
+};
+
+module.exports = api;
+
+iconolab = api;
+
+console.log(test);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/static/iconolab/js/package.json	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,36 @@
+{
+  "name": "iconolab",
+  "version": "0.0.1",
+  "description": "Iconolab by IRI",
+  "main": "main.js",
+  "scripts": {
+    "test": "run test",
+    "start": "webpack-dev-server --inline & webpack --progress --colors --watch",
+    "build": "webpack -p"
+  },
+  "keywords": [
+    "annotation",
+    "image",
+    "iconolab"
+  ],
+  "author": "h.baptiste",
+  "license": "ISC",
+  "dependencies": {
+    "bootstrap": "^3.3.6",
+    "css-loader": "^0.23.1",
+    "expose-loader": "^0.7.1",
+    "html-loader": "^0.4.3",
+    "jquery": "^2.2.4",
+    "lodash": "^4.13.1",
+    "snapsvg": "^0.4.0",
+    "style-loader": "^0.13.1",
+    "vue": "^1.0.24",
+    "vue-resource": "^0.7.2"
+  },
+  "devDependencies": {
+    "imports-loader": "^0.6.5",
+    "snapsvg": "^0.4.0",
+    "webpack": "^1.13.1",
+    "webpack-dev-server": "^1.14.1"
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/static/iconolab/js/webpack.config.js	Tue May 31 17:46:32 2016 +0200
@@ -0,0 +1,25 @@
+var path = require('path');
+var webpack=  require('webpack');
+
+module.exports = {
+	entry: './main.js',
+	output: {
+		path: path.join(__dirname,'dist'),
+		filename: 'bundle.js'
+	},
+	module: {
+		loaders: [
+			{test: /\.css$/, loader: 'style!css'},
+			{test: /\.html$/, loader: 'html'},
+			{
+            	test: require.resolve('snapsvg'),
+                loader: 'imports-loader?this=>window,fix=>module.exports=0'
+            },
+		]
+	},
+
+	resolve: {
+		extensions: ['', '.js', '.json', '.html'],
+		modulesDirectories: ['node_modules', 'loaders']
+	}
+}
\ No newline at end of file