Initial commit
authordsc <david.schoonover@gmail.com>
Sat, 30 Oct 2010 23:54:53 +0000 (16:54 -0700)
committerdsc <david.schoonover@gmail.com>
Sat, 30 Oct 2010 23:54:53 +0000 (16:54 -0700)
98 files changed:
css/log.css [new file with mode: 0644]
css/lttl.css [new file with mode: 0644]
css/reset.css [new file with mode: 0644]
css/test.css [new file with mode: 0644]
index.php [new file with mode: 0644]
lib/cake.js [new file with mode: 0644]
lib/excanvas.js [new file with mode: 0644]
lib/excanvas.min.js [new file with mode: 0644]
lib/functional.js [new file with mode: 0644]
lib/gury.js [new file with mode: 0644]
lib/jquery-1.4.3.js [new file with mode: 0644]
lib/jquery-1.4.3.min.js [new file with mode: 0644]
lib/jquery.hotkeys.js [new file with mode: 0644]
lib/jquery.hotkeys.min.js [new file with mode: 0644]
lib/processing-0.9.7.js [new file with mode: 0644]
lib/processing-0.9.7.min.js [new file with mode: 0644]
lib/qunit/qunit.css [new file with mode: 0644]
lib/qunit/qunit.js [new file with mode: 0644]
lib/raphael-min.js [new file with mode: 0644]
lib/raphael.js [new file with mode: 0644]
lib/uki-0.3.8.js [new file with mode: 0644]
lib/uki-0.3.8.min.js [new file with mode: 0644]
lib/uki-more-0.3.8.min.js [new file with mode: 0644]
notes.md [new file with mode: 0644]
src/Y/_intro.js [new file with mode: 0644]
src/Y/_outro.js [new file with mode: 0644]
src/Y/alias.js [new file with mode: 0644]
src/Y/core.js [new file with mode: 0644]
src/Y/modules/event.js [new file with mode: 0644]
src/Y/modules/metaclass.js [new file with mode: 0644]
src/Y/modules/y.event.js [new file with mode: 0644]
src/Y/modules/y.json.js [new file with mode: 0644]
src/Y/modules/y.kv.js [new file with mode: 0644]
src/Y/modules/y.op.js [new file with mode: 0644]
src/Y/modules/y.plugin-arch.js [new file with mode: 0644]
src/Y/modules/y.polyevent.js [new file with mode: 0644]
src/Y/type.js [new file with mode: 0644]
src/Y/y-array.js [new file with mode: 0644]
src/Y/y-class.js [new file with mode: 0644]
src/Y/y-collection.js [new file with mode: 0644]
src/Y/y-core.js [new file with mode: 0644]
src/Y/y-function.js [new file with mode: 0644]
src/Y/y-number.js [new file with mode: 0644]
src/Y/y-object.js [new file with mode: 0644]
src/Y/y-op.js [new file with mode: 0644]
src/Y/y-string.js [new file with mode: 0644]
src/Y/y.js.php [new file with mode: 0644]
src/js/_intro.js [new file with mode: 0644]
src/js/_outro.js [new file with mode: 0644]
src/js/array.js [new file with mode: 0644]
src/js/core.js [new file with mode: 0644]
src/js/function.js [new file with mode: 0644]
src/js/js.js.php [new file with mode: 0644]
src/js/number.js [new file with mode: 0644]
src/js/object.js [new file with mode: 0644]
src/js/regexp.js [new file with mode: 0644]
src/js/string.js [new file with mode: 0644]
src/js/type.js [new file with mode: 0644]
src/lessly/bitgrid.js [new file with mode: 0644]
src/lessly/draw.js [new file with mode: 0644]
src/lessly/future.js [new file with mode: 0644]
src/lessly/log.js [new file with mode: 0644]
src/lessly/log.uki.js [new file with mode: 0644]
src/lessly/viewport.js [new file with mode: 0644]
src/portal/layer.js [new file with mode: 0644]
src/portal/path.js [new file with mode: 0644]
src/portal/portal.js [new file with mode: 0644]
src/portal/shape.js [new file with mode: 0644]
src/portal/simpleclass.js [new file with mode: 0644]
src/portal/util/cooldown.js [new file with mode: 0644]
src/portal/util/eventloop.js [new file with mode: 0644]
src/portal/util/loc.js [new file with mode: 0644]
src/portal/util/pointquadtree.js [new file with mode: 0644]
src/portal/util/quadtree.js [new file with mode: 0644]
src/portal/util/rbtree.js [new file with mode: 0644]
src/simoon/ability/ability.js [new file with mode: 0644]
src/simoon/ability/laser.js [new file with mode: 0644]
src/simoon/ability/projectile.js [new file with mode: 0644]
src/simoon/game/calc.js [new file with mode: 0644]
src/simoon/game/draw.js [new file with mode: 0644]
src/simoon/game/game.js [new file with mode: 0644]
src/simoon/game/map.js [new file with mode: 0644]
src/simoon/globals.js [new file with mode: 0644]
src/simoon/grid/grid.js [new file with mode: 0644]
src/simoon/player/player.js [new file with mode: 0644]
src/simoon/player/test-player.js [new file with mode: 0644]
src/simoon/simoon.js [new file with mode: 0644]
src/simoon/ui.js [new file with mode: 0644]
src/simoon/unit/agent.js [new file with mode: 0644]
src/simoon/unit/creep.js [new file with mode: 0644]
src/simoon/unit/tower.js [new file with mode: 0644]
src/simoon/unit/unit.js [new file with mode: 0644]
src/tanks/game.js [new file with mode: 0644]
src/tanks/lttl.js [new file with mode: 0644]
src/tanks/map.js [new file with mode: 0644]
src/tanks/tank.js [new file with mode: 0644]
src/tanks/ui.js [new file with mode: 0644]
src/u.js [new file with mode: 0644]

diff --git a/css/log.css b/css/log.css
new file mode 100644 (file)
index 0000000..897fee1
--- /dev/null
@@ -0,0 +1,12 @@
+#log {
+       position:fixed; top:0; right:0; width:50%; height:100%; overflow:hidden;
+       border-left:1px solid #bbb; background-color: #fff; color:#333; }
+#log h5 { font-size:1.25em; font-weight:normal; text-transform:uppercase; letter-spacing:0.16667em; }
+
+.log_meta { position:absolute; top:0; left:0; width:100%; z-index:2; background-color:#fff; }
+.log_meta > * { padding:0.3em; }
+       .log_menu { color:#fff; background-color:#bbb; }
+       .log_menu a { color:#fff; cursor:pointer; }
+.log_container { position:relative; width:100%; height:100%; overflow:auto; z-index:1; }
+       .log_item { padding:0.5em; font:normal normal 10pt/1 Menlo, Monospace; text-decoration:none; }
+
diff --git a/css/lttl.css b/css/lttl.css
new file mode 100644 (file)
index 0000000..d746829
--- /dev/null
@@ -0,0 +1,18 @@
+html, body { width:100%; height:100%; 
+    font-family:Geogrotesque,Helvetica; color:#fff; background-color:#3F3F3F; }
+body { font-family:Geogrotesque, Helvetica; font-size:12pt; }
+h1 { position:fixed; top:0; right:0; margin:0; padding:0; font-size:3em; color:#000; opacity:0.25; z-index:100; }
+ul, ol, li { list-style: none ! important; margin:0; padding:0; }
+
+.rounded { border-radius:1em; -moz-border-radius:1em; -webkit-border-radius:1em; }
+
+#viewport { position:relative; top:1em; width:500px; height:500px; margin:0 auto; }
+
+#howto { position:fixed; top:3em; right:1em; color:#BFBFBF; }
+
+#info { position:fixed; bottom:10px; right:10px; padding:0.5em; background-color:rgba(0,0,0, 0.1); color:#787878; }
+    #info label { display:block; float:left; width:3em; margin-right:0.5em; color:#787878; }
+    #info input { border:0; background-color:transparent; min-width:5em; width:5em; color:#5c5c5c; }
+
+#log { position:fixed; top:auto; bottom:0; left:0; width:100%; height:30%; border-top:1px solid #bbb; }
+
diff --git a/css/reset.css b/css/reset.css
new file mode 100644 (file)
index 0000000..dd5886b
--- /dev/null
@@ -0,0 +1,2 @@
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
+h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;} em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;}
\ No newline at end of file
diff --git a/css/test.css b/css/test.css
new file mode 100644 (file)
index 0000000..1c2d3e4
--- /dev/null
@@ -0,0 +1,4 @@
+html, body { width:100%; height:100%; 
+    font-family:Geogrotesque,Helvetica; color:#fff; background-color:#3F3F3F; }
+h1:not([id]) { position:fixed; top:-0.25em; right:-0.25em; margin:0; padding:0;
+    font-size:3em; color:#000; opacity:0.25; z-index:100; }
diff --git a/index.php b/index.php
new file mode 100644 (file)
index 0000000..8806461
--- /dev/null
+++ b/index.php
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>The Littlest Battletank</title>
+<link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
+<link rel="stylesheet" href="css/lttl.css" type="text/css" media="screen">
+</head>
+<body class="lttl tanks">
+
+
+<div id="viewport"></div>
+
+<h1>The Littlest Battletank</h1>
+
+<ul id="info" class="rounded">
+    <li id="state"></li>
+    <li><label for="fps">fps</label> <input id="fps" name="fps" value="" type="text"></li>
+    <li><label for="frame">frame</label> <input id="frame" name="frame" value="" type="text"></li>
+    <li><label for="agents">agents</label> <input id="agents" name="agents" value="" type="text"></li>
+    <li><label for="units">units</label> <input id="units" name="units" value="" type="text"></li>
+    <li><label for="bullets">bullets</label> <input id="bullets" name="bullets" value="" type="text"></li>
+</ul>
+
+<div id="log" style="display:none"></div>
+
+<div id="scripts">
+    <!--[if IE]><script type="text/javascript" src="lib/excanvas.min.js"></script><![endif]-->
+<?php
+$scripts = array(
+    "lib/jquery-1.4.3.js",
+    "lib/jquery.hotkeys.js",
+    
+    // "http://static.ukijs.org/pkg/0.3.8/uki.js",
+    // "http://static.ukijs.org/pkg/0.3.8/uki-more.js",
+    
+    "src/lessly/future.js",
+    
+    "src/Y/y.js.php",
+    "src/Y/modules/y.event.js",
+    
+    "src/portal/layer.js",
+    "src/portal/shape.js",
+    
+    // "src/portal/util/quadtree.js",
+    // "src/portal/util/rbtree.js",
+    // "src/portal/util/eventloop.js",
+    // "src/portal/util/cooldown.js",
+    // "src/portal/util/loc.js",
+    
+    "src/tanks/map.js",
+    "src/tanks/tank.js",
+    "src/tanks/game.js",
+    "src/tanks/ui.js",
+    
+    "src/tanks/lttl.js"
+);
+
+function js($src) {
+    echo "    <script src=\"$src\" type=\"text/javascript\"></script>\n";
+}
+
+foreach ($scripts as $s) js($s);
+?>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/lib/cake.js b/lib/cake.js
new file mode 100644 (file)
index 0000000..e222e3c
--- /dev/null
@@ -0,0 +1,7657 @@
+/*
+CAKE - Canvas Animation Kit Experiment
+
+Copyright (C) 2007  Ilmari Heikkinen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+
+/**
+  Delete the first instance of obj from the array.
+
+  @param obj The object to delete
+  @return true on success, false if array contains no instances of obj
+  @type boolean
+  @addon
+  */
+Array.prototype.deleteFirst = function(obj) {
+  for (var i=0; i<this.length; i++) {
+    if (this[i] == obj) {
+      this.splice(i,1)
+      return true
+    }
+  }
+  return false
+}
+
+Array.prototype.stableSort = function(cmp) {
+  // hack to work around Chrome's qsort
+  for(var i=0; i<this.length; i++) {
+    this[i].__arrayPos = i;
+  }
+  return this.sort(Array.__stableSorter(cmp));
+}
+Array.__stableSorter = function(cmp) {
+  return (function(c1, c2) {
+    var r = cmp(c1,c2);
+    if (!r) { // hack to work around Chrome's qsort
+      return c1.__arrayPos - c2.__arrayPos
+    }
+    return r;
+  });
+}
+
+/**
+  Compares two arrays for equality. Returns true if the arrays are equal.
+  */
+Array.prototype.equals = function(array) {
+  if (!array) return false
+  if (this.length != array.length) return false
+  for (var i=0; i<this.length; i++) {
+    var a = this[i]
+    var b = array[i]
+    if (a.equals && typeof(a.equals) == 'function') {
+      if (!a.equals(b)) return false
+    } else if (a != b) {
+      return false
+    }
+  }
+  return true
+}
+
+/**
+  Rotates the first element of an array to be the last element.
+  Rotates last element to be the first element when backToFront is true.
+
+  @param {boolean} backToFront Whether to move the last element to the front or not
+  @return The last element when backToFront is false, the first element when backToFront is true
+  @addon
+  */
+Array.prototype.rotate = function(backToFront) {
+  if (backToFront) {
+    this.unshift(this.pop())
+    return this[0]
+  } else {
+    this.push(this.shift())
+    return this[this.length-1]
+  }
+}
+/**
+  Returns a random element from the array.
+
+  @return A random element
+  @addon
+ */
+Array.prototype.pick = function() {
+  return this[Math.floor(Math.random()*this.length)]
+}
+
+Array.prototype.flatten = function() {
+  var a = []
+  for (var i=0; i<this.length; i++) {
+    var e = this[i]
+    if (e.flatten) {
+      var ef = e.flatten()
+      for (var j=0; j<ef.length; j++) {
+        a[a.length] = ef[j]
+      }
+    } else {
+      a[a.length] = e
+    }
+  }
+  return a
+}
+
+Array.prototype.take = function() {
+  var a = []
+  for (var i=0; i<this.length; i++) {
+    var e = []
+    for (var j=0; j<arguments.length; j++) {
+      e[j] = this[i][arguments[j]]
+    }
+    a[i] = e
+  }
+  return a
+}
+
+if (!Array.prototype.pluck) {
+  Array.prototype.pluck = function(key) {
+    var a = []
+    for (var i=0; i<this.length; i++) {
+      a[i] = this[i][key]
+    }
+    return a
+  }
+}
+
+Array.prototype.set = function(key, value) {
+  for (var i=0; i<this.length; i++) {
+    this[i][key] = value
+  }
+}
+
+Array.prototype.allWith = function() {
+  var a = []
+  topLoop:
+  for (var i=0; i<this.length; i++) {
+    var e = this[i]
+    for (var j=0; j<arguments.length; j++) {
+      if (!this[i][arguments[j]])
+        continue topLoop
+    }
+    a[a.length] = e
+  }
+  return a
+}
+
+// some common helper methods
+
+if (!Function.prototype.bind) {
+  /**
+    Creates a function that calls this function in the scope of the given
+    object.
+
+      var obj = { x: 'obj' }
+      var f = function() { return this.x }
+      window.x = 'window'
+      f()
+      // => 'window'
+      var g = f.bind(obj)
+      g()
+      // => 'obj'
+
+    @param object Object to bind this function to
+    @return Function bound to object
+    @addon
+    */
+  Function.prototype.bind = function(object) {
+    var t = this
+    return function() {
+      return t.apply(object, arguments)
+    }
+  }
+}
+
+if (!Array.prototype.last) {
+  /**
+    Returns the last element of the array.
+
+    @return The last element of the array
+    @addon
+    */
+  Array.prototype.last = function() {
+    return this[this.length-1]
+  }
+}
+if (!Array.prototype.indexOf) {
+  /**
+    Returns the index of obj if it is in the array.
+    Returns -1 otherwise.
+
+    @param obj The object to find from the array.
+    @return The index of obj or -1 if obj isn't in the array.
+    @addon
+    */
+  Array.prototype.indexOf = function(obj) {
+    for (var i=0; i<this.length; i++)
+      if (obj == this[i]) return i
+    return -1
+  }
+}
+if (!Array.prototype.includes) {
+  /**
+    Returns true if obj is in the array.
+    Returns false if it isn't.
+
+    @param obj The object to find from the array.
+    @return True if obj is in the array, false if it isn't
+    @addon
+    */
+  Array.prototype.includes = function(obj) {
+    return (this.indexOf(obj) >= 0);
+  }
+}
+/**
+  Iterate function f over each element of the array and return an array
+  of the return values.
+
+  @param f Function to apply to each element
+  @return An array of return values from applying f on each element of the array
+  @type Array
+  @addon
+  */
+Array.prototype.map = function(f) {
+  var na = new Array(this.length)
+  if (f)
+    for (var i=0; i<this.length; i++) na[i] = f(this[i], i, this)
+  else
+    for (var i=0; i<this.length; i++) na[i] = this[i]
+  return na
+}
+Array.prototype.forEach = function(f) {
+  for (var i=0; i<this.length; i++) f(this[i], i, this)
+}
+if (!Array.prototype.reduce) {
+  Array.prototype.reduce = function(f, s) {
+    var i = 0
+    if (arguments.length == 1) {
+      s = this[0]
+      i++
+    }
+    for(; i<this.length; i++) {
+      s = f(s, this[i], i, this)
+    }
+    return s
+  }
+}
+if (!Array.prototype.find) {
+  Array.prototype.find = function(f) {
+    for(var i=0; i<this.length; i++) {
+      if (f(this[i], i, this)) return this[i]
+    }
+  }
+}
+
+if (!String.prototype.capitalize) {
+  /**
+    Returns a copy of this string with the first character uppercased.
+
+    @return Capitalized version of the string
+    @type String
+    @addon
+    */
+  String.prototype.capitalize = function() {
+    return this.replace(/^./, this.slice(0,1).toUpperCase())
+  }
+}
+
+if (!String.prototype.escape) {
+  /**
+    Returns a version of the string that can be used as a string literal.
+
+    @return Copy of string enclosed in double-quotes, with double-quotes
+            inside string escaped.
+    @type String
+    @addon
+    */
+  String.prototype.escape = function() {
+    return '"' + this.replace(/"/g, '\\"') + '"'
+  }
+}
+if (!String.prototype.splice) {
+  String.prototype.splice = function(start, count, replacement) {
+    return this.slice(0,start) + replacement + this.slice(start+count)
+  }
+}
+if (!String.prototype.strip) {
+  /**
+    Returns a copy of the string with preceding and trailing whitespace
+    removed.
+
+    @return Copy of string sans surrounding whitespace.
+    @type String
+    @addon
+    */
+  String.prototype.strip = function() {
+    return this.replace(/^\s+|\s+$/g, '')
+  }
+}
+
+if (!window['$A']) {
+  /**
+    Creates a new array from an object with #length.
+    */
+  $A = function(obj) {
+    var a = new Array(obj.length)
+    for (var i=0; i<obj.length; i++)
+      a[i] = obj[i]
+    return a
+  }
+}
+
+if (!window['$']) {
+  $ = function(id) {
+    return document.getElementById(id)
+  }
+}
+
+if (!Math.sinh) {
+  /**
+    Returns the hyperbolic sine of x.
+
+    @param x The value for x
+    @return The hyperbolic sine of x
+    @addon
+    */
+  Math.sinh = function(x) {
+    return 0.5 * (Math.exp(x) - Math.exp(-x))
+  }
+  /**
+    Returns the inverse hyperbolic sine of x.
+
+    @param x The value for x
+    @return The inverse hyperbolic sine of x
+    @addon
+    */
+  Math.asinh = function(x) {
+    return Math.log(x + Math.sqrt(x*x + 1))
+  }
+}
+if (!Math.cosh) {
+  /**
+    Returns the hyperbolic cosine of x.
+
+    @param x The value for x
+    @return The hyperbolic cosine of x
+    @addon
+    */
+  Math.cosh = function(x) {
+    return 0.5 * (Math.exp(x) + Math.exp(-x))
+  }
+  /**
+    Returns the inverse hyperbolic cosine of x.
+
+    @param x The value for x
+    @return The inverse hyperbolic cosine of x
+    @addon
+    */
+  Math.acosh = function(x) {
+    return Math.log(x + Math.sqrt(x*x - 1))
+  }
+}
+
+/**
+  Creates and configures a DOM element.
+
+  The tag of the element is given by name.
+
+  If params is a string, it is used as the innerHTML of the created element.
+  If params is a DOM element, it is appended to the created element.
+  If params is an object, it is treated as a config object and merged
+  with the created element.
+
+  If params is a string or DOM element, the third argument is treated
+  as the config object.
+
+  Special attributes of the config object:
+    * content
+      - if content is a string, it is used as the innerHTML of the
+        created element
+      - if content is an element, it is appended to the created element
+    * style
+      - the style object is merged with the created element's style
+
+  @param {String} name The tag for the created element
+  @param params The content or config for the created element
+  @param config The config for the created element if params is content
+  @return The created DOM element
+  */
+E = function(name, params, config) {
+  var el = document.createElement(name)
+  if (params) {
+    if (typeof(params) == 'string') {
+      el.innerHTML = params
+      params = config
+    } else if (params.DOCUMENT_NODE) {
+      el.appendChild(params)
+      params = config
+    }
+    if (params) {
+      if (params.style) {
+        var style = params.style
+        params = Object.clone(params)
+        delete params.style
+        Object.forceExtend(el.style, style)
+      }
+      if (params.content) {
+        if (typeof(params.content) == 'string') {
+          el.appendChild(T(params.content))
+        } else {
+          el.appendChild(params.content)
+        }
+        params = Object.clone(params)
+        delete params.content
+      }
+      Object.forceExtend(el, params)
+    }
+  }
+  return el
+}
+E.append = function(node) {
+  for(var i=1; i<arguments.length; i++) {
+    if (typeof(arguments[i]) == 'string') {
+      node.appendChild(T(arguments[i]))
+    } else {
+      node.appendChild(arguments[i])
+    }
+  }
+}
+// Safari requires each canvas to have a unique id.
+E.lastCanvasId = 0
+/**
+  Creates and returns a canvas element with width w and height h.
+
+  @param {int} w The width for the canvas
+  @param {int} h The height for the canvas
+  @param config Optional config object to pass to E()
+  @return The created canvas element
+  */
+E.canvas = function(w,h,config) {
+  var id = 'canvas-uuid-' + E.lastCanvasId
+  E.lastCanvasId++
+  if (!config) config = {}
+  return E('canvas', Object.extend(config, {id: id, width: w, height: h}))
+}
+
+/**
+  Shortcut for document.createTextNode.
+
+  @param {String} text The text for the text node
+  @return The created text node
+  */
+T = function(text) {
+  return document.createTextNode(text)
+}
+
+/**
+  Merges the src object's attributes with the dst object, ignoring errors.
+
+  @param dst The destination object
+  @param src The source object
+  @return The dst object
+  @addon
+  */
+Object.forceExtend = function(dst, src) {
+  for (var i in src) {
+    try{ dst[i] = src[i] } catch(e) {}
+  }
+  return dst
+}
+// In case Object.extend isn't defined already, set it to Object.forceExtend.
+if (!Object.extend)
+  Object.extend = Object.forceExtend
+
+/**
+  Merges the src object's attributes with the dst object, preserving all dst
+  object's current attributes.
+
+  @param dst The destination object
+  @param src The source object
+  @return The dst object
+  @addon
+  */
+Object.conditionalExtend = function(dst, src) {
+  for (var i in src) {
+    if (dst[i] == null)
+      dst[i] = src[i]
+  }
+  return dst
+}
+
+/**
+  Creates and returns a shallow copy of the src object.
+
+  @param src The source object
+  @return A clone of the src object
+  @addon
+  */
+Object.clone = function(src) {
+  if (!src || src == true)
+    return src
+  switch (typeof(src)) {
+    case 'string':
+      return Object.extend(src+'', src)
+      break
+    case 'number':
+      return src
+      break
+    case 'function':
+      obj = eval(src.toSource())
+      return Object.extend(obj, src)
+      break
+    case 'object':
+      if (src instanceof Array) {
+        return Object.extend([], src)
+      } else {
+        return Object.extend({}, src)
+      }
+      break
+  }
+}
+
+/**
+  Creates and returns an Image object, with source URL set to src and
+  onload handler set to onload.
+
+  @param {String} src The source URL for the image
+  @param {Function} onload The onload handler for the image
+  @return The created Image object
+  @type {Image}
+  */
+Object.loadImage = function(src, onload) {
+  var img = new Image()
+  if (onload)
+    img.onload = onload
+  img.src = src
+  return img
+}
+
+/**
+  Returns true if image is fully loaded and ready for use.
+
+  @param image The image to check
+  @return Whether the image is loaded or not
+  @type {boolean}
+  @addon
+  */
+Object.isImageLoaded = function(image) {
+  if (image.tagName == 'CANVAS') return true
+  if (!image.complete) return false
+  if (image.naturalWidth == null) return true
+  return !!image.naturalWidth
+}
+
+/**
+  Sums two objects.
+  */
+Object.sum = function(a,b) {
+  if (a instanceof Array) {
+    if (b instanceof Array) {
+      var ab = []
+      for (var i=0; i<a.length; i++) {
+        ab[i] = a[i] + b[i]
+      }
+      return ab
+    } else {
+      return a.map(function(v){ return v + b })
+    }
+  } else if (b instanceof Array) {
+    return b.map(function(v){ return v + a })
+  } else {
+    return a + b
+  }
+}
+
+/**
+  Substracts b from a.
+  */
+Object.sub = function(a,b) {
+  if (a instanceof Array) {
+    if (b instanceof Array) {
+      var ab = []
+      for (var i=0; i<a.length; i++) {
+        ab[i] = a[i] - b[i]
+      }
+      return ab
+    } else {
+      return a.map(function(v){ return v - b })
+    }
+  } else if (b instanceof Array) {
+    return b.map(function(v){ return a - v })
+  } else {
+    return a - b
+  }
+}
+
+if (!window.Mouse) Mouse = {}
+/**
+  Returns the coordinates for a mouse event relative to element.
+  Element must be the target for the event.
+
+  @param element The element to compare against
+  @param event The mouse event
+  @return An object of form {x: relative_x, y: relative_y}
+  */
+Mouse.getRelativeCoords = function(element, event) {
+  var xy = {x:0, y:0}
+  var osl = 0
+  var ost = 0
+  var el = element
+  while (el) {
+    osl += el.offsetLeft
+    ost += el.offsetTop
+    el = el.offsetParent
+  }
+  xy.x = event.pageX - osl
+  xy.y = event.pageY - ost
+  return xy
+}
+
+Browser = (function(){
+  var ua = window.navigator.userAgent
+  var khtml = ua.match(/KHTML/)
+  var gecko = ua.match(/Gecko/)
+  var webkit = ua.match(/WebKit\/\d+/)
+  var ie = ua.match(/Explorer/)
+  if (khtml) return 'KHTML'
+  if (gecko) return 'Gecko'
+  if (webkit) return 'Webkit'
+  if (ie) return 'IE'
+  return 'UNKNOWN'
+})()
+
+
+Mouse.LEFT = 0
+Mouse.MIDDLE = 1
+Mouse.RIGHT = 2
+
+if (Browser == 'IE') {
+  Mouse.LEFT = 1
+  Mouse.MIDDLE = 4
+}
+
+
+/**
+  Klass is a function that returns a constructor function.
+
+  The constructor function calls #initialize with its arguments.
+
+  The parameters to Klass have their prototypes or themselves merged with the
+  constructor function's prototype.
+
+  Finally, the constructor function's prototype is merged with the constructor
+  function. So you can write Shape.getArea.call(this) instead of
+  Shape.prototype.getArea.call(this).
+
+  Shape = Klass({
+    getArea : function() {
+      raise('No area defined!')
+    }
+  })
+
+  Rectangle = Klass(Shape, {
+    initialize : function(x, y) {
+      this.x = x
+      this.y = y
+    },
+
+    getArea : function() {
+      return this.x * this.y
+    }
+  })
+
+  Square = Klass(Rectangle, {
+    initialize : function(s) {
+      Rectangle.initialize.call(this, s, s)
+    }
+  })
+
+  new Square(5).getArea()
+  //=> 25
+
+  @return Constructor object for the class
+  */
+Klass = function() {
+  var c = function() {
+    this.initialize.apply(this, arguments)
+  }
+  c.ancestors = $A(arguments)
+  c.prototype = {}
+  for(var i = 0; i<arguments.length; i++) {
+    var a = arguments[i]
+    if (a.prototype) {
+      Object.extend(c.prototype, a.prototype)
+    } else {
+      Object.extend(c.prototype, a)
+    }
+  }
+  Object.extend(c, c.prototype)
+  return c
+}
+
+
+
+Curves = {
+
+  angularDistance : function(a, b) {
+    var pi2 = Math.PI*2
+    var d = (b - a) % pi2
+    if (d > Math.PI) d -= pi2
+    if (d < -Math.PI) d += pi2
+    return d
+  },
+
+  linePoint : function(a, b, t) {
+    return [a[0]+(b[0]-a[0])*t, a[1]+(b[1]-a[1])*t]
+  },
+
+  quadraticPoint : function(a, b, c, t) {
+    // var d = this.linePoint(a,b,t)
+    // var e = this.linePoint(b,c,t)
+    // return this.linePoint(d,e,t)
+    var dx = a[0]+(b[0]-a[0])*t
+    var ex = b[0]+(c[0]-b[0])*t
+    var x = dx+(ex-dx)*t
+    var dy = a[1]+(b[1]-a[1])*t
+    var ey = b[1]+(c[1]-b[1])*t
+    var y = dy+(ey-dy)*t
+    return [x,y]
+  },
+
+  cubicPoint : function(a, b, c, d, t) {
+    var ax3 = a[0]*3
+    var bx3 = b[0]*3
+    var cx3 = c[0]*3
+    var ay3 = a[1]*3
+    var by3 = b[1]*3
+    var cy3 = c[1]*3
+    return [
+      a[0] + t*(bx3 - ax3 + t*(ax3-2*bx3+cx3 + t*(bx3-a[0]-cx3+d[0]))),
+      a[1] + t*(by3 - ay3 + t*(ay3-2*by3+cy3 + t*(by3-a[1]-cy3+d[1])))
+    ]
+  },
+
+  linearValue : function(a,b,t) {
+    return a + (b-a)*t
+  },
+
+  quadraticValue : function(a,b,c,t) {
+    var d = a + (b-a)*t
+    var e = b + (c-b)*t
+    return d + (e-d)*t
+  },
+
+  cubicValue : function(a,b,c,d,t) {
+    var a3 = a*3, b3 = b*3, c3 = c*3
+    return a + t*(b3 - a3 + t*(a3-2*b3+c3 + t*(b3-a-c3+d)))
+  },
+
+  catmullRomPoint : function (a,b,c,d, t) {
+    var af = ((-t+2)*t-1)*t*0.5
+    var bf = (((3*t-5)*t)*t+2)*0.5
+    var cf = ((-3*t+4)*t+1)*t*0.5
+    var df = ((t-1)*t*t)*0.5
+    return [
+      a[0]*af + b[0]*bf + c[0]*cf + d[0]*df,
+      a[1]*af + b[1]*bf + c[1]*cf + d[1]*df
+    ]
+  },
+
+  catmullRomAngle : function (a,b,c,d, t) {
+    var dx = 0.5 * (c[0] - a[0] + 2*t*(2*a[0] - 5*b[0] + 4*c[0] - d[0]) +
+             3*t*t*(3*b[0] + d[0] - a[0] - 3*c[0]))
+    var dy = 0.5 * (c[1] - a[1] + 2*t*(2*a[1] - 5*b[1] + 4*c[1] - d[1]) +
+             3*t*t*(3*b[1] + d[1] - a[1] - 3*c[1]))
+    return Math.atan2(dy, dx)
+  },
+
+  catmullRomPointAngle : function (a,b,c,d, t) {
+    var p = this.catmullRomPoint(a,b,c,d,t)
+    var a = this.catmullRomAngle(a,b,c,d,t)
+    return {point:p, angle:a}
+  },
+
+  lineAngle : function(a,b) {
+    return Math.atan2(b[1]-a[1], b[0]-a[0])
+  },
+
+  quadraticAngle : function(a,b,c,t) {
+    var d = this.linePoint(a,b,t)
+    var e = this.linePoint(b,c,t)
+    return this.lineAngle(d,e)
+  },
+
+  cubicAngle : function(a, b, c, d, t) {
+    var e = this.quadraticPoint(a,b,c,t)
+    var f = this.quadraticPoint(b,c,d,t)
+    return this.lineAngle(e,f)
+  },
+
+  lineLength : function(a,b) {
+    var x = (b[0]-a[0])
+    var y = (b[1]-a[1])
+    return Math.sqrt(x*x + y*y)
+  },
+
+  squareLineLength : function(a,b) {
+    var x = (b[0]-a[0])
+    var y = (b[1]-a[1])
+    return x*x + y*y
+  },
+
+  quadraticLength : function(a,b,c, error) {
+    var p1 = this.linePoint(a,b,2/3)
+    var p2 = this.linePoint(b,c,1/3)
+    return this.cubicLength(a,p1,p2,c, error)
+  },
+
+  cubicLength : (function() {
+    var bezsplit = function(v) {
+      var vtemp = [v.slice(0)]
+
+      for (var i=1; i < 4; i++) {
+        vtemp[i] = [[],[],[],[]]
+        for (var j=0; j < 4-i; j++) {
+          vtemp[i][j][0] = 0.5 * (vtemp[i-1][j][0] + vtemp[i-1][j+1][0])
+          vtemp[i][j][1] = 0.5 * (vtemp[i-1][j][1] + vtemp[i-1][j+1][1])
+        }
+      }
+      var left = []
+      var right = []
+      for (var j=0; j<4; j++) {
+        left[j] = vtemp[j][0]
+        right[j] = vtemp[3-j][j]
+      }
+      return [left, right]
+    }
+
+    var addifclose = function(v, error) {
+      var len = 0
+      for (var i=0; i < 3; i++) {
+        len += Curves.lineLength(v[i], v[i+1])
+      }
+      var chord = Curves.lineLength(v[0], v[3])
+      if ((len - chord) > error) {
+        var lr = bezsplit(v)
+        len = addifclose(lr[0], error) + addifclose(lr[1], error)
+      }
+      return len
+    }
+
+    return function(a,b,c,d, error) {
+      if (!error) error = 1
+      return addifclose([a,b,c,d], error)
+    }
+  })(),
+
+  quadraticLengthPointAngle : function(a,b,c,lt,error) {
+    var p1 = this.linePoint(a,b,2/3)
+    var p2 = this.linePoint(b,c,1/3)
+    return this.cubicLengthPointAngle(a,p1,p2,c, error)
+  },
+
+  cubicLengthPointAngle : function(a,b,c,d,lt,error) {
+    // this thing outright rapes the GC.
+    // how about not creating a billion arrays, hmm?
+    var len = this.cubicLength(a,b,c,d,error)
+    var point = a
+    var prevpoint = a
+    var lengths = []
+    var prevlensum = 0
+    var lensum = 0
+    var tl = lt*len
+    var segs = 20
+    var fac = 1/segs
+    for (var i=1; i<=segs; i++) { // FIXME get smarter
+      prevpoint = point
+      point = this.cubicPoint(a,b,c,d, fac*i)
+      prevlensum = lensum
+      lensum += this.lineLength(prevpoint, point)
+      if (lensum >= tl) {
+        if (lensum == prevlensum)
+          return {point: point, angle: this.lineAngle(a,b)}
+        var dl = lensum - tl
+        var dt = dl / (lensum-prevlensum)
+        return {point: this.linePoint(prevpoint, point, 1-dt),
+                angle: this.cubicAngle(a,b,c,d, fac*(i-dt)) }
+      }
+    }
+    return {point: d.slice(0), angle: this.lineAngle(c,d)}
+  }
+
+}
+
+
+
+/**
+  Color helper functions.
+  */
+Colors = {
+
+  /**
+    Converts an HSL color to its corresponding RGB color.
+
+    @param h Hue in degrees (0 .. 359)
+    @param s Saturation (0.0 .. 1.0)
+    @param l Lightness (0 .. 255)
+    @return The corresponding RGB color as [r,g,b]
+    @type Array
+    */
+  hsl2rgb : function(h,s,l) {
+    var r,g,b
+    if (s == 0) {
+      r=g=b=v
+    } else {
+      var q = (l < 0.5 ? l * (1+s) : l+s-(l*s))
+      var p = 2 * l - q
+      var hk = (h % 360) / 360
+      var tr = hk + 1/3
+      var tg = hk
+      var tb = hk - 1/3
+      if (tr < 0) tr++
+      if (tr > 1) tr--
+      if (tg < 0) tg++
+      if (tg > 1) tg--
+      if (tb < 0) tb++
+      if (tb > 1) tb--
+      if (tr < 1/6)
+        r = p + ((q-p)*6*tr)
+      else if (tr < 1/2)
+        r = q
+      else if (tr < 2/3)
+        r = p + ((q-p)*6*(2/3 - tr))
+      else
+        r = p
+
+      if (tg < 1/6)
+        g = p + ((q-p)*6*tg)
+      else if (tg < 1/2)
+        g = q
+      else if (tg < 2/3)
+        g = p + ((q-p)*6*(2/3 - tg))
+      else
+        g = p
+
+      if (tb < 1/6)
+        b = p + ((q-p)*6*tb)
+      else if (tb < 1/2)
+        b = q
+      else if (tb < 2/3)
+        b = p + ((q-p)*6*(2/3 - tb))
+      else
+        b = p
+    }
+
+    return [r,g,b]
+  },
+
+  /**
+    Converts an HSV color to its corresponding RGB color.
+
+    @param h Hue in degrees (0 .. 359)
+    @param s Saturation (0.0 .. 1.0)
+    @param v Value (0 .. 255)
+    @return The corresponding RGB color as [r,g,b]
+    @type Array
+    */
+  hsv2rgb : function(h,s,v) {
+    var r,g,b
+    if (s == 0) {
+      r=g=b=v
+    } else {
+      h = (h % 360)/60.0
+      var i = Math.floor(h)
+      var f = h-i
+      var p = v * (1-s)
+      var q = v * (1-s*f)
+      var t = v * (1-s*(1-f))
+      switch (i) {
+        case 0:
+          r = v
+          g = t
+          b = p
+          break
+        case 1:
+          r = q
+          g = v
+          b = p
+          break
+        case 2:
+          r = p
+          g = v
+          b = t
+          break
+        case 3:
+          r = p
+          g = q
+          b = v
+          break
+        case 4:
+          r = t
+          g = p
+          b = v
+          break
+        case 5:
+          r = v
+          g = p
+          b = q
+          break
+      }
+    }
+    return [r,g,b]
+  },
+
+  /**
+    Parses a color style object into one that can be used with the given
+    canvas context.
+
+    Accepted formats:
+      'white'
+      '#fff'
+      '#ffffff'
+      'rgba(255,255,255, 1.0)'
+      [255, 255, 255]
+      [255, 255, 255, 1.0]
+      new Gradient(...)
+      new Pattern(...)
+
+    @param style The color style to parse
+    @param ctx Canvas 2D context on which the style is to be used
+    @return A parsed style, ready to be used as ctx.fillStyle / strokeStyle
+    */
+  parseColorStyle : function(style, ctx) {
+    if (typeof style == 'string') {
+      return style
+    } else if (style.compiled) {
+      return style.compiled
+    } else if (style.isPattern) {
+      return style.compile(ctx)
+    } else if (style.length == 3) {
+      return 'rgba('+style.map(Math.round).join(",")+', 1)'
+    } else if (style.length == 4) {
+      return 'rgba('+
+              Math.round(style[0])+','+
+              Math.round(style[1])+','+
+              Math.round(style[2])+','+
+              style[3]+
+             ')'
+    } else { // wtf
+      throw( "Bad style: " + style )
+    }
+  }
+}
+
+
+
+/**
+  Navigating around differing implementations of canvas features.
+
+  Current issues:
+
+  isPointInPath(x,y):
+
+    Opera supports isPointInPath.
+
+    Safari doesn't have isPointInPath. So you need to keep track of the CTM and
+    do your own in-fill-checking. Which is done for circles and rectangles
+    in Circle#isPointInPath and Rectangle#isPointInPath.
+    Paths use an inaccurate bounding box test, implemented in
+    Path#isPointInPath.
+
+    Firefox 3 has isPointInPath. But it uses user-space coordinates.
+    Which can be easily navigated around because it has setTransform.
+
+    Firefox 2 has isPointInPath. But it uses user-space coordinates.
+    And there's no setTransform, so you need to keep track of the CTM and
+    multiply the mouse vector with the CTM's inverse.
+
+  Drawing text:
+
+    Rhino has ctx.drawString(x,y, text)
+
+    Firefox has ctx.mozDrawText(text)
+
+    The WhatWG spec, Safari and Opera have nothing.
+
+*/
+CanvasSupport = {
+  DEVICE_SPACE : 0, // Opera
+  USER_SPACE : 1,   // Fx2, Fx3
+  isPointInPathMode : null,
+  supportsIsPointInPath : null,
+  supportsCSSTransform : null,
+  supportsCanvas : null,
+
+  isCanvasSupported : function() {
+    if (this.supportsCanvas == null) {
+      var e = {};
+      try { e = E('canvas'); } catch(x) {}
+      this.supportsCanvas = (e.getContext != null);
+    }
+    return this.supportsCanvas;
+  },
+
+  isCSSTransformSupported : function() {
+    if (this.supportsCSSTransform == null) {
+      var e = E('div')
+      var dbs = e.style
+      var s = (dbs.webkitTransform != null || dbs.MozTransform != null)
+      this.supportsCSSTransform = (s != null)
+    }
+    return this.supportsCSSTransform
+  },
+
+  getTestContext : function() {
+    if (!this.testContext) {
+      var c = E.canvas(1,1)
+      this.testContext = c.getContext('2d')
+    }
+    return this.testContext
+  },
+
+  getSupportsAudioTag : function() {
+    var e = E('audio')
+    return !!e.play
+  },
+
+  getSupportsSoundManager : function() {
+    return (window.soundManager && soundManager.enabled)
+  },
+
+  soundId : 0,
+
+  getSoundObject : function() {
+    var e = null
+//     if (this.getSupportsAudioTag()) {
+//       e = this.getAudioTagSoundObject()
+//     } else
+    if (this.getSupportsSoundManager()) {
+      e = this.getSoundManagerSoundObject()
+    }
+    return e
+  },
+
+  getAudioTagSoundObject : function() {
+    var sid = 'sound-' + this.soundId++
+    var e = E('audio', {id: sid})
+    e.load = function(src) {
+      this.src = src
+    }
+    e.addEventListener('canplaythrough', function() {
+      if (this.onready) this.onready()
+    }, false)
+    e.setVolume = function(v){ this.volume = v }
+    e.setPan = function(v){ this.pan = v }
+    return e
+  },
+
+  getSoundManagerSoundObject : function() {
+    var sid = 'sound-' + this.soundId++
+    var e = {
+      volume: 100,
+      pan: 0,
+      sid : sid,
+      load : function(src) {
+        return soundManager.load(this.sid, {
+          url: src,
+          autoPlay: false,
+          volume: this.volume,
+          pan: this.pan
+        })
+      },
+      _onload : function() {
+        if (this.onload) this.onload()
+        if (this.onready) this.onready()
+      },
+      _onerror : function() {
+        if (this.onerror) this.onerror()
+      },
+      _onfinish : function() {
+        if (this.onfinish) this.onfinish()
+      },
+      play : function() {
+        return soundManager.play(this.sid)
+      },
+      stop : function() {
+        return soundManager.stop(this.sid)
+      },
+      pause : function() {
+        return soundManager.togglePause(this.sid)
+      },
+      setVolume : function(v) {
+        this.volume = v*100
+        return soundManager.setVolume(this.sid, v*100)
+      },
+      setPan : function(v) {
+        this.pan = v*100
+        return soundManager.setPan(this.sid, v*100)
+      }
+    }
+    soundManager.createSound(sid, 'null.mp3')
+    e.sound = soundManager.getSoundById(sid)
+    e.sound.options.onfinish = e._onfinish.bind(e)
+    e.sound.options.onload = e._onload.bind(e)
+    e.sound.options.onerror = e._onerror.bind(e)
+    return e
+  },
+
+  /**
+    Canvas context augment module that adds setters.
+    */
+  ContextSetterAugment : {
+    setFillStyle : function(fs) { this.fillStyle = fs },
+    setStrokeStyle : function(ss) { this.strokeStyle = ss },
+    setGlobalAlpha : function(ga) { this.globalAlpha = ga },
+    setLineWidth : function(lw) { this.lineWidth = lw },
+    setLineCap : function(lw) { this.lineCap = lw },
+    setLineJoin : function(lw) { this.lineJoin = lw },
+    setMiterLimit : function(lw) { this.miterLimit = lw },
+    setGlobalCompositeOperation : function(lw) {
+      this.globalCompositeOperation = lw
+    },
+    setShadowColor : function(x) { this.shadowColor = x },
+    setShadowBlur : function(x) { this.shadowBlur = x },
+    setShadowOffsetX : function(x) { this.shadowOffsetX = x },
+    setShadowOffsetY : function(x) { this.shadowOffsetY = x },
+    setMozTextStyle : function(x) { this.mozTextStyle = x },
+    setFont : function(x) { this.font = x },
+    setTextAlign : function(x) { this.textAlign = x },
+    setTextBaseline : function(x) { this.textBaseline = x }
+  },
+
+  ContextJSImplAugment : {
+    identity : function() {
+      CanvasSupport.setTransform(this, [1,0,0,1,0,0])
+    }
+  },
+
+  /**
+    Augments a canvas context with setters.
+    */
+  augment : function(ctx) {
+    Object.conditionalExtend(ctx, this.ContextSetterAugment)
+    Object.conditionalExtend(ctx, this.ContextJSImplAugment)
+    return ctx
+  },
+
+  /**
+    Gets the augmented context for canvas.
+    */
+  getContext : function(canvas, type) {
+    var ctx = canvas.getContext(type || '2d')
+    this.augment(ctx)
+    return ctx
+  },
+
+
+  /**
+    Multiplies two 3x2 affine 2D column-major transformation matrices with
+    each other and stores the result in the first matrix.
+
+    Returns the multiplied matrix m1.
+    */
+  tMatrixMultiply : function(m1, m2) {
+    var m11 = m1[0]*m2[0] + m1[2]*m2[1]
+    var m12 = m1[1]*m2[0] + m1[3]*m2[1]
+
+    var m21 = m1[0]*m2[2] + m1[2]*m2[3]
+    var m22 = m1[1]*m2[2] + m1[3]*m2[3]
+
+    var dx = m1[0]*m2[4] + m1[2]*m2[5] + m1[4]
+    var dy = m1[1]*m2[4] + m1[3]*m2[5] + m1[5]
+
+    m1[0] = m11
+    m1[1] = m12
+    m1[2] = m21
+    m1[3] = m22
+    m1[4] = dx
+    m1[5] = dy
+
+    return m1
+  },
+
+  /**
+    Multiplies the vector [x, y, 1] with the 3x2 transformation matrix m.
+    */
+  tMatrixMultiplyPoint : function(m, x, y) {
+    return [
+      x*m[0] + y*m[2] + m[4],
+      x*m[1] + y*m[3] + m[5]
+    ]
+  },
+
+  /**
+    Inverts a 3x2 affine 2D column-major transformation matrix.
+
+    Returns an inverted copy of the matrix.
+    */
+  tInvertMatrix : function(m) {
+    var d = 1 / (m[0]*m[3]-m[1]*m[2])
+    return [
+      m[3]*d, -m[1]*d,
+      -m[2]*d, m[0]*d,
+      d*(m[2]*m[5]-m[3]*m[4]), d*(m[1]*m[4]-m[0]*m[5])
+    ]
+  },
+
+  /**
+    Applies a transformation matrix m on the canvas context ctx.
+    */
+  transform : function(ctx, m) {
+    if (ctx.transform)
+      return ctx.transform.apply(ctx, m)
+    ctx.translate(m[4], m[5])
+    // scale
+    if (Math.abs(m[1]) < 1e-6 && Math.abs(m[2]) < 1e-6) {
+      ctx.scale(m[0], m[3])
+      return
+    }
+    var res = this.svdTransform({xx:m[0], xy:m[2], yx:m[1], yy:m[3], dx:m[4], dy:m[5]})
+    ctx.rotate(res.angle2)
+    ctx.scale(res.sx, res.sy)
+    ctx.rotate(res.angle1)
+    return
+  },
+
+  // broken svd...
+  brokenSvd : function(m) {
+    var mt = [m[0], m[2], m[1], m[3], 0,0]
+    var mtm = [
+      mt[0]*m[0]+mt[2]*m[1],
+      mt[1]*m[0]+mt[3]*m[1],
+      mt[0]*m[2]+mt[2]*m[3],
+      mt[1]*m[2]+mt[3]*m[3],
+      0,0
+    ]
+    // (mtm[0]-x) * (mtm[3]-x) - (mtm[1]*mtm[2]) = 0
+    // x*x - (mtm[0]+mtm[3])*x - (mtm[1]*mtm[2])+(mtm[0]*mtm[3]) = 0
+    var a = 1
+    var b = -(mtm[0]+mtm[3])
+    var c = -(mtm[1]*mtm[2])+(mtm[0]*mtm[3])
+    var d = Math.sqrt(b*b - 4*a*c)
+    var c1 = (-b + d) / (2*a)
+    var c2 = (-b - d) / (2*a)
+    if (c1 < c2)
+      var tmp = c1, c1 = c2, c2 = tmp
+    var s1 = Math.sqrt(c1)
+    var s2 = Math.sqrt(c2)
+    var i_s = [1/s1, 0, 0, 1/s2, 0,0]
+    // (mtm[0]-c1)*x1 + mtm[2]*x2 = 0
+    // mtm[1]*x1 + (mtm[3]-c1)*x2 = 0
+    // x2 = -(mtm[0]-c1)*x1 / mtm[2]
+    var e = ((mtm[0]-c1)/mtm[2])
+    var l = Math.sqrt(1 + e*e)
+    var v00 = 1 / l
+    var v10 = e / l
+    var v11 = v00
+    var v01 = -v10
+    var v = [v00, v01, v10, v11, 0,0]
+    var u = m.slice(0)
+    this.tMatrixMultiply(u,v)
+    this.tMatrixMultiply(u,i_s)
+    return [u, [s1,0,0,s2,0,0], [v00, v10, v01, v11, 0, 0]]
+  },
+
+
+  svdTransform : (function(){
+    //   Copyright (c) 2004-2005, The Dojo Foundation
+    //   All Rights Reserved
+    var m = {}
+    m.Matrix2D = function(arg){
+      // summary: a 2D matrix object
+      // description: Normalizes a 2D matrix-like object. If arrays is passed,
+      //    all objects of the array are normalized and multiplied sequentially.
+      // arg: Object
+      //    a 2D matrix-like object, a number, or an array of such objects
+      if(arg){
+        if(typeof arg == "number"){
+          this.xx = this.yy = arg;
+        }else if(arg instanceof Array){
+          if(arg.length > 0){
+            var matrix = m.normalize(arg[0]);
+            // combine matrices
+            for(var i = 1; i < arg.length; ++i){
+              var l = matrix, r = m.normalize(arg[i]);
+              matrix = new m.Matrix2D();
+              matrix.xx = l.xx * r.xx + l.xy * r.yx;
+              matrix.xy = l.xx * r.xy + l.xy * r.yy;
+              matrix.yx = l.yx * r.xx + l.yy * r.yx;
+              matrix.yy = l.yx * r.xy + l.yy * r.yy;
+              matrix.dx = l.xx * r.dx + l.xy * r.dy + l.dx;
+              matrix.dy = l.yx * r.dx + l.yy * r.dy + l.dy;
+            }
+            Object.extend(this, matrix);
+          }
+        }else{
+          Object.extend(this, arg);
+        }
+      }
+    }
+    // ensure matrix 2D conformance
+    m.normalize = function(matrix){
+        // summary: converts an object to a matrix, if necessary
+        // description: Converts any 2D matrix-like object or an array of
+        //    such objects to a valid dojox.gfx.matrix.Matrix2D object.
+        // matrix: Object: an object, which is converted to a matrix, if necessary
+        return (matrix instanceof m.Matrix2D) ? matrix : new m.Matrix2D(matrix); // dojox.gfx.matrix.Matrix2D
+    }
+    m.multiply = function(matrix){
+      // summary: combines matrices by multiplying them sequentially in the given order
+      // matrix: dojox.gfx.matrix.Matrix2D...: a 2D matrix-like object,
+      //    all subsequent arguments are matrix-like objects too
+      var M = m.normalize(matrix);
+      // combine matrices
+      for(var i = 1; i < arguments.length; ++i){
+        var l = M, r = m.normalize(arguments[i]);
+        M = new m.Matrix2D();
+        M.xx = l.xx * r.xx + l.xy * r.yx;
+        M.xy = l.xx * r.xy + l.xy * r.yy;
+        M.yx = l.yx * r.xx + l.yy * r.yx;
+        M.yy = l.yx * r.xy + l.yy * r.yy;
+        M.dx = l.xx * r.dx + l.xy * r.dy + l.dx;
+        M.dy = l.yx * r.dx + l.yy * r.dy + l.dy;
+      }
+      return M; // dojox.gfx.matrix.Matrix2D
+    }
+    m.invert = function(matrix) {
+      var M = m.normalize(matrix),
+        D = M.xx * M.yy - M.xy * M.yx,
+        M = new m.Matrix2D({
+          xx: M.yy/D, xy: -M.xy/D,
+          yx: -M.yx/D, yy: M.xx/D,
+          dx: (M.xy * M.dy - M.yy * M.dx) / D,
+          dy: (M.yx * M.dx - M.xx * M.dy) / D
+        });
+      return M; // dojox.gfx.matrix.Matrix2D
+    }
+    // the default (identity) matrix, which is used to fill in missing values
+    Object.extend(m.Matrix2D, {xx: 1, xy: 0, yx: 0, yy: 1, dx: 0, dy: 0});
+
+    var eq = function(/* Number */ a, /* Number */ b){
+      // summary: compare two FP numbers for equality
+      return Math.abs(a - b) <= 1e-6 * (Math.abs(a) + Math.abs(b)); // Boolean
+    };
+
+    var calcFromValues = function(/* Number */ s1, /* Number */ s2){
+      // summary: uses two close FP values to approximate the result
+      if(!isFinite(s1)){
+        return s2;  // Number
+      }else if(!isFinite(s2)){
+        return s1;  // Number
+      }
+      return (s1 + s2) / 2; // Number
+    };
+
+    var transpose = function(/* dojox.gfx.matrix.Matrix2D */ matrix){
+      // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
+      var M = new m.Matrix2D(matrix);
+      return Object.extend(M, {dx: 0, dy: 0, xy: M.yx, yx: M.xy}); // dojox.gfx.matrix.Matrix2D
+    };
+
+    var scaleSign = function(/* dojox.gfx.matrix.Matrix2D */ matrix){
+      return (matrix.xx * matrix.yy < 0 || matrix.xy * matrix.yx > 0) ? -1 : 1; // Number
+    };
+
+    var eigenvalueDecomposition = function(/* dojox.gfx.matrix.Matrix2D */ matrix){
+      // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
+      var M = m.normalize(matrix),
+        b = -M.xx - M.yy,
+        c = M.xx * M.yy - M.xy * M.yx,
+        d = Math.sqrt(b * b - 4 * c),
+        l1 = -(b + (b < 0 ? -d : d)) / 2,
+        l2 = c / l1,
+        vx1 = M.xy / (l1 - M.xx), vy1 = 1,
+        vx2 = M.xy / (l2 - M.xx), vy2 = 1;
+      if(eq(l1, l2)){
+        vx1 = 1, vy1 = 0, vx2 = 0, vy2 = 1;
+      }
+      if(!isFinite(vx1)){
+        vx1 = 1, vy1 = (l1 - M.xx) / M.xy;
+        if(!isFinite(vy1)){
+          vx1 = (l1 - M.yy) / M.yx, vy1 = 1;
+          if(!isFinite(vx1)){
+            vx1 = 1, vy1 = M.yx / (l1 - M.yy);
+          }
+        }
+      }
+      if(!isFinite(vx2)){
+        vx2 = 1, vy2 = (l2 - M.xx) / M.xy;
+        if(!isFinite(vy2)){
+          vx2 = (l2 - M.yy) / M.yx, vy2 = 1;
+          if(!isFinite(vx2)){
+            vx2 = 1, vy2 = M.yx / (l2 - M.yy);
+          }
+        }
+      }
+      var d1 = Math.sqrt(vx1 * vx1 + vy1 * vy1),
+        d2 = Math.sqrt(vx2 * vx2 + vy2 * vy2);
+      if(isNaN(vx1 /= d1)){ vx1 = 0; }
+      if(isNaN(vy1 /= d1)){ vy1 = 0; }
+      if(isNaN(vx2 /= d2)){ vx2 = 0; }
+      if(isNaN(vy2 /= d2)){ vy2 = 0; }
+      return {  // Object
+        value1: l1,
+        value2: l2,
+        vector1: {x: vx1, y: vy1},
+        vector2: {x: vx2, y: vy2}
+      };
+    };
+
+    var decomposeSR = function(/* dojox.gfx.matrix.Matrix2D */ M, /* Object */ result){
+      // summary: decomposes a matrix into [scale, rotate]; no checks are done.
+      var sign = scaleSign(M),
+        a = result.angle1 = (Math.atan2(M.yx, M.yy) + Math.atan2(-sign * M.xy, sign * M.xx)) / 2,
+        cos = Math.cos(a), sin = Math.sin(a);
+      result.sx = calcFromValues(M.xx / cos, -M.xy / sin);
+      result.sy = calcFromValues(M.yy / cos, M.yx / sin);
+      return result;  // Object
+    };
+
+    var decomposeRS = function(/* dojox.gfx.matrix.Matrix2D */ M, /* Object */ result){
+      // summary: decomposes a matrix into [rotate, scale]; no checks are done
+      var sign = scaleSign(M),
+        a = result.angle2 = (Math.atan2(sign * M.yx, sign * M.xx) + Math.atan2(-M.xy, M.yy)) / 2,
+        cos = Math.cos(a), sin = Math.sin(a);
+      result.sx = calcFromValues(M.xx / cos, M.yx / sin);
+      result.sy = calcFromValues(M.yy / cos, -M.xy / sin);
+      return result;  // Object
+    };
+
+    return function(matrix){
+      // summary: decompose a 2D matrix into translation, scaling, and rotation components
+      // description: this function decompose a matrix into four logical components:
+      //  translation, rotation, scaling, and one more rotation using SVD.
+      //  The components should be applied in following order:
+      //  | [translate, rotate(angle2), scale, rotate(angle1)]
+      // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
+      var M = m.normalize(matrix),
+        result = {dx: M.dx, dy: M.dy, sx: 1, sy: 1, angle1: 0, angle2: 0};
+      // detect case: [scale]
+      if(eq(M.xy, 0) && eq(M.yx, 0)){
+        return Object.extend(result, {sx: M.xx, sy: M.yy});  // Object
+      }
+      // detect case: [scale, rotate]
+      if(eq(M.xx * M.yx, -M.xy * M.yy)){
+        return decomposeSR(M, result);  // Object
+      }
+      // detect case: [rotate, scale]
+      if(eq(M.xx * M.xy, -M.yx * M.yy)){
+        return decomposeRS(M, result);  // Object
+      }
+      // do SVD
+      var MT = transpose(M),
+        u  = eigenvalueDecomposition([M, MT]),
+        v  = eigenvalueDecomposition([MT, M]),
+        U  = new m.Matrix2D({xx: u.vector1.x, xy: u.vector2.x, yx: u.vector1.y, yy: u.vector2.y}),
+        VT = new m.Matrix2D({xx: v.vector1.x, xy: v.vector1.y, yx: v.vector2.x, yy: v.vector2.y}),
+        S = new m.Matrix2D([m.invert(U), M, m.invert(VT)]);
+      decomposeSR(VT, result);
+      S.xx *= result.sx;
+      S.yy *= result.sy;
+      decomposeRS(U, result);
+      S.xx *= result.sx;
+      S.yy *= result.sy;
+      return Object.extend(result, {sx: S.xx, sy: S.yy});  // Object
+    };
+  })(),
+
+
+  /**
+    Sets the canvas context ctx's transformation matrix to m, with ctm being
+    the current transformation matrix.
+    */
+  setTransform : function(ctx, m, ctm) {
+    if (ctx.setTransform)
+      return ctx.setTransform.apply(ctx, m)
+    this.transform(ctx, this.tInvertMatrix(ctm))
+    this.transform(ctx, m)
+  },
+
+  /**
+    Skews the canvas context by angle on the x-axis.
+    */
+  skewX : function(ctx, angle) {
+    return this.transform(ctx, this.tSkewXMatrix(angle))
+  },
+
+  /**
+    Skews the canvas context by angle on the y-axis.
+    */
+  skewY : function(ctx, angle) {
+    return this.transform(ctx, this.tSkewYMatrix(angle))
+  },
+
+  /**
+    Rotates a transformation matrix by angle.
+    */
+  tRotate : function(m1, angle) {
+    // return this.tMatrixMultiply(matrix, this.tRotationMatrix(angle))
+    var c = Math.cos(angle)
+    var s = Math.sin(angle)
+    var m11 = m1[0]*c + m1[2]*s
+    var m12 = m1[1]*c + m1[3]*s
+    var m21 = m1[0]*-s + m1[2]*c
+    var m22 = m1[1]*-s + m1[3]*c
+    m1[0] = m11
+    m1[1] = m12
+    m1[2] = m21
+    m1[3] = m22
+    return m1
+  },
+
+  /**
+    Translates a transformation matrix by x and y.
+    */
+  tTranslate : function(m1, x, y) {
+    // return this.tMatrixMultiply(matrix, this.tTranslationMatrix(x,y))
+    m1[4] += m1[0]*x + m1[2]*y
+    m1[5] += m1[1]*x + m1[3]*y
+    return m1
+  },
+
+  /**
+    Scales a transformation matrix by sx and sy.
+    */
+  tScale : function(m1, sx, sy) {
+    // return this.tMatrixMultiply(matrix, this.tScalingMatrix(sx,sy))
+    m1[0] *= sx
+    m1[1] *= sx
+    m1[2] *= sy
+    m1[3] *= sy
+    return m1
+  },
+
+  /**
+    Skews a transformation matrix by angle on the x-axis.
+    */
+  tSkewX : function(m1, angle) {
+    return this.tMatrixMultiply(m1, this.tSkewXMatrix(angle))
+  },
+
+  /**
+    Skews a transformation matrix by angle on the y-axis.
+    */
+  tSkewY : function(m1, angle) {
+    return this.tMatrixMultiply(m1, this.tSkewYMatrix(angle))
+  },
+
+  /**
+    Returns a 3x2 2D column-major y-skew matrix for the angle.
+    */
+  tSkewXMatrix : function(angle) {
+    return [ 1, 0, Math.tan(angle), 1, 0, 0 ]
+  },
+
+  /**
+    Returns a 3x2 2D column-major y-skew matrix for the angle.
+    */
+  tSkewYMatrix : function(angle) {
+    return [ 1, Math.tan(angle), 0, 1, 0, 0 ]
+  },
+
+  /**
+    Returns a 3x2 2D column-major rotation matrix for the angle.
+    */
+  tRotationMatrix : function(angle) {
+    var c = Math.cos(angle)
+    var s = Math.sin(angle)
+    return [ c, s, -s, c, 0, 0 ]
+  },
+
+  /**
+    Returns a 3x2 2D column-major translation matrix for x and y.
+    */
+  tTranslationMatrix : function(x, y) {
+    return [ 1, 0, 0, 1, x, y ]
+  },
+
+  /**
+    Returns a 3x2 2D column-major scaling matrix for sx and sy.
+    */
+  tScalingMatrix : function(sx, sy) {
+    return [ sx, 0, 0, sy, 0, 0 ]
+  },
+
+  /**
+    Returns the name of the text backend to use.
+
+    Possible values are:
+      * 'MozText' for Firefox
+      * 'DrawString' for Rhino
+      * 'NONE' no text drawing
+
+    @return The text backend name
+    @type String
+    */
+  getTextBackend : function() {
+    if (this.textBackend == null)
+      this.textBackend = this.detectTextBackend()
+    return this.textBackend
+  },
+
+  /**
+    Detects the name of the text backend to use.
+
+    Possible values are:
+      * 'MozText' for Firefox
+      * 'DrawString' for Rhino
+      * 'NONE' no text drawing
+
+    @return The text backend name
+    @type String
+    */
+  detectTextBackend : function() {
+    var ctx = this.getTestContext()
+    if (ctx.fillText) {
+      return 'HTML5'
+    } else if (ctx.mozDrawText) {
+      return 'MozText'
+    } else if (ctx.drawString) {
+      return 'DrawString'
+    }
+    return 'NONE'
+  },
+
+  getSupportsPutImageData : function() {
+    if (this.supportsPutImageData == null) {
+      var ctx = this.getTestContext()
+      var support = ctx.putImageData
+      if (support) {
+        try {
+          var idata = ctx.getImageData(0,0,1,1)
+          idata[0] = 255
+          idata[1] = 0
+          idata[2] = 255
+          idata[3] = 255
+          ctx.putImageData({width: 1, height: 1, data: idata}, 0, 0)
+          var idata = ctx.getImageData(0,0,1,1)
+          support = [255, 0, 255, 255].equals(idata.data)
+        } catch(e) {
+          support = false
+        }
+      }
+      this.supportsPutImageData = support
+    }
+    return support
+  },
+
+  /**
+    Returns true if the browser can be coaxed to work with
+    {@link CanvasSupport.isPointInPath}.
+
+    @return Whether the browser supports isPointInPath or not
+    @type boolean
+    */
+  getSupportsIsPointInPath : function() {
+    if (this.supportsIsPointInPath == null)
+      this.supportsIsPointInPath = !!this.getTestContext().isPointInPath
+    return this.supportsIsPointInPath
+  },
+
+  /**
+    Returns the coordinate system in which the isPointInPath of the
+    browser operates. Possible coordinate systems are
+    CanvasSupport.DEVICE_SPACE and CanvasSupport.USER_SPACE.
+
+    @return The coordinate system for the browser's isPointInPath
+    */
+  getIsPointInPathMode : function() {
+    if (this.isPointInPathMode == null)
+      this.isPointInPathMode = this.detectIsPointInPathMode()
+    return this.isPointInPathMode
+  },
+
+  /**
+    Detects the coordinate system in which the isPointInPath of the
+    browser operates. Possible coordinate systems are
+    CanvasSupport.DEVICE_SPACE and CanvasSupport.USER_SPACE.
+
+    @return The coordinate system for the browser's isPointInPath
+    @private
+    */
+  detectIsPointInPathMode : function() {
+    var ctx = this.getTestContext()
+    var rv
+    if (!ctx.isPointInPath)
+      return this.USER_SPACE
+    ctx.save()
+    ctx.translate(1,0)
+    ctx.beginPath()
+    ctx.rect(0,0,1,1)
+    if (ctx.isPointInPath(0.3,0.3)) {
+      rv = this.USER_SPACE
+    } else {
+      rv = this.DEVICE_SPACE
+    }
+    ctx.restore()
+    return rv
+  },
+
+  /**
+    Returns true if the device-space point (x,y) is inside the fill of
+    ctx's current path.
+
+    @param ctx Canvas 2D context to query
+    @param x The distance in pixels from the left side of the canvas element
+    @param y The distance in pixels from the top side of the canvas element
+    @param matrix The current transformation matrix. Needed if the browser has
+                  no isPointInPath or the browser's isPointInPath works in
+                  user-space coordinates and the browser doesn't support
+                  setTransform.
+    @param callbackObj If the browser doesn't support isPointInPath,
+                       callbackObj.isPointInPath will be called with the
+                       x,y-coordinates transformed to user-space.
+    @param
+    @return Whether (x,y) is inside ctx's current path or not
+    @type boolean
+    */
+  isPointInPath : function(ctx, x, y, matrix, callbackObj) {
+    var rv
+    if (!ctx.isPointInPath) {
+      if (callbackObj && callbackObj.isPointInPath) {
+        var xy = this.tMatrixMultiplyPoint(this.tInvertMatrix(matrix), x, y)
+        return callbackObj.isPointInPath(xy[0], xy[1])
+      } else {
+        return false
+      }
+    } else {
+      if (this.getIsPointInPathMode() == this.USER_SPACE) {
+        if (!ctx.setTransform) {
+          var xy = this.tMatrixMultiplyPoint(this.tInvertMatrix(matrix), x, y)
+          rv = ctx.isPointInPath(xy[0], xy[1])
+        } else {
+          ctx.save()
+          ctx.setTransform(1,0,0,1,0,0)
+          rv = ctx.isPointInPath(x,y)
+          ctx.restore()
+        }
+      } else {
+        rv = ctx.isPointInPath(x,y)
+      }
+      return rv
+    }
+  }
+}
+
+
+RecordingContext = Klass({
+  objectId : 0,
+  commands : [],
+  isMockObject : true,
+
+  initialize : function(commands) {
+    this.commands = commands || []
+    Object.conditionalExtend(this, this.getMockContext())
+  },
+
+  getMockContext : function() {
+    if (!RecordingContext.MockContext) {
+      var c = E.canvas(1,1)
+      var ctx = CanvasSupport.getContext(c, '2d')
+      var obj = {}
+      for (var i in ctx) {
+        if (typeof(ctx[i]) == 'function')
+          obj[i] = this.createRecordingFunction(i)
+        else
+          obj[i] = ctx[i]
+      }
+      obj.isPointInPath = null
+      obj.transform = null
+      obj.setTransform = null
+      RecordingContext.MockContext = obj
+    }
+    return RecordingContext.MockContext
+  },
+
+  createRecordingFunction : function(name){
+    if (name.search(/^set[A-Z]/) != -1 && name != 'setTransform') {
+      var varName = name.charAt(3).toLowerCase() + name.slice(4)
+      return function(){
+        this[varName] = arguments[0]
+        this.commands.push([name, $A(arguments)])
+      }
+    } else {
+      return function(){
+        this.commands.push([name, $A(arguments)])
+      }
+    }
+  },
+
+  clear : function(){
+    this.commands = []
+  },
+
+  getRecording : function() {
+    return this.commands
+  },
+
+  serialize : function(width, height) {
+    return '(' + {
+      width: width, height: height,
+      commands: this.getRecording()
+    }.toSource() + ')'
+  },
+
+  play : function(ctx) {
+    RecordingContext.play(ctx, this.getRecording())
+  },
+
+  createLinearGradient : function() {
+    var id = this.objectId++
+    this.commands.push([id, '=', 'createLinearGradient', $A(arguments)])
+    return new MockGradient(this, id)
+  },
+
+  createRadialGradient : function() {
+    var id = this.objectId++
+    this.commands.push([id, '=', 'createRadialGradient', $A(arguments)])
+    return new this.MockGradient(this, id)
+  },
+
+  createPattern : function() {
+    var id = this.objectId++
+    this.commands.push([id, '=', 'createPattern', $A(arguments)])
+    return new this.MockGradient(this, id)
+  },
+
+  MockGradient : Klass({
+    isMockObject : true,
+
+    initialize : function(recorder, id) {
+      this.recorder = recorder
+      this.id = id
+    },
+
+    addColorStop : function() {
+      this.recorder.commands.push([this.id, 'addColorStop', $A(arguments)])
+    },
+
+    toSource : function() {
+      return {id : this.id, isMockObject : true}.toSource()
+    }
+  })
+})
+RecordingContext.play = function(ctx, commands) {
+  var dictionary = []
+  for (var i=0; i<commands.length; i++) {
+    var cmd = commands[i]
+    if (cmd.length == 2) {
+      var args = cmd[1]
+      if (args[0] && args[0].isMockObject) {
+        ctx[cmd[0]](dictionary[args[0].id])
+      } else {
+        ctx[cmd[0]].apply(ctx, cmd[1])
+      }
+    } else if (cmd.length == 3) {
+      var obj = dictionary[cmd[0]]
+      obj[cmd[1]].apply(obj, cmd[2])
+    } else if (cmd.length == 4) {
+      dictionary[cmd[0]] = ctx[cmd[2]].apply(ctx, cmd[3])
+    } else {
+      throw "Malformed command: "+cmd.toString()
+    }
+  }
+}
+
+
+
+
+Transformable = Klass({
+  needMatrixUpdate : true,
+
+  /**
+    Transforms the context state according to this node's attributes.
+
+    @param ctx Canvas 2D context
+    */
+  transform : function(ctx) {
+    var atm = this.absoluteMatrix
+    var xy = this.x || this.y
+    var rot = this.rotation
+    var sca = this.scale != null
+    var skX = this.skewX
+    var skY = this.skewY
+    var tm = this.matrix
+    var tl = this.transformList
+
+    // update the node's transformation matrix
+    if (this.needMatrixUpdate || !this.currentMatrix) {
+      if (!this.currentMatrix) this.currentMatrix = [1,0,0,1,0,0]
+      if (this.parent)
+        this.__copyMatrix(this.parent.currentMatrix)
+      else
+        this.__identityMatrix()
+      if (atm) this.__setMatrixMatrix(this.absoluteMatrix)
+      if (xy) this.__translateMatrix(this.x, this.y)
+      if (rot) this.__rotateMatrix(this.rotation)
+      if (skX) this.__skewXMatrix(this.skewX)
+      if (skY) this.__skewYMatrix(this.skewY)
+      if (sca) this.__scaleMatrix(this.scale)
+      if (tm) this.__matrixMatrix(this.matrix)
+      if (tl) {
+        for (var i=0; i<this.transformList.length; i++) {
+          var tl = this.transformList[i]
+          this['__'+tl[0]+'Matrix'](tl[1])
+        }
+      }
+      this.needMatrixUpdate = false
+    }
+
+    if (!ctx) return
+
+    // transform matrix modifiers
+    this.__setMatrix(ctx, this.currentMatrix)
+  },
+
+  distanceTo : function(node) {
+    return Curves.lineLength([this.x, this.y], [node.x, node.y])
+  },
+
+  angleTo : function(node) {
+    return Curves.lineAngle([this.x, this.y], [node.x, node.y])
+  },
+
+
+
+  __setMatrixMatrix : function(matrix) {
+    if (!this.previousMatrix) this.previousMatrix = []
+    var p = this.previousMatrix
+    var c = this.currentMatrix
+    p[0] = c[0]
+    p[1] = c[1]
+    p[2] = c[2]
+    p[3] = c[3]
+    p[4] = c[4]
+    p[5] = c[5]
+    p = this.currentMatrix
+    c = matrix
+    p[0] = c[0]
+    p[1] = c[1]
+    p[2] = c[2]
+    p[3] = c[3]
+    p[4] = c[4]
+    p[5] = c[5]
+  },
+
+  __copyMatrix : function(matrix) {
+    var p = this.currentMatrix
+    var c = matrix
+    p[0] = c[0]
+    p[1] = c[1]
+    p[2] = c[2]
+    p[3] = c[3]
+    p[4] = c[4]
+    p[5] = c[5]
+  },
+
+  __identityMatrix : function() {
+    var p = this.currentMatrix
+    p[0] = 1
+    p[1] = 0
+    p[2] = 0
+    p[3] = 1
+    p[4] = 0
+    p[5] = 0
+  },
+
+  __translateMatrix : function(x, y) {
+    if (x.length) {
+      CanvasSupport.tTranslate( this.currentMatrix, x[0], x[1] )
+    } else {
+      CanvasSupport.tTranslate( this.currentMatrix, x, y )
+    }
+  },
+
+  __rotateMatrix : function(rotation) {
+    if (rotation.length) {
+      if (rotation[0] % Math.PI*2 == 0) return
+      if (rotation[1] || rotation[2]) {
+        CanvasSupport.tTranslate( this.currentMatrix,
+                                  rotation[1], rotation[2] )
+        CanvasSupport.tRotate( this.currentMatrix, rotation[0] )
+        CanvasSupport.tTranslate( this.currentMatrix,
+                                  -rotation[1], -rotation[2] )
+      } else {
+        CanvasSupport.tRotate( this.currentMatrix, rotation[0] )
+      }
+    } else {
+      if (rotation % Math.PI*2 == 0) return
+      CanvasSupport.tRotate( this.currentMatrix, rotation )
+    }
+  },
+
+  __skewXMatrix : function(skewX) {
+    if (skewX.length && skewX[0])
+      CanvasSupport.tSkewX(this.currentMatrix, skewX[0])
+    else
+      CanvasSupport.tSkewX(this.currentMatrix, skewX)
+  },
+
+  __skewYMatrix : function(skewY) {
+    if (skewY.length && skewY[0])
+      CanvasSupport.tSkewY(this.currentMatrix, skewY[0])
+    else
+      CanvasSupport.tSkewY(this.currentMatrix, skewY)
+  },
+
+  __scaleMatrix : function(scale) {
+    if (scale.length == 2) {
+      if (scale[0] == 1 && scale[1] == 1) return
+      CanvasSupport.tScale(this.currentMatrix,
+                           scale[0], scale[1])
+    } else if (scale.length == 3) {
+      if (scale[0] == 1 || (scale[0].length && (scale[0][0] == 1 && scale[0][1] == 1)))
+        return
+      CanvasSupport.tTranslate(this.currentMatrix,
+                               scale[1], scale[2])
+      if (scale[0].length) {
+        CanvasSupport.tScale(this.currentMatrix,
+                              scale[0][0], scale[0][1])
+      } else {
+        CanvasSupport.tScale( this.currentMatrix, scale[0], scale[0] )
+      }
+      CanvasSupport.tTranslate(this.currentMatrix,
+                               -scale[1], -scale[2])
+    } else if (scale != 1) {
+      CanvasSupport.tScale( this.currentMatrix, scale, scale )
+    }
+  },
+
+  __matrixMatrix : function(matrix) {
+    CanvasSupport.tMatrixMultiply(this.currentMatrix, matrix)
+  },
+
+  __setMatrix : function(ctx, matrix) {
+    CanvasSupport.setTransform(ctx, matrix, this.previousMatrix)
+  },
+
+  __translate : function(ctx, x,y) {
+    if (x.length != null)
+      ctx.translate(x[0], x[1])
+    else
+      ctx.translate(x, y)
+  },
+
+  __rotate : function(ctx, rotation) {
+    if (rotation.length) {
+      if (rotation[1] || rotation[2]) {
+        if (rotation[0] % Math.PI*2 == 0) return
+        ctx.translate( rotation[1], rotation[2] )
+        ctx.rotate( rotation[0] )
+        ctx.translate( -rotation[1], -rotation[2] )
+      } else {
+        ctx.rotate( rotation[0] )
+      }
+    } else {
+      ctx.rotate( rotation )
+    }
+  },
+
+  __skewX : function(ctx, skewX) {
+    if (skewX.length && skewX[0])
+      CanvasSupport.skewX(ctx, skewX[0])
+    else
+      CanvasSupport.skewX(ctx, skewX)
+  },
+
+  __skewY : function(ctx, skewY) {
+    if (skewY.length && skewY[0])
+      CanvasSupport.skewY(ctx, skewY[0])
+    else
+      CanvasSupport.skewY(ctx, skewY)
+  },
+
+  __scale : function(ctx, scale) {
+    if (scale.length == 2) {
+      ctx.scale(scale[0], scale[1])
+    } else if (scale.length == 3) {
+      ctx.translate( scale[1], scale[2] )
+      if (scale[0].length) {
+        ctx.scale(scale[0][0], scale[0][1])
+      } else {
+        ctx.scale(scale[0], scale[0])
+      }
+      ctx.translate( -scale[1], -scale[2] )
+    } else {
+      ctx.scale(scale, scale)
+    }
+  },
+
+  __matrix : function(ctx, matrix) {
+    CanvasSupport.transform(ctx, matrix)
+  }
+
+})
+
+
+/**
+  Timeline is an animator that tweens between its frames.
+
+  When object.time = k.time:
+       object.state = k.state
+  When object.time > k[i-1].time and object.time < k[i].time:
+       object.state = k[i].tween(position, k[i-1].state, k[i].state)
+       where position = elapsed / duration,
+             elapsed = object.time - k[i-1].time,
+             duration = k[i].time - k[i-1].time
+  */
+Timeline = Klass({
+  startTime : null,
+  repeat : false,
+  lastAction : 0,
+
+  initialize : function(repeat, pingpong) {
+    this.repeat = repeat
+    this.keyframes = []
+  },
+
+  addKeyframe : function(time, target, tween) {
+    if (arguments.length == 1) this.keyframes.push(time)
+    else  this.keyframes.push({
+            time : time,
+            target : target,
+            tween : tween
+          })
+  },
+
+  appendKeyframe : function(timeDelta, target, tween) {
+    this.lastAction += timeDelta
+    return this.addKeyframe(this.lastAction, target, tween)
+  },
+
+  evaluate : function(object, ot, dt) {
+    if (this.startTime == null) this.startTime = ot
+    var t = ot - this.startTime
+    if (this.keyframes.length > 0) {
+      // find current keyframe
+      var currentIndex, previousFrame, currentFrame
+      for (var i=0; i<this.keyframes.length; i++) {
+        if (this.keyframes[i].time > t) {
+          currentIndex = i
+          break
+        }
+      }
+      if (currentIndex != null) {
+        previousFrame = this.keyframes[currentIndex-1]
+        currentFrame = this.keyframes[currentIndex]
+      }
+      if (!currentFrame) {
+        if (!this.keyframes.atEnd) {
+          this.keyframes.atEnd = true
+          previousFrame = this.keyframes[this.keyframes.length - 1]
+          Object.extend(object, Object.clone(previousFrame.target))
+          if (this.repeat) this.startTime = ot
+          object.changed = true
+        }
+      } else if (previousFrame) {
+        this.keyframes.atEnd = false
+        // animate towards current keyframe
+        var elapsed = t - previousFrame.time
+        var duration = currentFrame.time - previousFrame.time
+        var pos = elapsed / duration
+        for (var k in currentFrame.target) {
+          if (previousFrame.target[k] != null) {
+            object.tweenVariable(k,
+              previousFrame.target[k], currentFrame.target[k],
+              pos, currentFrame.tween)
+          }
+        }
+      }
+    }
+  }
+
+})
+
+
+Animatable = Klass({
+  tweenFunctions : {
+    linear : function(v) { return v },
+
+    set : function(v) { return Math.floor(v) },
+    discrete : function(v) { return Math.floor(v) },
+
+    sine : function(v) { return 0.5-0.5*Math.cos(v*Math.PI) },
+
+    sproing : function(v) {
+      return (0.5-0.5*Math.cos(v*3.59261946538606)) * 1.05263157894737
+      // pi + pi-acos(0.9)
+    },
+
+    square : function(v) {
+      return v*v
+    },
+
+    cube : function(v) {
+      return v*v*v
+    },
+
+    sqrt : function(v) {
+      return Math.sqrt(v)
+    },
+
+    curt : function(v) {
+      return Math.pow(v, -0.333333333333)
+    }
+  },
+
+  initialize : function() {
+    this.lastAction = 0
+    this.timeline = []
+    this.keyframes = []
+    this.pendingKeyframes = []
+    this.pendingTimelineEvents = []
+    this.timelines = []
+    this.animators = []
+    this.addFrameListener(this.updateTimelines)
+    this.addFrameListener(this.updateKeyframes)
+    this.addFrameListener(this.updateTimeline)
+    this.addFrameListener(this.updateAnimators)
+  },
+
+  updateTimelines : function(t, dt) {
+    for (var i=0; i<this.timelines.length; i++)
+      this.timelines[i].evaluate(this, t, dt)
+  },
+
+  addTimeline : function(tl) {
+    this.timelines.push(tl)
+  },
+
+  removeTimeline : function(tl) {
+    this.timelines.deleteFirst(tl)
+  },
+
+  /**
+    Tweens between keyframes (a keyframe is an object with the new values of
+    the members of this, e.g. { time: 0, target: { x: 10, y: 20 }, tween: 'square'})
+
+    Keyframes are very much like multi-variable animators, the main difference
+    is that with keyframes the start value and the duration are implicit.
+
+    While an animation from value A to B would take two keyframes instead of
+    a single animator, chaining and reordering keyframes is very easy.
+    */
+  updateKeyframes : function(t,dt) {
+    this.addPendingKeyframes(t)
+    if (this.keyframes.length > 0) {
+      // find current keyframe
+      var currentIndex, previousFrame, currentFrame
+      for (var i=0; i<this.keyframes.length; i++) {
+        if (this.keyframes[i].time > t) {
+          currentIndex = i
+          break
+        }
+      }
+      if (currentIndex != null) {
+        previousFrame = this.keyframes[currentIndex-1]
+        currentFrame = this.keyframes[currentIndex]
+      }
+      if (!currentFrame) {
+        if (!this.keyframes.atEnd) {
+          this.keyframes.atEnd = true
+          previousFrame = this.keyframes[this.keyframes.length - 1]
+          Object.extend(this, Object.clone(previousFrame.target))
+          this.changed = true
+        }
+      } else if (previousFrame) {
+        this.keyframes.atEnd = false
+        // animate towards current keyframe
+        var elapsed = t - previousFrame.time
+        var duration = currentFrame.time - previousFrame.time
+        var pos = elapsed / duration
+        for (var k in currentFrame.target) {
+          if (previousFrame.target[k] != null) {
+            this.tweenVariable(k,
+              previousFrame.target[k], currentFrame.target[k],
+              pos, currentFrame.tween)
+          }
+        }
+      }
+    }
+  },
+
+  addPendingKeyframes : function(t) {
+    if (this.pendingKeyframes.length > 0) {
+      while (this.pendingKeyframes.length > 0) {
+        var kf = this.pendingKeyframes.shift()
+        if (kf.time == null)
+          kf.time = kf.relativeTime + t
+        this.keyframes.push(kf)
+      }
+      this.keyframes.stableSort(function(a,b) { return a.time - b.time })
+    }
+  },
+
+  /**
+    Run and remove timelineEvents that have startTime <= t.
+    TimelineEvents are run in the ascending order of their startTimes.
+    */
+  updateTimeline : function(t, dt) {
+    this.addPendingTimelineEvents(t)
+    while (this.timeline[0] && this.timeline[0].startTime <= t) {
+      var keyframe = this.timeline.shift()
+      var rv = true
+      if (typeof(keyframe.action) == 'function')
+        rv = keyframe.action.call(this, t, dt, keyframe)
+      else
+        this.animators.push(keyframe.action)
+      if (keyframe.repeatEvery != null && rv != false) {
+        if (keyframe.repeatTimes != null) {
+          if (keyframe.repeatTimes <= 0) continue
+          keyframe.repeatTimes--
+        }
+        keyframe.startTime += keyframe.repeatEvery
+        this.addTimelineEvent(keyframe)
+      }
+      this.changed = true
+    }
+  },
+
+  addPendingTimelineEvents : function(t) {
+    if (this.pendingTimelineEvents.length > 0) {
+      while (this.pendingTimelineEvents.length > 0) {
+        var kf = this.pendingTimelineEvents.shift()
+        if (!kf.startTime)
+          kf.startTime = kf.relativeStartTime + t
+        this.timeline.push(kf)
+      }
+      this.timeline.stableSort(function(a,b) { return a.startTime - b.startTime })
+    }
+  },
+
+  addTimelineEvent : function(kf) {
+    this.pendingTimelineEvents.push(kf)
+  },
+
+  /**
+    Run each animator, delete ones that have their durations exceeded.
+    */
+  updateAnimators : function(t, dt) {
+    for (var i=0; i<this.animators.length; i++) {
+      var ani = this.animators[i]
+      if (!ani.startTime) ani.startTime = t
+      var elapsed = t - ani.startTime
+      var pos = elapsed / ani.duration
+      var shouldRemove = false
+      if (pos >= 1) {
+        if (!ani.repeat) {
+          pos = 1
+          shouldRemove = true
+        } else {
+          if (ani.repeat !== true) ani.repeat = Math.max(0, ani.repeat - 1)
+          if (ani.accumulate) {
+            ani.startValue = Object.clone(ani.endValue)
+            ani.endValue = Object.sum(ani.difference, ani.endValue)
+          }
+          if (ani.repeat == 0) {
+            shouldRemove = true
+            pos = 1
+          } else {
+            ani.startTime = t
+            pos = pos % 1
+          }
+        }
+      } else if (ani.repeat && ani.repeat !== true && ani.repeat <= pos) {
+        shouldRemove = true
+        pos = ani.repeat
+      }
+      this.tweenVariable(ani.variable, ani.startValue, ani.endValue, pos, ani.tween)
+      if (shouldRemove) {
+        this.animators.splice(i, 1)
+        i--
+      }
+    }
+  },
+
+  tweenVariable : function(variable, start, end, pos, tweenFunction) {
+    if (typeof(tweenFunction) != 'function') {
+      tweenFunction = this.tweenFunctions[tweenFunction] || this.tweenFunctions.linear
+    }
+    var tweened = tweenFunction(pos)
+    if (typeof(variable) != 'function') {
+      if (start instanceof Array) {
+        for (var j=0; j<start.length; j++) {
+          this[variable][j] = start[j] + tweened*(end[j]-start[j])
+        }
+      } else {
+        this[variable] = start + tweened*(end-start)
+      }
+    } else {
+      variable.call(this, tweened, start, end)
+    }
+    this.changed = true
+  },
+
+  animate : function(variable, start, end, duration, tween, config) {
+    var start = Object.clone(start)
+    var end = Object.clone(end)
+    if (!config) config = {}
+    if (config.additive) {
+      var diff = Object.sub(end, start)
+      start = Object.sum(start, this[variable])
+      end = Object.sum(end, this[variable])
+    }
+    if (typeof(variable) != 'function')
+      this[variable] = Object.clone(start)
+    var ani = {
+      id : Animatable.uid++,
+      variable : variable,
+      startValue : start,
+      endValue : end,
+      difference : diff,
+      duration : duration,
+      tween : tween,
+      repeat : config.repeat,
+      additive : config.additive,
+      accumulate : config.accumulate,
+      pingpong : config.pingpong
+    }
+    this.animators.push(ani)
+    return ani
+  },
+
+  removeAnimator : function(animator) {
+    this.animators.deleteFirst(animator)
+  },
+
+  animateTo : function(variableName, end, duration, tween, config) {
+    return this.animate(variableName, this[variableName], end, duration, tween, config)
+  },
+
+  animateFrom : function(variableName, start, duration, tween, config) {
+    return this.animate(variableName, start, this[variableName], duration, tween, config)
+  },
+
+  animateFactor : function(variableName, start, endFactor, duration, tween, config) {
+    var end
+    if (start instanceof Array) {
+      end = []
+      for (var i=0; i<start.length; i++) {
+        end[i] = start[i] * endFactor
+      }
+    } else {
+      end = start * endFactor
+    }
+    return this.animate(variableName, start, end, duration, tween, config)
+  },
+
+  animateToFactor : function(variableName, endFactor, duration, tween, config) {
+    var start = this[variableName]
+    return this.animateFactor(variableName, start, endFactor, duration, tween, config)
+  },
+
+  addKeyframe : function(time, target, tween) {
+    var kf = {
+      relativeTime: time,
+      target: target,
+      tween: tween
+    }
+    this.pendingKeyframes.push(kf)
+  },
+
+  addKeyframeAt : function(time, target, tween) {
+    var kf = {
+      time: time,
+      target: target,
+      tween: tween
+    }
+    this.pendingKeyframes.push(kf)
+  },
+
+  appendKeyframe : function(timeDelta, target, tween) {
+    this.lastAction += timeDelta
+    return this.addKeyframe(this.lastAction, target, tween)
+  },
+
+  every : function(duration, action, noFirst) {
+    var kf = {
+      action : action,
+      relativeStartTime : noFirst ? duration : 0,
+      repeatEvery : duration
+    }
+    this.addTimelineEvent(kf)
+    return kf
+  },
+
+  at : function(time, action) {
+    var kf = {
+      action : action,
+      startTime : time
+    }
+    this.addTimelineEvent(kf)
+    return kf
+  },
+
+  after : function(duration, action) {
+    var kf = {
+      action : action,
+      relativeStartTime : duration
+    }
+    this.addTimelineEvent(kf)
+    return kf
+  },
+
+  afterFrame : function(duration, callback) {
+    var elapsed = 0
+    var animator
+    animator = function(t, dt){
+      if (elapsed >= duration) {
+        callback.call(this)
+        this.removeFrameListener(animator)
+      }
+      elapsed++
+    }
+    this.addFrameListener(animator)
+    return animator
+  },
+
+  everyFrame : function(duration, callback, noFirst) {
+    var elapsed = noFirst ? 0 : duration
+    var animator
+    animator = function(t, dt){
+      if (elapsed >= duration) {
+        if (callback.call(this) == false)
+          this.removeFrameListener(animator)
+        elapsed = 0
+      }
+      elapsed++
+    }
+    this.addFrameListener(animator)
+    return animator
+  }
+})
+Animatable.uid = 0
+
+
+
+/**
+  CanvasNode is the base CAKE scenegraph node. All the other scenegraph nodes
+  derive from it. A plain CanvasNode does no drawing, but it can be used for
+  grouping other nodes and setting up the group's drawing state.
+
+  var scene = new CanvasNode({x: 10, y: 10})
+
+  The usual way to use CanvasNodes is to append them to a Canvas object:
+
+    var scene = new CanvasNode()
+    scene.append(new Rectangle(40, 40, {fill: true}))
+    var elem = E.canvas(400, 400)
+    var canvas = new Canvas(elem)
+    canvas.append(scene)
+
+  You can also use CanvasNodes to draw directly to a canvas element:
+
+    var scene = new CanvasNode()
+    scene.append(new Circle(40, {x:200, y:200, stroke: true}))
+    var elem = E.canvas(400, 400)
+    scene.handleDraw(elem.getContext('2d'))
+
+  */
+CanvasNode = Klass(Animatable, Transformable, {
+  OBJECTBOUNDINGBOX : 'objectBoundingBox',
+
+  // whether to draw the node and its childNodes or not
+  visible : true,
+
+  // whether to draw the node (doesn't affect subtree)
+  drawable : true,
+
+  // the CSS display property can be used to affect 'visible'
+  // false     => visible = visible
+  // 'none'    => visible = false
+  // otherwise => visible = true
+  display : null,
+
+  // the CSS visibility property can be used to affect 'drawable'
+  // false     => drawable = drawable
+  // 'hidden'  => drawable = false
+  // otherwise => drawable = true
+  visibility : null,
+
+  // whether this and the subtree from this register mouse hover
+  catchMouse : true,
+
+  // Whether this object registers mouse hover. Only set this to true when you
+  // have a drawable object that can be picked. Otherwise the object requires
+  // a matrix inversion on Firefox 2 and Safari, which is slow.
+  pickable : false,
+
+  // true if this node or one of its descendants is under the mouse
+  // cursor and catchMouse is true
+  underCursor : false,
+
+  // zIndex in relation to sibling nodes (note: not global)
+  zIndex : 0,
+
+  // x translation of the node
+  x : 0,
+
+  // y translation of the node
+  y : 0,
+
+  // scale factor: number for uniform scaling, [x,y] for dimension-wise
+  scale : 1,
+
+  // Rotation of the node, in radians.
+  //
+  // The rotation can also be the array [angle, cx, cy],
+  // where cx and cy define the rotation center.
+  //
+  // The array form is equivalent to
+  // translate(cx, cy); rotate(angle); translate(-cx, -cy);
+  rotation : 0,
+
+  // Transform matrix with which to multiply the current transform matrix.
+  // Applied after all other transformations.
+  matrix : null,
+
+  // Transform matrix with which to replace the current transform matrix.
+  // Applied before any other transformation.
+  absoluteMatrix : null,
+
+  // SVG-like list of transformations to apply.
+  // The different transformations are:
+  // ['translate', [x,y]]
+  // ['rotate', [angle, cx, cy]] - (optional) cx and cy are the rotation center
+  // ['scale', [x,y]]
+  // ['matrix', [m11, m12, m21, m22, dx, dy]]
+  transformList : null,
+
+  // fillStyle for the node and its descendants
+  // Possibilities:
+  //   null // use the previous
+  //   true      // use the previous but do fill
+  //   false     // use the previous but don't do fill
+  //   'none'    // use the previous but don't do fill
+  //
+  //   'white'
+  //   '#fff'
+  //   '#ffffff'
+  //   'rgba(255,255,255, 1.0)'
+  //   [255, 255, 255, 1.0]
+  //   new Gradient(...)
+  //   new Pattern(myImage, 'no-repeat')
+  fill : null,
+
+  // strokeStyle for the node and its descendants
+  // Possibilities:
+  //   null // use the previous
+  //   true      // use the previous but do stroke
+  //   false     // use the previous but don't do stroke
+  //   'none'    // use the previous but don't do stroke
+  //
+  //   'white'
+  //   '#fff'
+  //   '#ffffff'
+  //   'rgba(255,255,255, 1.0)'
+  //   [255, 255, 255, 1.0]
+  //   new Gradient(...)
+  //   new Pattern(myImage, 'no-repeat')
+  stroke : null,
+
+  // stroke line width
+  strokeWidth : null,
+
+  // stroke line cap style ('butt' | 'round' | 'square')
+  lineCap : null,
+
+  // stroke line join style ('bevel' | 'round' | 'miter')
+  lineJoin : null,
+
+  // stroke line miter limit
+  miterLimit : null,
+
+  // set globalAlpha to this value
+  absoluteOpacity : null,
+
+  // multiply globalAlpha by this value
+  opacity : null,
+
+  // fill opacity
+  fillOpacity : null,
+
+  // stroke opacity
+  strokeOpacity : null,
+
+  // set globalCompositeOperation to this value
+  // Possibilities:
+  // ( 'source-over' |
+  //   'copy' |
+  //   'lighter' |
+  //   'darker' |
+  //   'xor' |
+  //   'source-in' |
+  //   'source-out' |
+  //   'destination-over' |
+  //   'destination-atop' |
+  //   'destination-in' |
+  //   'destination-out' )
+  compositeOperation : null,
+
+  // Color for the drop shadow
+  shadowColor : null,
+
+  // Drop shadow blur radius
+  shadowBlur : null,
+
+  // Drop shadow's x-offset
+  shadowOffsetX : null,
+
+  // Drop shadow's y-offset
+  shadowOffsetY : null,
+
+  // HTML5 text API
+  font : null,
+  // horizontal position of the text origin
+  // 'left' | 'center' | 'right' | 'start' | 'end'
+  textAlign : null,
+  // vertical position of the text origin
+  // 'top' | 'hanging' | 'middle' | 'alphabetic' | 'ideographic' | 'bottom'
+  textBaseline : null,
+
+  cursor : null,
+
+  changed : true,
+
+  tagName : 'g',
+
+  getNextSibling : function(){
+    if (this.parentNode)
+      return this.parentNode.childNodes[this.parentNode.childNodes.indexOf(this)+1]
+    return null
+  },
+
+  getPreviousSibling : function(){
+    if (this.parentNode)
+      return this.parentNode.childNodes[this.parentNode.childNodes.indexOf(this)-1]
+    return null
+  },
+
+  /**
+    Initialize the CanvasNode and merge an optional config hash.
+    */
+  initialize : function(config) {
+    this.root = this
+    this.currentMatrix = [1,0,0,1,0,0]
+    this.previousMatrix = [1,0,0,1,0,0]
+    this.needMatrixUpdate = true
+    this.childNodes = []
+    this.frameListeners = []
+    this.eventListeners = {}
+    Animatable.initialize.call(this)
+    if (config)
+      Object.extend(this, config)
+  },
+
+  /**
+    Create a clone of the node and its subtree.
+    */
+  clone : function() {
+    var c = Object.clone(this)
+    c.parent = c.root = null
+    for (var i in this) {
+      if (typeof(this[i]) == 'object')
+        c[i] = Object.clone(this[i])
+    }
+    c.parent = c.root = null
+    c.childNodes = []
+    c.setRoot(null)
+    for (var i=0; i<this.childNodes.length; i++) {
+      var ch = this.childNodes[i].clone()
+      c.append(ch)
+    }
+    return c
+  },
+
+  cloneNode : function(){ return this.clone() },
+
+  /**
+    Gets node by id.
+    */
+  getElementById : function(id) {
+    if (this.id == id)
+      return this
+    for (var i=0; i<this.childNodes.length; i++) {
+      var n = this.childNodes[i].getElementById(id)
+      if (n) return n
+    }
+    return null
+  },
+
+  $ : function(id) {
+    return this.getElementById(id)
+  },
+
+  /**
+    Alias for append().
+
+    @param Node[s] to append
+    */
+  appendChild : function() {
+    return this.append.apply(this, arguments)
+  },
+
+  /**
+    Appends arguments as childNodes to the node.
+
+    Adding a child sets child.parent to be the node and calls
+    child.setRoot(node.root)
+
+    @param Node[s] to append
+    */
+  append : function(obj) {
+    var a = $A(arguments)
+    for (var i=0; i<a.length; i++) {
+      if (a[i].parent) a[i].removeSelf()
+      this.childNodes.push(a[i])
+      a[i].parent = a[i].parentNode = this
+      a[i].setRoot(this.root)
+    }
+    this.changed = true
+  },
+
+  /**
+    Removes all childNodes from the node.
+    */
+  removeAllChildren : function() {
+    this.remove.apply(this, this.childNodes)
+  },
+
+  /**
+    Alias for remove().
+
+    @param Node[s] to remove
+    */
+  removeChild : function() {
+    return this.remove.apply(this, arguments)
+  },
+
+  /**
+    Removes arguments from the node's childNodes.
+
+    Removing a child sets its parent to null and calls
+    child.setRoot(null)
+
+    @param Child node[s] to remove
+    */
+  remove : function(obj) {
+    var a = arguments
+    for (var i=0; i<a.length; i++) {
+      this.childNodes.deleteFirst(a[i])
+      delete a[i].parent
+      delete a[i].parentNode
+      a[i].setRoot(null)
+    }
+    this.changed = true
+  },
+
+  /**
+    Calls this.parent.removeChild(this) if this.parent is set.
+    */
+  removeSelf : function() {
+    if (this.parentNode) {
+      this.parentNode.remove(this)
+    }
+  },
+
+  /**
+    Returns true if this node's subtree contains obj. (I.e. obj is this or
+    obj's parent chain includes this.)
+
+    @param obj Node to look for
+    @return True if obj is in this node's subtree, false if it isn't.
+    */
+  contains : function(obj) {
+    while (obj) {
+      if (obj == this) return true
+      obj = obj.parentNode
+    }
+    return false
+  },
+
+  /**
+    Set this.root to the given value and propagate the update to childNodes.
+
+    @param root The new root node
+    @private
+    */
+  setRoot : function(root) {
+    if (!root) root = this
+    this.dispatchEvent({type: 'rootChanged', canvasTarget: this, relatedTarget: root})
+    this.root = root
+    for (var i=0; i<this.childNodes.length; i++)
+      this.childNodes[i].setRoot(root)
+  },
+
+  /**
+    Adds a callback function to be called before drawing each frame.
+
+    @param f Callback function
+    */
+  addFrameListener : function(f) {
+    this.frameListeners.push(f)
+  },
+
+  /**
+    Removes a callback function from update callbacks.
+
+    @param f Callback function
+    */
+  removeFrameListener : function(f) {
+    this.frameListeners.deleteFirst(f)
+  },
+
+  addEventListener : function(type, listener, capture) {
+    if (!this.eventListeners[type])
+      this.eventListeners[type] = {capture:[], bubble:[]}
+    this.eventListeners[type][capture ? 'capture' : 'bubble'].push(listener)
+  },
+
+  /**
+    Synonym for addEventListener.
+  */
+  when : function(type, listener, capture) {
+    this.addEventListener(type, listener, capture || false)
+  },
+
+  removeEventListener : function(type, listener, capture) {
+    if (!this.eventListeners[type]) return
+    this.eventListeners[type][capture ? 'capture' : 'bubble'].deleteFirst(listener)
+    if (this.eventListeners[type].capture.length == 0 &&
+        this.eventListeners[type].bubble.length == 0)
+      delete this.eventListeners[type]
+  },
+
+  dispatchEvent : function(event) {
+    var type = event.type
+    if (!event.canvasTarget) {
+      if (type.search(/^(key|text)/i) == 0) {
+        event.canvasTarget = this.root.focused || this.root.target
+      } else {
+        event.canvasTarget = this.root.target
+      }
+      if (!event.canvasTarget)
+        event.canvasTarget = this
+    }
+    var path = []
+    var obj = event.canvasTarget
+    while (obj && obj != this) {
+      path.push(obj)
+      obj = obj.parent
+    }
+    path.push(this)
+    event.canvasPhase = 'capture'
+    for (var i=path.length-1; i>=0; i--)
+      if (!path[i].handleEvent(event)) return false
+    event.canvasPhase = 'bubble'
+    for (var i=0; i<path.length; i++)
+      if (!path[i].handleEvent(event)) return false
+    return true
+  },
+
+  broadcastEvent : function(event) {
+    var type = event.type
+    event.canvasPhase = 'capture'
+    if (!this.handleEvent(event)) return false
+    for (var i=0; i<this.childNodes.length; i++)
+      if (!this.childNodes[i].broadcastEvent(event)) return false
+    event.canvasPhase = 'bubble'
+    if (!this.handleEvent(event)) return false
+    return true
+  },
+
+  handleEvent : function(event) {
+    var type = event.type
+    var phase = event.canvasPhase
+    if (this.cursor && phase == 'capture')
+      event.cursor = this.cursor
+    var els = this.eventListeners[type]
+    els = els && els[phase]
+    if (els) {
+      for (var i=0; i<els.length; i++) {
+        var rv = els[i].call(this, event)
+        if (rv == false || event.stopped) {
+          if (!event.stopped)
+            event.stopPropagation()
+          event.stopped = true
+          return false
+        }
+      }
+    }
+    return true
+  },
+
+  /**
+    Handle scenegraph update.
+    Called with current time before drawing each frame.
+
+    This method should be touched only if you know what you're doing.
+    If you need your own update handler, either add a frame listener or
+    overwrite {@link CanvasNode#update}.
+
+    @param time Current animation time
+    @param timeDelta Time since last frame in milliseconds
+    */
+  handleUpdate : function(time, timeDelta) {
+    this.update(time, timeDelta)
+    this.willBeDrawn = (!this.parent || this.parent.willBeDrawn) && (this.display ? this.display != 'none' : this.visible)
+    for(var i=0; i<this.childNodes.length; i++)
+      this.childNodes[i].handleUpdate(time, timeDelta)
+    // TODO propagate dirty area bbox up the scene graph
+    if (this.parent && this.changed) {
+      this.parent.changed = this.changed
+      this.changed = false
+    }
+    this.needMatrixUpdate = true
+  },
+
+  /**
+    Update this node. Calls all frame listener callbacks in the order they
+    were added.
+
+    Overwrite this with your own method if you want to do things differently.
+
+    @param time Current animation time
+    @param timeDelta Time since last frame in milliseconds
+    */
+  update : function(time, timeDelta) {
+    // need to operate on a copy, otherwise bad stuff happens
+    var fl = this.frameListeners.slice(0)
+    for(var i=0; i<fl.length; i++) {
+      if (this.frameListeners.includes(fl[i]))
+        fl[i].apply(this, arguments)
+    }
+  },
+
+  /**
+    Tests if this node or its subtree is under the mouse cursor and
+    sets this.underCursor accordingly.
+
+    If this node (and not one of its childNodes) is under the mouse cursor
+    this.root.target is set to this. This way, the topmost (== drawn last)
+    node under the mouse cursor is the root target.
+
+    To see whether a subtree node is the current target:
+
+    if (this.underCursor && this.contains(this.root.target)) {
+      // we are the target, let's roll
+    }
+
+    This method should be touched only if you know what you're doing.
+    Overwrite {@link CanvasNode#drawPickingPath} to change the way the node's
+    picking path is created.
+
+    Called after handleUpdate, but before handleDraw.
+
+    @param ctx Canvas 2D context
+    */
+  handlePick : function(ctx) {
+    // CSS display & visibility
+    if (this.display)
+      this.visible = (this.display != 'none')
+    if (this.visibility)
+      this.drawable = (this.visibility != 'hidden')
+    this.underCursor = false
+    if (this.visible && this.catchMouse && this.root.absoluteMouseX != null) {
+      ctx.save()
+      this.transform(ctx, true)
+      if (this.pickable && this.drawable) {
+        if (ctx.isPointInPath) {
+          ctx.beginPath()
+          if (this.drawPickingPath)
+            this.drawPickingPath(ctx)
+        }
+        this.underCursor = CanvasSupport.isPointInPath(
+                              this.drawPickingPath ? ctx : false,
+                              this.root.mouseX,
+                              this.root.mouseY,
+                              this.currentMatrix,
+                              this)
+        if (this.underCursor)
+          this.root.target = this
+      } else {
+        this.underCursor = false
+      }
+      var c = this.__getChildrenCopy()
+      this.__zSort(c)
+      for(var i=0; i<c.length; i++) {
+        c[i].handlePick(ctx)
+        if (!this.underCursor)
+          this.underCursor = c[i].underCursor
+      }
+      ctx.restore()
+    } else {
+      var c = this.__getChildrenCopy()
+      while (c.length > 0) {
+        var c0 = c.pop()
+        if (c0.underCursor) {
+          c0.underCursor = false
+          Array.prototype.push.apply(c, c0.childNodes)
+        }
+      }
+    }
+  },
+
+  __zSort : function(c) {
+    c.stableSort(function(c1,c2) { return c1.zIndex - c2.zIndex; });
+  },
+
+  __getChildrenCopy : function() {
+    if (this.__childNodesCopy) {
+      while (this.__childNodesCopy.length > this.childNodes.length)
+        this.__childNodesCopy.pop()
+      for (var i=0; i<this.childNodes.length; i++)
+        this.__childNodesCopy[i] = this.childNodes[i]
+    } else {
+      this.__childNodesCopy = this.childNodes.slice(0)
+    }
+    return this.__childNodesCopy
+  },
+
+  /**
+    Returns true if the point x,y is inside the path of a drawable node.
+
+    The x,y point is in user-space coordinates, meaning that e.g. the point
+    5,5 will always be inside the rectangle [0, 0, 10, 10], regardless of the
+    transform on the rectangle.
+
+    Leave isPointInPath to false to avoid unnecessary matrix inversions for
+    non-drawables.
+
+    @param x X-coordinate of the point.
+    @param y Y-coordinate of the point.
+    @return Whether the point is inside the path of this node.
+    @type boolean
+    */
+  isPointInPath : false,
+
+  /**
+    Handles transforming and drawing the node and its childNodes
+    on each frame.
+
+    Pushes context state, applies state transforms and draws the node.
+    Then sorts the node's childNodes by zIndex, smallest first, and
+    calls their handleDraws in that order. Finally, pops the context state.
+
+    Called after handleUpdate and handlePick.
+
+    This method should be touched only if you know what you're doing.
+    Overwrite {@link CanvasNode#draw} when you need to draw things.
+
+    @param ctx Canvas 2D context
+    */
+  handleDraw : function(ctx) {
+    // CSS display & visibility
+    if (this.display)
+      this.visible = (this.display != 'none')
+    if (this.visibility)
+      this.drawable = (this.visibility != 'hidden')
+    if (!this.visible) return
+    ctx.save()
+    var pff = ctx.fontFamily
+    var pfs = ctx.fontSize
+    var pfo = ctx.fillOn
+    var pso = ctx.strokeOn
+    if (this.fontFamily)
+      ctx.fontFamily = this.fontFamily
+    if (this.fontSize)
+      ctx.fontSize = this.fontSize
+    this.transform(ctx)
+    if (this.clipPath) {
+      ctx.beginPath()
+      if (this.clipPath.units == this.OBJECTBOUNDINGBOX) {
+        var bb = this.getSubtreeBoundingBox(true)
+        ctx.save()
+        ctx.translate(bb[0], bb[1])
+        ctx.scale(bb[2], bb[3])
+        this.clipPath.createSubtreePath(ctx, true)
+        ctx.restore()
+        ctx.clip()
+      } else {
+        this.clipPath.createSubtreePath(ctx, true)
+        ctx.clip()
+      }
+    }
+    if (this.drawable && this.draw)
+      this.draw(ctx)
+    var c = this.__getChildrenCopy()
+    this.__zSort(c);
+    for(var i=0; i<c.length; i++) {
+      c[i].handleDraw(ctx)
+    }
+    ctx.fontFamily = pff
+    ctx.fontSize = pfs
+    ctx.fillOn = pfo
+    ctx.strokeOn = pso
+    ctx.restore()
+  },
+
+  /**
+    Transforms the context state according to this node's attributes.
+
+    @param ctx Canvas 2D context
+    @param onlyTransform If set to true, only do matrix transforms.
+    */
+  transform : function(ctx, onlyTransform) {
+    Transformable.prototype.transform.call(this, ctx)
+
+    if (onlyTransform) return
+
+    // stroke / fill modifiers
+    if (this.fill != null) {
+      if (!this.fill || this.fill == 'none') {
+        ctx.fillOn = false
+      } else {
+        ctx.fillOn = true
+        if (this.fill != true) {
+          var fillStyle = Colors.parseColorStyle(this.fill, ctx)
+          ctx.setFillStyle( fillStyle )
+        }
+      }
+    }
+    if (this.stroke != null) {
+      if (!this.stroke || this.stroke == 'none') {
+        ctx.strokeOn = false
+      } else {
+        ctx.strokeOn = true
+        if (this.stroke != true)
+          ctx.setStrokeStyle( Colors.parseColorStyle(this.stroke, ctx) )
+      }
+    }
+    if (this.strokeWidth != null)
+      ctx.setLineWidth( this.strokeWidth )
+    if (this.lineCap != null)
+      ctx.setLineCap( this.lineCap )
+    if (this.lineJoin != null)
+      ctx.setLineJoin( this.lineJoin )
+    if (this.miterLimit != null)
+      ctx.setMiterLimit( this.miterLimit )
+
+    // compositing modifiers
+    if (this.absoluteOpacity != null)
+      ctx.setGlobalAlpha( this.absoluteOpacity )
+    if (this.opacity != null)
+      ctx.setGlobalAlpha( ctx.globalAlpha * this.opacity )
+    if (this.compositeOperation != null)
+      ctx.setGlobalCompositeOperation( this.compositeOperation )
+
+    // shadow modifiers
+    if (this.shadowColor != null)
+      ctx.setShadowColor( Colors.parseColorStyle(this.shadowColor, ctx) )
+    if (this.shadowBlur != null)
+      ctx.setShadowBlur( this.shadowBlur )
+    if (this.shadowOffsetX != null)
+      ctx.setShadowOffsetX( this.shadowOffsetX )
+    if (this.shadowOffsetY != null)
+      ctx.setShadowOffsetY( this.shadowOffsetY )
+
+    // text modifiers
+    if (this.textAlign != null)
+      ctx.setTextAlign( this.textAlign )
+    if (this.textBaseline != null)
+      ctx.setTextBaseline( this.textBaseline )
+    if (this.font != null)
+      ctx.setFont( this.font )
+  },
+
+  /**
+    Draws the picking path for the node for testing if the mouse cursor
+    is inside the node.
+
+    False by default, overwrite if you need special behaviour.
+
+    @param ctx Canvas 2D context
+    */
+  drawPickingPath : false,
+
+  /**
+    Draws the node.
+
+    False by default, overwrite to actually draw something.
+
+    @param ctx Canvas 2D context
+    */
+  draw : false,
+
+  createSubtreePath : function(ctx, skipTransform) {
+    ctx.save()
+    if (!skipTransform) this.transform(ctx, true)
+    for (var i=0; i<this.childNodes.length; i++)
+      this.childNodes[i].createSubtreePath(ctx)
+    ctx.restore()
+  },
+
+  getSubtreeBoundingBox : function(identity) {
+    if (identity) {
+      var p = this.parent
+      this.parent = null
+      this.needMatrixUpdate = true
+    }
+    var bb = this.getAxisAlignedBoundingBox()
+    for (var i=0; i<this.childNodes.length; i++) {
+      var cbb = this.childNodes[i].getSubtreeBoundingBox()
+      if (!bb) {
+        bb = cbb
+      } else if (cbb) {
+        this.mergeBoundingBoxes(bb, cbb)
+      }
+    }
+    if (identity) {
+      this.parent = p
+      this.needMatrixUpdate = true
+    }
+    return bb
+  },
+
+  mergeBoundingBoxes : function(bb, bb2) {
+    if (bb[0] > bb2[0]) bb[0] = bb2[0]
+    if (bb[1] > bb2[1]) bb[1] = bb2[1]
+    if (bb[2]+bb[0] < bb2[2]+bb2[0]) bb[2] = bb2[2]+bb2[0]-bb[0]
+    if (bb[3]+bb[1] < bb2[3]+bb2[1]) bb[3] = bb2[3]+bb2[1]-bb[1]
+  },
+
+  getAxisAlignedBoundingBox : function() {
+    this.transform(null, true)
+    if (!this.getBoundingBox) return null
+    var bbox = this.getBoundingBox()
+    var xy1 = CanvasSupport.tMatrixMultiplyPoint(this.currentMatrix,
+      bbox[0], bbox[1])
+    var xy2 = CanvasSupport.tMatrixMultiplyPoint(this.currentMatrix,
+      bbox[0]+bbox[2], bbox[1]+bbox[3])
+    var xy3 = CanvasSupport.tMatrixMultiplyPoint(this.currentMatrix,
+      bbox[0], bbox[1]+bbox[3])
+    var xy4 = CanvasSupport.tMatrixMultiplyPoint(this.currentMatrix,
+      bbox[0]+bbox[2], bbox[1])
+    var x1 = Math.min(xy1[0], xy2[0], xy3[0], xy4[0])
+    var x2 = Math.max(xy1[0], xy2[0], xy3[0], xy4[0])
+    var y1 = Math.min(xy1[1], xy2[1], xy3[1], xy4[1])
+    var y2 = Math.max(xy1[1], xy2[1], xy3[1], xy4[1])
+    return [x1, y1, x2-x1, y2-y1]
+  },
+
+  makeDraggable : function() {
+    this.addEventListener('dragstart', function(ev) {
+      this.dragStartPosition = {x: this.x, y: this.y};
+      ev.stopPropagation();
+      ev.preventDefault();
+      return false;
+    }, false);
+    this.addEventListener('drag', function(ev) {
+      this.x = this.dragStartPosition.x + this.root.dragX / this.parent.currentMatrix[0];
+      this.y = this.dragStartPosition.y + this.root.dragY / this.parent.currentMatrix[3];
+      ev.stopPropagation();
+      ev.preventDefault();
+      return false;
+    }, false);
+  }
+})
+
+
+/**
+  Canvas is the canvas manager class.
+  It takes care of updating and drawing its childNodes on a canvas element.
+
+  An example with a rotating rectangle:
+
+    var c = E.canvas(500, 500)
+    var canvas = new Canvas(c)
+    var rect = new Rectangle(100, 100)
+    rect.x = 250
+    rect.y = 250
+    rect.fill = true
+    rect.fillStyle = 'green'
+    rect.addFrameListener(function(t) {
+      this.rotation = ((t / 3000) % 1) * Math.PI * 2
+    })
+    canvas.append(rect)
+    document.body.appendChild(c)
+
+
+  To use the canvas as a manually updated image:
+
+    var canvas = new Canvas(E.canvas(200,40), {
+      isPlaying : false,
+      redrawOnlyWhenChanged : true
+    })
+    var c = new Circle(20)
+    c.x = 100
+    c.y = 20
+    c.fill = true
+    c.fillStyle = 'red'
+    c.addFrameListener(function(t) {
+      if (this.root.absoluteMouseX != null) {
+        this.x = this.root.mouseX // relative to canvas surface
+        this.root.changed = true
+      }
+    })
+    canvas.append(c)
+
+
+  Or by using raw onFrame-calls:
+
+    var canvas = new Canvas(E.canvas(200,40), {
+      isPlaying : false,
+      fill : true,
+      fillStyle : 'white'
+    })
+    var c = new Circle(20)
+    c.x = 100
+    c.y = 20
+    c.fill = true
+    c.fillStyle = 'red'
+    canvas.append(c)
+    canvas.onFrame()
+
+
+  Which is also the recommended way to use a canvas inside another canvas:
+
+    var canvas = new Canvas(E.canvas(200,40), {
+      isPlaying : false
+    })
+    var c = new Circle(20, {
+      x: 100, y: 20,
+      fill: true, fillStyle: 'red'
+    })
+    canvas.append(c)
+
+    var topCanvas = new Canvas(E.canvas(500, 500))
+    var canvasImage = new ImageNode(canvas.canvas, {x: 250, y: 250})
+    topCanvas.append(canvasImage)
+    canvasImage.addFrameListener(function(t) {
+      this.rotation = (t / 3000 % 1) * Math.PI * 2
+      canvas.onFrame(t)
+    })
+
+  */
+Canvas = Klass(CanvasNode, {
+
+  clear : true,
+  frameLoop : false,
+  recording : false,
+  opacity : 1,
+  frame : 0,
+  elapsed : 0,
+  frameDuration : 30,
+  speed : 1.0,
+  time : 0,
+  fps : 0,
+  currentRealFps : 0,
+  currentFps : 0,
+  fpsFrames : 30,
+  startTime : 0,
+  realFps : 0,
+  fixedTimestep : false,
+  playOnlyWhenFocused : true,
+  isPlaying : true,
+  redrawOnlyWhenChanged : false,
+  changed : true,
+  drawBoundingBoxes : false,
+  cursor : 'default',
+
+  mouseDown : false,
+  mouseEvents : [],
+
+  // absolute pixel coordinates from canvas top-left
+  absoluteMouseX : null,
+  absoluteMouseY : null,
+
+  /*
+    Coordinates relative to the canvas's surface scale.
+    Example:
+      canvas.width
+      #=> 100
+      canvas.style.width
+      #=> '100px'
+      canvas.absoluteMouseX
+      #=> 50
+      canvas.mouseX
+      #=> 50
+
+      canvas.style.width = '200px'
+      canvas.width
+      #=> 100
+      canvas.absoluteMouseX
+      #=> 100
+      canvas.mouseX
+      #=> 50
+  */
+  mouseX : null,
+  mouseY : null,
+
+  elementNodeZIndexCounter : 0,
+
+  initialize : function(canvas, config) {
+    if (arguments.length > 2) {
+      var container = arguments[0]
+      var w = arguments[1]
+      var h = arguments[2]
+      var config = arguments[3]
+      var canvas = E.canvas(w,h)
+      var canvasContainer = E('div', canvas, {style:
+        {overflow:'hidden', width:w+'px', height:h+'px', position:'relative'}
+      })
+      this.canvasContainer = canvasContainer
+      if (container)
+        container.appendChild(canvasContainer)
+    }
+    CanvasNode.initialize.call(this, config)
+    this.mouseEventStack = []
+    this.canvas = canvas
+    canvas.canvas = this
+    this.width = this.canvas.width
+    this.height = this.canvas.height
+    var th = this
+    this.frameHandler = function() { th.onFrame() }
+    this.canvas.addEventListener('DOMNodeInserted', function(ev) {
+      if (ev.target == this)
+        th.addEventListeners()
+    }, false)
+    this.canvas.addEventListener('DOMNodeRemoved', function(ev) {
+      if (ev.target == this)
+        th.removeEventListeners()
+    }, false)
+    if (this.canvas.parentNode) this.addEventListeners()
+    this.startTime = new Date().getTime()
+    if (this.isPlaying)
+      this.play()
+  },
+
+  // FIXME
+  removeEventListeners : function() {
+  },
+
+  addEventListeners : function() {
+    var th = this
+    this.canvas.parentNode.addMouseEvent = function(e){
+      var xy = Mouse.getRelativeCoords(this, e)
+      th.absoluteMouseX = xy.x
+      th.absoluteMouseY = xy.y
+      var style = document.defaultView.getComputedStyle(th.canvas,"")
+      var w = parseFloat(style.getPropertyValue('width'))
+      var h = parseFloat(style.getPropertyValue('height'))
+      th.mouseX = th.absoluteMouseX * (w / th.canvas.width)
+      th.mouseY = th.absoluteMouseY * (h / th.canvas.height)
+      th.addMouseEvent(th.mouseX, th.mouseY, th.mouseDown)
+    }
+    this.canvas.parentNode.contains = this.contains
+
+    this.canvas.parentNode.addEventListener('mousedown', function(e) {
+      th.mouseDown = true
+      if (th.keyTarget != th.target) {
+        if (th.keyTarget)
+          th.dispatchEvent({type: 'blur', canvasTarget: th.keyTarget})
+        th.keyTarget = th.target
+        if (th.keyTarget)
+          th.dispatchEvent({type: 'focus', canvasTarget: th.keyTarget})
+      }
+      this.addMouseEvent(e)
+    }, true)
+
+    this.canvas.parentNode.addEventListener('mouseup', function(e) {
+      this.addMouseEvent(e)
+      th.mouseDown = false
+    }, true)
+
+    this.canvas.parentNode.addEventListener('mousemove', function(e) {
+      this.addMouseEvent(e)
+      if (th.prevClientX == null) {
+        th.prevClientX = e.clientX
+        th.prevClientY = e.clientY
+      }
+      if (th.dragTarget) {
+        var nev = document.createEvent('MouseEvents')
+        nev.initMouseEvent('drag', true, true, window, e.detail,
+          e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey,
+          e.shiftKey, e.metaKey, e.button, e.relatedTarget)
+        nev.canvasTarget = th.dragTarget
+        nev.dx = e.clientX - th.prevClientX
+        nev.dy = e.clientY - th.prevClientY
+        th.dragX += nev.dx
+        th.dragY += nev.dy
+        th.dispatchEvent(nev)
+      }
+      if (!th.mouseDown) {
+        if (th.dragTarget) {
+          var nev = document.createEvent('MouseEvents')
+          nev.initMouseEvent('dragend', true, true, window, e.detail,
+            e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey,
+            e.shiftKey, e.metaKey, e.button, e.relatedTarget)
+          nev.canvasTarget = th.dragTarget
+          th.dispatchEvent(nev)
+          th.dragX = th.dragY = 0
+          th.dragTarget = false
+        }
+      } else if (!th.dragTarget && th.target) {
+        th.dragTarget = th.target
+        var nev = document.createEvent('MouseEvents')
+        nev.initMouseEvent('dragstart', true, true, window, e.detail,
+          e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey,
+          e.shiftKey, e.metaKey, e.button, e.relatedTarget)
+        nev.canvasTarget = th.dragTarget
+        th.dragStartX = e.clientX
+        th.dragStartY = e.clientY
+        th.dragX = th.dragY = 0
+        th.dispatchEvent(nev)
+      }
+      th.prevClientX = e.clientX
+      th.prevClientY = e.clientY
+    }, true)
+
+    this.canvas.parentNode.addEventListener('mouseout', function(e) {
+      if (!CanvasNode.contains.call(this, e.relatedTarget))
+        th.absoluteMouseX = th.absoluteMouseY = th.mouseX = th.mouseY = null
+    }, true)
+
+    var dispatch = this.dispatchEvent.bind(this)
+    var types = [
+      'mousemove', 'mouseover', 'mouseout',
+      'click', 'dblclick',
+      'mousedown', 'mouseup',
+      'keypress', 'keydown', 'keyup',
+      'DOMMouseScroll', 'mousewheel', 'mousemultiwheel', 'textInput',
+      'focus', 'blur'
+    ]
+    for (var i=0; i<types.length; i++) {
+      this.canvas.parentNode.addEventListener(types[i], dispatch, false)
+    }
+    this.keys = {}
+
+    this.windowEventListeners = {
+
+      keydown : function(ev) {
+        if (th.keyTarget) {
+          th.updateKeys(ev)
+          ev.canvasTarget = th.keyTarget
+          th.dispatchEvent(ev)
+        }
+      },
+
+      keyup : function(ev) {
+        if (th.keyTarget) {
+          th.updateKeys(ev)
+          ev.canvasTarget = th.keyTarget
+          th.dispatchEvent(ev)
+        }
+      },
+
+      // do we even want to have this?
+      keypress : function(ev) {
+        if (th.keyTarget) {
+          ev.canvasTarget = th.keyTarget
+          th.dispatchEvent(ev)
+        }
+      },
+
+      blur : function(ev) {
+        th.absoluteMouseX = th.absoluteMouseY = null
+        if (th.playOnlyWhenFocused && th.isPlaying) {
+          th.stop()
+          th.__blurStop = true
+        }
+      },
+
+      focus : function(ev) {
+        if (th.__blurStop && !th.isPlaying) th.play()
+      },
+
+      mouseup : function(e) {
+        th.mouseDown = false
+        if (th.dragTarget) {
+          // TODO
+          // find the object that receives the drag (i.e. drop target)
+          var nev = document.createEvent('MouseEvents')
+          nev.initMouseEvent('dragend', true, true, window, e.detail,
+            e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey,
+            e.shiftKey, e.metaKey, e.button, e.relatedTarget)
+          nev.canvasTarget = th.dragTarget
+          th.dispatchEvent(nev)
+          th.dragTarget = false
+        }
+        if (!th.canvas.parentNode.contains(e.target)) {
+          var rv = th.dispatchEvent(e)
+          if (th.keyTarget) {
+            th.dispatchEvent({type: 'blur', canvasTarget: th.keyTarget})
+            th.keyTarget = null
+          }
+          return rv
+        }
+      },
+
+      mousemove : function(ev) {
+        if (th.__blurStop && !th.isPlaying) th.play()
+        if (!th.canvas.parentNode.contains(ev.target) && th.mouseDown)
+          return th.dispatchEvent(ev)
+      }
+
+    }
+
+    this.canvas.parentNode.addEventListener('DOMNodeRemoved', function(ev) {
+      if (ev.target == this)
+        th.removeWindowEventListeners()
+    }, false)
+    this.canvas.parentNode.addEventListener('DOMNodeInserted', function(ev) {
+      if (ev.target == this)
+        th.addWindowEventListeners()
+    }, false)
+    if (this.canvas.parentNode.parentNode) this.addWindowEventListeners()
+  },
+
+  updateKeys : function(ev) {
+    this.keys.shift = ev.shiftKey
+    this.keys.ctrl = ev.ctrlKey
+    this.keys.alt = ev.altKey
+    this.keys.meta = ev.metaKey
+    var state = (ev.type == 'keydown')
+    switch (ev.keyCode) {
+      case 37: this.keys.left = state; break
+      case 38: this.keys.up = state; break
+      case 39: this.keys.right = state; break
+      case 40: this.keys.down = state; break
+      case 32: this.keys.space = state; break
+      case 13: this.keys.enter = state; break
+      case 9: this.keys.tab = state; break
+      case 8: this.keys.backspace = state; break
+      case 16: this.keys.shift = state; break
+      case 17: this.keys.ctrl = state; break
+      case 18: this.keys.alt = state; break
+    }
+    this.keys[ev.keyCode] = state
+  },
+
+  addWindowEventListeners : function() {
+    for (var i in this.windowEventListeners)
+      window.addEventListener(i, this.windowEventListeners[i], false)
+  },
+
+  removeWindowEventListeners : function() {
+    for (var i in this.windowEventListeners)
+      window.removeEventListener(i, this.windowEventListeners[i], false)
+  },
+
+  addMouseEvent : function(x,y,mouseDown) {
+    var a = this.allocMouseEvent()
+    a[0] = x
+    a[1] = y
+    a[2] = mouseDown
+    this.mouseEvents.push(a)
+  },
+
+  allocMouseEvent : function() {
+    if (this.mouseEventStack.length > 0) {
+      return this.mouseEventStack.pop()
+    } else {
+      return [null, null, null]
+    }
+  },
+
+  freeMouseEvent : function(ev) {
+    this.mouseEventStack.push(ev)
+    if (this.mouseEventStack.length > 100)
+      this.mouseEventStack.splice(0,this.mouseEventStack.length)
+  },
+
+  clearMouseEvents : function() {
+    while (this.mouseEvents.length > 0)
+      this.freeMouseEvent(this.mouseEvents.pop())
+  },
+
+  /**
+    Start frame loop.
+
+    The frame loop is an interval, where #onFrame is called every
+    #frameDuration milliseconds.
+    */
+  play : function() {
+    this.stop()
+    this.realTime = new Date().getTime()
+    this.frameLoop = setInterval(this.frameHandler, this.frameDuration)
+    this.isPlaying = true
+  },
+
+  /**
+    Stop frame loop.
+    */
+  stop : function() {
+    this.__blurStop = false
+    if (this.frameLoop) {
+      clearInterval(this.frameLoop)
+      this.frameLoop = false
+    }
+    this.isPlaying = false
+  },
+
+  dispatchEvent : function(ev) {
+    var rv = CanvasNode.prototype.dispatchEvent.call(this, ev)
+    if (ev.cursor) {
+      if (this.canvas.style.cursor != ev.cursor)
+        this.canvas.style.cursor = ev.cursor
+    } else {
+      if (this.canvas.style.cursor != this.cursor)
+        this.canvas.style.cursor = this.cursor
+    }
+    return rv
+  },
+
+  /**
+    The frame loop function. Called every #frameDuration milliseconds.
+    Takes an optional external time parameter (for syncing Canvases with each
+    other, e.g. when using a Canvas as an image.)
+
+    If the time parameter is given, the second parameter is used as the frame
+    time delta (i.e. the time elapsed since last frame.)
+
+    If time or timeDelta is not given, the canvas computes its own timeDelta.
+
+    @param time The external time. Optional.
+    @param timeDelta Time since last frame in milliseconds. Optional.
+    */
+  onFrame : function(time, timeDelta) {
+    this.elementNodeZIndexCounter = 0
+    var ctx = this.getContext()
+    try {
+      var realTime = new Date().getTime()
+      this.currentRealElapsed = (realTime - this.realTime)
+      this.currentRealFps = 1000 / this.currentRealElapsed
+      var dt = this.frameDuration * this.speed
+      if (!this.fixedTimestep)
+        dt = this.currentRealElapsed * this.speed
+      this.realTime = realTime
+      if (time != null) {
+        this.time = time
+        if (timeDelta)
+          dt = timeDelta
+      } else {
+        this.time += dt
+      }
+      this.previousTarget = this.target
+      this.target = null
+      if (this.catchMouse)
+        this.handlePick(ctx)
+      if (this.previousTarget != this.target) {
+        if (this.previousTarget) {
+          var nev = document.createEvent('MouseEvents')
+          nev.initMouseEvent('mouseout', true, true, window,
+            0, 0, 0, 0, 0, false, false, false, false, 0, null)
+          nev.canvasTarget = this.previousTarget
+          this.dispatchEvent(nev)
+        }
+        if (this.target) {
+          var nev = document.createEvent('MouseEvents')
+          nev.initMouseEvent('mouseover', true, true, window,
+            0, 0, 0, 0, 0, false, false, false, false, 0, null)
+          nev.canvasTarget = this.target
+          this.dispatchEvent(nev)
+        }
+      }
+      this.handleUpdate(this.time, dt)
+      this.clearMouseEvents()
+      if (!this.redrawOnlyWhenChanged || this.changed) {
+        try {
+          this.handleDraw(ctx)
+        } catch(e) {
+          console.log(e)
+          throw(e)
+        }
+        this.changed = false
+      }
+      this.currentElapsed = (new Date().getTime() - this.realTime)
+      this.elapsed += this.currentElapsed
+      this.currentFps = 1000 / this.currentElapsed
+      this.frame++
+      if (this.frame % this.fpsFrames == 0) {
+        this.fps = this.fpsFrames*1000 / (this.elapsed)
+        this.realFps = this.fpsFrames*1000 / (new Date().getTime() - this.startTime)
+        this.elapsed = 0
+        this.startTime = new Date().getTime()
+      }
+    } catch(e) {
+      if (ctx) {
+        // screwed up, context is borked
+        try {
+          // FIXME don't be stupid
+          for (var i=0; i<1000; i++)
+            ctx.restore()
+        } catch(er) {}
+      }
+      delete this.context
+      throw(e)
+    }
+  },
+
+  /**
+    Returns the canvas drawing context object.
+
+    @return Canvas drawing context
+    */
+  getContext : function() {
+    if (this.recording)
+      return this.getRecordingContext()
+    else if (this.useMockContext)
+      return this.getMockContext()
+    else
+      return this.get2DContext()
+  },
+
+  /**
+    Gets and returns an augmented canvas 2D drawing context.
+
+    The canvas 2D context is augmented by setter functions for all
+    its instance variables, making it easier to record canvas operations in
+    a cross-browser fashion.
+    */
+  get2DContext : function() {
+    if (!this.context) {
+      var ctx = CanvasSupport.getContext(this.canvas, '2d')
+      this.context = ctx
+    }
+    return this.context
+  },
+
+  /**
+    Creates and returns a mock drawing context.
+
+    @return Mock drawing context
+    */
+  getMockContext : function() {
+    if (!this.fakeContext) {
+      var ctx = this.get2DContext()
+      this.fakeContext = {}
+      var f = function(){ return this }
+      for (var i in ctx) {
+        if (typeof(ctx[i]) == 'function')
+          this.fakeContext[i] = f
+        else
+          this.fakeContext[i] = ctx[i]
+      }
+      this.fakeContext.isMockObject = true
+      this.fakeContext.addColorStop = f
+    }
+    return this.fakeContext
+  },
+
+  getRecordingContext : function() {
+    if (!this.recordingContext)
+      this.recordingContext = new RecordingContext()
+    return this.recordingContext
+  },
+
+  /**
+    Canvas drawPickingPath uses the canvas rectangle as its path.
+
+    @param ctx Canvas drawing context
+    */
+  drawPickingPath : function(ctx) {
+    ctx.rect(0,0, this.canvas.width, this.canvas.height)
+  },
+
+  isPointInPath : function(x,y) {
+    return ((x >= 0) && (x <= this.canvas.width) && (y >= 0) && (y <= this.canvas.height))
+  },
+
+  /**
+    Sets globalAlpha to this.opacity and clears the canvas if #clear is set to
+    true. If #fill is also set to true, fills the canvas rectangle instead of
+    clearing (using #fillStyle as the color.)
+
+    @param ctx Canvas drawing context
+    */
+  draw : function(ctx) {
+    ctx.setGlobalAlpha( this.opacity )
+    if (this.clear) {
+      if (ctx.fillOn) {
+        ctx.beginPath()
+        ctx.rect(0,0, this.canvas.width, this.canvas.height)
+        ctx.fill()
+      } else {
+        ctx.clearRect(0,0, this.canvas.width, this.canvas.height)
+      }
+    }
+    // set default fill and stroke for the canvas contents
+    ctx.fillStyle = 'black'
+    ctx.strokeStyle = 'black'
+    ctx.fillOn = false
+    ctx.strokeOn = false
+  }
+})
+
+
+/**
+  Hacky link class for emulating <a>.
+
+  The correct way would be to have a real <a> under the cursor while hovering
+  this, or an imagemap polygon built from the clipped subtree path.
+
+  @param href Link href.
+  @param target Link target, defaults to _self.
+  @param config Optional config hash.
+  */
+LinkNode = Klass(CanvasNode, {
+  href : null,
+  target : '_self',
+  cursor : 'pointer',
+
+  initialize : function(href, target, config) {
+    this.href = href
+    if (target)
+      this.target = target
+    CanvasNode.initialize.call(this, config)
+    this.setupLinkEventListeners()
+  },
+
+  setupLinkEventListeners : function() {
+    this.addEventListener('click', function(ev) {
+      if (ev.button == Mouse.RIGHT) return
+      var target = this.target
+      if ((ev.ctrlKey || ev.button == Mouse.MIDDLE) && target == '_self')
+        target = '_blank'
+      window.open(this.href, target)
+    }, false)
+  }
+})
+
+
+/**
+  AudioNode is a CanvasNode used to play a sound.
+
+  */
+AudioNode = Klass(CanvasNode, {
+  ready : false,
+  autoPlay : false,
+  playing : false,
+  paused : false,
+  pan : 0,
+  volume : 1,
+  loop : false,
+
+  transformSound : false,
+
+  initialize : function(filename, params) {
+    CanvasNode.initialize.call(this, params)
+    this.filename = filename
+    this.when('load', this._autoPlaySound)
+    this.loadSound()
+  },
+
+  loadSound : function() {
+    this.sound = CanvasSupport.getSoundObject()
+    if (!this.sound) return
+    var self = this
+    this.sound.onready = function() {
+      self.ready = true
+      self.root.dispatchEvent({type: 'ready', canvasTarget: self})
+    }
+    this.sound.onload = function() {
+      self.loaded = true
+      self.root.dispatchEvent({type: 'load', canvasTarget: self})
+    }
+    this.sound.onerror = function() {
+      self.root.dispatchEvent({type: 'error', canvasTarget: self})
+    }
+    this.sound.onfinish = function() {
+      if (self.loop) self.play()
+      else self.stop()
+    }
+    this.sound.load(this.filename)
+  },
+
+  play : function() {
+    this.playing = true
+    this.needPlayUpdate = true
+  },
+
+  stop : function() {
+    this.playing = false
+    this.needPlayUpdate = true
+  },
+
+  pause : function() {
+    if (this.needPauseUpdate) {
+      this.needPauseUpdate = false
+      return
+    }
+    this.paused = !this.paused
+    this.needPauseUpdate = true
+  },
+
+  setVolume : function(v) {
+    this.volume = v
+    this.needStatusUpdate = true
+  },
+
+  setPan : function(p) {
+    this.pan = p
+    this.needStatusUpdate = true
+  },
+
+  handleUpdate : function() {
+    CanvasNode.handleUpdate.apply(this, arguments)
+    if (this.willBeDrawn) {
+      this.transform(null, true)
+      if (!this.sound) this.loadSound()
+      if (this.ready) {
+        if (this.transformSound) {
+          var x = this.currentMatrix[4]
+          var y = this.currentMatrix[5]
+          var a = this.currentMatrix[2]
+          var b = this.currentMatrix[3]
+          var c = this.currentMatrix[0]
+          var d = this.currentMatrix[1]
+          var hw = this.root.width * 0.5
+          var ys = Math.sqrt(a*a + b*b)
+          var xs = Math.sqrt(c*c + d*d)
+          this.setVolume(ys)
+          this.setPan((x - hw) / hw)
+        }
+        if (this.needPauseUpdate) {
+          this.needPauseUpdate = false
+          this._pauseSound()
+        }
+        if (this.needPlayUpdate) {
+          this.needPlayUpdate = false
+          if (this.playing) this._playSound()
+          else this._stopSound()
+        }
+        if (this.needStatusUpdate) {
+          this._setSoundVolume()
+          this._setSoundPan()
+        }
+      }
+    }
+  },
+
+  _autoPlaySound : function() {
+    if (this.autoPlay) this.play()
+  },
+
+  _setSoundVolume : function() {
+    this.sound.setVolume(this.volume)
+  },
+
+  _setSoundPan : function() {
+    this.sound.setPan(this.pan)
+  },
+
+  _playSound : function() {
+    if (this.sound.play() == false)
+      return this.playing = false
+    this.root.dispatchEvent({type: 'play', canvasTarget: this})
+  },
+
+  _stopSound : function() {
+    this.sound.stop()
+    this.root.dispatchEvent({type: 'stop', canvasTarget: this})
+  },
+
+  _pauseSound : function() {
+    this.sound.pause()
+    this.root.dispatchEvent({type: this.paused ? 'pause' : 'play', canvasTarget: this})
+  }
+})
+
+
+/**
+  ElementNode is a CanvasNode that has an HTML element as its content.
+
+  The content is added to an absolutely positioned HTML element, which is added
+  to the root node's canvases parentNode. The content element follows the
+  current transformation matrix.
+
+  The opacity of the element is set to the globalAlpha of the drawing context
+  unless #noAlpha is true.
+
+  The font-size of the element is set to the current y-scale unless #noScaling
+  is true.
+
+  Use ElementNode when you need accessible web content in your animations.
+
+    var e = new ElementNode(
+      E('h1', 'HERZLICH WILLKOMMEN IM BAHNHOF'),
+      {
+        x : 40,
+        y : 30
+      }
+    )
+    e.addFrameListener(function(t) {
+      this.scale = 1 + 0.5*Math.cos(t/1000)
+    })
+
+  @param content An HTML element or string of HTML to use as the content.
+  @param config Optional config has.
+  */
+ElementNode = Klass(CanvasNode, {
+  noScaling : false,
+  noAlpha : false,
+  inherit : 'inherit',
+  align: null, // left | center | right
+  valign: null, // top | center | bottom
+  xOffset: 0,
+  yOffset: 0,
+
+  initialize : function(content, config) {
+    CanvasNode.initialize.call(this, config)
+    this.content = content
+    this.element = E('div', content)
+    this.element.style.MozTransformOrigin =
+    this.element.style.webkitTransformOrigin = '0 0'
+    this.element.style.position = 'absolute'
+  },
+
+  clone : function() {
+    var c = CanvasNode.prototype.clone.call(this)
+    if (this.content && this.content.cloneNode)
+      c.content = this.content.cloneNode(true)
+    c.element = E('div', c.content)
+    c.element.style.position = 'absolute'
+    c.element.style.MozTransformOrigin =
+    c.element.style.webkitTransformOrigin = '0 0'
+    return c
+  },
+
+  setRoot : function(root) {
+    CanvasNode.setRoot.call(this, root)
+    if (this.element && this.element.parentNode && this.element.parentNode.removeChild)
+      this.element.parentNode.removeChild(this.element)
+  },
+
+  handleUpdate : function(t, dt) {
+    CanvasNode.handleUpdate.call(this, t, dt)
+    if (!this.willBeDrawn || !this.visible || this.display == 'none' || this.visibility == 'hidden' || !this.drawable) {
+      if (this.element.style.display != 'none')
+        this.element.style.display = 'none'
+    } else if (this.element.style.display == 'none') {
+      this.element.style.display = 'block'
+    }
+  },
+
+  addEventListener : function(event, callback, capture) {
+    var th = this
+    var ccallback = function() { callback.apply(th, arguments) }
+    return this.element.addEventListener(event, ccallback, capture||false)
+  },
+
+  removeEventListener : function(event, callback, capture) {
+    var th = this
+    var ccallback = function() { callback.apply(th, arguments) }
+    return this.element.removeEventListener(event, ccallback, capture||false)
+  },
+
+  draw : function(ctx) {
+    if (this.cursor && this.element.style.cursor != this.cursor)
+      this.element.style.cursor = this.cursor
+    if (this.element.style.zIndex != this.root.elementNodeZIndexCounter)
+      this.element.style.zIndex = this.root.elementNodeZIndexCounter
+    this.root.elementNodeZIndexCounter++
+    var baseTransform = this.currentMatrix
+    xo = this.xOffset
+    yo = this.yOffset
+    if (this.fillBoundingBox && this.parent && this.parent.getBoundingBox) {
+      var bb = this.parent.getBoundingBox()
+      xo += bb[0]
+      yo += bb[1]
+    }
+    var xy = CanvasSupport.tMatrixMultiplyPoint(baseTransform.slice(0,4).concat([0,0]),
+      xo, yo)
+    var x = this.currentMatrix[4] + xy[0]
+    var y = this.currentMatrix[5] + xy[1]
+    var a = this.currentMatrix[2]
+    var b = this.currentMatrix[3]
+    var c = this.currentMatrix[0]
+    var d = this.currentMatrix[1]
+    var ys = Math.sqrt(a*a + b*b)
+    var xs = Math.sqrt(c*c + d*d)
+    if (ctx.fontFamily != null)
+      this.element.style.fontFamily = ctx.fontFamily
+
+    var wkt = CanvasSupport.isCSSTransformSupported()
+    if (wkt && !this.noScaling) {
+      this.element.style.MozTransform =
+      this.element.style.webkitTransform = 'matrix('+baseTransform.join(",")+')'
+    } else {
+      this.element.style.MozTransform =
+      this.element.style.webkitTransform = ''
+    }
+    if (ctx.fontSize != null) {
+      if (this.noScaling || wkt) {
+        this.element.style.fontSize = ctx.fontSize + 'px'
+      } else {
+        this.element.style.fontSize = ctx.fontSize * ys + 'px'
+      }
+    } else {
+      if (this.noScaling || wkt) {
+        this.element.style.fontSize = 'inherit'
+      } else {
+        this.element.style.fontSize = 100 * ys + '%'
+      }
+    }
+    if (this.noAlpha)
+      this.element.style.opacity = 1
+    else
+      this.element.style.opacity = ctx.globalAlpha
+    if (!this.element.parentNode && this.root.canvas.parentNode) {
+      this.element.style.visibility = 'hidden'
+      this.root.canvas.parentNode.appendChild(this.element)
+      var hidden = true
+    }
+    var fs = this.color || this.fill
+    if (this.parent) {
+      if (!fs || !fs.length)
+        fs = this.parent.color
+      if (!fs || !fs.length)
+        fs = this.parent.fill
+    }
+    if (!fs || !fs.length)
+      fs = ctx.fillStyle
+    if (typeof(fs) == 'string') {
+      if (fs.search(/^rgba\(/) != -1) {
+        this.element.style.color = 'rgb(' +
+          fs.match(/\d+/g).slice(0,3).join(",") +
+          ')'
+      } else {
+        this.element.style.color = fs
+      }
+    } else if (fs.length) {
+      this.element.style.color = 'rgb(' + fs.slice(0,3).map(Math.floor).join(",") + ')'
+    }
+    var dx = 0, dy = 0
+    if (bb) {
+      this.element.style.width = Math.floor(xs * bb[2]) + 'px'
+      this.element.style.height = Math.floor(ys * bb[3]) + 'px'
+      this.eWidth = xs
+      this.eHeight = ys
+    } else {
+      this.element.style.width = ''
+      this.element.style.height = ''
+      var align = this.align || this.textAnchor
+      var origin = [0,0]
+      if (align == 'center' || align == 'middle') {
+        dx = -this.element.offsetWidth / 2
+        origin[0] = '50%'
+      } else if (align == 'right') {
+        dx = -this.element.offsetWidth
+        origin[0] = '100%'
+      }
+      var valign = this.valign
+      if (valign == 'center' || valign == 'middle') {
+        dy = -this.element.offsetHeight / 2
+        origin[1] = '50%'
+      } else if (valign == 'bottom') {
+        dy = -this.element.offsetHeight
+        origin[1] = '100%'
+      }
+      this.element.style.webkitTransformOrigin =
+      this.element.style.MozTransformOrigin = origin.join(" ")
+      this.eWidth = this.element.offsetWidth / xs
+      this.eHeight = this.element.offsetHeight / ys
+    }
+    if (wkt && !this.noScaling) {
+      this.element.style.left = Math.floor(dx) + 'px'
+      this.element.style.top = Math.floor(dy) + 'px'
+    } else {
+      this.element.style.left = Math.floor(x+dx) + 'px'
+      this.element.style.top = Math.floor(y+dy) + 'px'
+    }
+    if (hidden)
+      this.element.style.visibility = 'visible'
+  }
+})
+
+
+/**
+  A Drawable is a CanvasNode with possible fill, stroke and clip.
+
+  It draws the path by calling #drawGeometry
+  */
+Drawable = Klass(CanvasNode, {
+  pickable : true,
+  //   'inside' // clip before drawing the stroke
+  // | 'above'  // draw stroke after the fill
+  // | 'below'  // draw stroke before the fill
+  strokeMode : 'above',
+
+  ABOVE : 'above', BELOW : 'below', INSIDE : 'inside',
+
+  initialize : function(config) {
+    CanvasNode.initialize.call(this, config)
+  },
+
+  /**
+    Draws the picking path for the Drawable.
+
+    The default version begins a new path and calls drawGeometry.
+
+    @param ctx Canvas drawing context
+    */
+  drawPickingPath : function(ctx) {
+    if (!this.drawGeometry) return
+    ctx.beginPath()
+    this.drawGeometry(ctx)
+  },
+
+  /**
+    Returns true if the point x,y is inside the path of a drawable node.
+
+    The x,y point is in user-space coordinates, meaning that e.g. the point
+    5,5 will always be inside the rectangle [0, 0, 10, 10], regardless of the
+    transform on the rectangle.
+
+    @param x X-coordinate of the point.
+    @param y Y-coordinate of the point.
+    @return Whether the point is inside the path of this node.
+    @type boolean
+    */
+  isPointInPath : function(x, y) {
+    return false
+  },
+
+  isVisible : function(ctx) {
+    var abb = this.getAxisAlignedBoundingBox()
+    if (!abb) return true
+    var x1 = abb[0], x2 = abb[0]+abb[2], y1 = abb[1], y2 = abb[1]+abb[3]
+    var w = this.root.width
+    var h = this.root.height
+    if (this.root.drawBoundingBoxes) {
+      ctx.save()
+        var bbox = this.getBoundingBox()
+        ctx.beginPath()
+        ctx.rect(bbox[0], bbox[1], bbox[2], bbox[3])
+        ctx.strokeStyle = 'green'
+        ctx.lineWidth = 1
+        ctx.stroke()
+      ctx.restore()
+      ctx.save()
+        CanvasSupport.setTransform(ctx, [1,0,0,1,0,0], this.currentMatrix)
+        ctx.beginPath()
+        ctx.rect(x1, y1, x2-x1, y2-y1)
+        ctx.strokeStyle = 'red'
+        ctx.lineWidth = 1.5
+        ctx.stroke()
+      ctx.restore()
+    }
+    var visible = !(x2 < 0 || x1 > w || y2 < 0 || y1 > h)
+    return visible
+  },
+
+  createSubtreePath : function(ctx, skipTransform) {
+    ctx.save()
+    if (!skipTransform) this.transform(ctx, true)
+    if (this.drawGeometry) this.drawGeometry(ctx)
+    for (var i=0; i<this.childNodes.length; i++)
+      this.childNodes[i].createSubtreePath(ctx)
+    ctx.restore()
+  },
+
+  /**
+    Draws the Drawable. Begins a path and calls this.drawGeometry, followed by
+    possibly filling, stroking and clipping the path, depending on whether
+    #fill, #stroke and #clip are set.
+
+    @param ctx Canvas drawing context
+    */
+  draw : function(ctx) {
+    if (!this.drawGeometry) return
+    // bbox checking is slower than just drawing in most cases.
+    // and caching the bboxes is hard to do correctly.
+    // plus, bboxes aren't hierarchical.
+    // so we are being glib :|
+    if (this.root.drawBoundingBoxes)
+      this.isVisible(ctx)
+    var ft = (ctx.fillStyle.transformList ||
+              ctx.fillStyle.matrix ||
+              ctx.fillStyle.scale != null ||
+              ctx.fillStyle.rotation ||
+              ctx.fillStyle.x ||
+              ctx.fillStyle.y )
+    var st = (ctx.strokeStyle.transformList ||
+              ctx.strokeStyle.matrix ||
+              ctx.strokeStyle.scale != null ||
+              ctx.strokeStyle.rotation ||
+              ctx.strokeStyle.x ||
+              ctx.strokeStyle.y )
+    ctx.beginPath()
+    this.drawGeometry(ctx)
+    if (ctx.strokeOn) {
+      switch (this.strokeMode) {
+        case this.ABOVE:
+          if (ctx.fillOn) this.doFill(ctx,ft)
+          this.doStroke(ctx, st)
+          break
+        case this.BELOW:
+          this.doStroke(ctx, st)
+          if (ctx.fillOn) this.doFill(ctx,ft)
+          break
+        case this.INSIDE:
+          if (ctx.fillOn) this.doFill(ctx,ft)
+          ctx.save()
+          var lw = ctx.lineWidth
+          ctx.setLineWidth(1)
+          this.doStroke(ctx, st)
+          ctx.setLineWidth(lw)
+          ctx.clip()
+          this.doStroke(ctx, st)
+          ctx.restore()
+          break
+      }
+    } else if (ctx.fillOn) {
+      this.doFill(ctx,ft)
+    }
+    this.drawMarkers(ctx)
+    if (this.clip) ctx.clip()
+  },
+
+  doFill : function(ctx, ft) {
+    if (ft || (this.getBoundingBox && ctx.fillStyle.units == this.OBJECTBOUNDINGBOX)) {
+      ctx.save()
+      if (this.getBoundingBox && ctx.fillStyle.units == this.OBJECTBOUNDINGBOX) {
+        var bb = this.getBoundingBox()
+        var sx = bb[2]
+        var sy = bb[3]
+        ctx.translate(bb[0],bb[1])
+        ctx.scale(sx,sy)
+      }
+      ctx.fillStyle.transform(ctx)
+    }
+    if (this.fillOpacity != null) {
+      var go = ctx.globalAlpha
+      ctx.setGlobalAlpha(go * this.fillOpacity)
+      ctx.fill()
+      ctx.globalAlpha = go
+    } else {
+      ctx.fill()
+    }
+    if (ft) ctx.restore()
+  },
+
+  doStroke : function(ctx, st) {
+    if (st || (this.getBoundingBox && ctx.strokeStyle.units == this.OBJECTBOUNDINGBOX)) {
+      ctx.save()
+      if (this.getBoundingBox && ctx.strokeStyle.units == this.OBJECTBOUNDINGBOX) {
+        var bb = this.getBoundingBox()
+        var sx = bb[2]
+        var sy = bb[3]
+        ctx.translate(bb[0],bb[1])
+        ctx.scale(sx,sy)
+      }
+      ctx.strokeStyle.needMatrixUpdate = true
+      ctx.strokeStyle.transform(ctx)
+      if (sx != null)
+        CanvasSupport.tScale(ctx.strokeStyle.currentMatrix, sx, sy)
+      var cm = ctx.strokeStyle.currentMatrix
+      // fix stroke width scale (non-uniform scales screw us up though)
+      var sw = Math.sqrt(Math.max(
+        cm[0]*cm[0] + cm[1]*cm[1],
+        cm[2]*cm[2] + cm[3]*cm[3]
+      ))
+      ctx.setLineWidth(((ctx.lineWidth == null) ? 1 : ctx.lineWidth) / sw)
+    }
+    if (this.strokeOpacity != null) {
+      var go = ctx.globalAlpha
+      ctx.setGlobalAlpha(go * this.strokeOpacity)
+      ctx.stroke()
+      ctx.globalAlpha = go
+    } else {
+      ctx.stroke()
+    }
+    if (st) ctx.restore()
+  },
+
+  drawMarkers : function(ctx) {
+    var sm = this.markerStart || this.marker
+    var em = this.markerEnd || this.marker
+    var mm = this.markerMid || this.marker
+    if (sm && this.getStartPoint) {
+      var pa = this.getStartPoint()
+      if (sm.orient != null && sm.orient != 'auto')
+        pa.angle = sm.orient
+      var scale = (sm.markerUnits == 'strokeWidth') ? ctx.lineWidth : 1
+      ctx.save()
+        ctx.translate(pa.point[0], pa.point[1])
+        ctx.scale(scale, scale)
+        ctx.rotate(pa.angle)
+        var mat = CanvasSupport.tRotate(
+          CanvasSupport.tScale(
+          CanvasSupport.tTranslate(
+            this.currentMatrix.slice(0),
+            pa.point[0], pa.point[1]
+          ), scale, scale), pa.angle)
+        sm.__copyMatrix(mat)
+        sm.handleDraw(ctx)
+      ctx.restore()
+    }
+    if (em && this.getEndPoint) {
+      var pa = this.getEndPoint()
+      if (em.orient != null && em.orient != 'auto')
+        pa.angle = em.orient
+      var scale = (em.markerUnits == 'strokeWidth') ? ctx.lineWidth : 1
+      ctx.save()
+        ctx.translate(pa.point[0], pa.point[1])
+        ctx.scale(scale, scale)
+        ctx.rotate(pa.angle)
+        var mat = CanvasSupport.tRotate(
+          CanvasSupport.tScale(
+          CanvasSupport.tTranslate(
+            this.currentMatrix.slice(0),
+            pa.point[0], pa.point[1]
+          ), scale, scale), pa.angle)
+        em.__copyMatrix(mat)
+        em.handleDraw(ctx)
+      ctx.restore()
+    }
+    if (mm && this.getMidPoints) {
+      var pas = this.getMidPoints()
+      var scale = (mm.markerUnits == 'strokeWidth') ? ctx.lineWidth : 1
+      for (var i=0; i<pas.length; i++) {
+        var pa = pas[i]
+        ctx.save()
+          ctx.translate(pa.point[0], pa.point[1])
+          ctx.scale(scale, scale)
+          if (mm.orient != null && mm.orient != 'auto')
+            pa.angle = em.orient
+          ctx.rotate(pa.angle)
+          var mat = CanvasSupport.tRotate(
+            CanvasSupport.tScale(
+            CanvasSupport.tTranslate(
+              this.currentMatrix.slice(0),
+              pa.point[0], pa.point[1]
+            ), scale, scale), pa.angle)
+          mm.__copyMatrix(mat)
+          mm.handleDraw(ctx)
+        ctx.restore()
+      }
+    }
+  },
+
+  getStartPoint : false,
+  getEndPoint : false,
+  getMidPoints : false,
+  getBoundingBox : false
+
+})
+
+
+/**
+  A Line is a line drawn from x1,y1 to x2,y2. Lines are stroked by default.
+
+  @param x1 X-coordinate of the line's first point.
+  @param y1 Y-coordinate of the line's first point.
+  @param x2 X-coordinate of the line's second point.
+  @param y2 Y-coordinate of the line's second point.
+  @param config Optional config hash.
+  */
+Line = Klass(Drawable, {
+  x1 : 0,
+  y1 : 0,
+  x2 : 0,
+  y2 : 0,
+  stroke : true,
+
+  initialize : function(x1,y1, x2,y2, config) {
+    this.x1 = x1
+    this.y1 = y1
+    this.x2 = x2
+    this.y2 = y2
+    Drawable.initialize.call(this, config)
+  },
+
+  drawGeometry : function(ctx) {
+    ctx.moveTo(this.x1, this.y1)
+    ctx.lineTo(this.x2, this.y2)
+  },
+
+  getStartPoint : function() {
+    return {
+      point: [this.x1, this.y1],
+      angle: Math.atan2(this.y2-this.y1, this.x2-this.x1)
+    }
+  },
+
+  getEndPoint : function() {
+    return {
+      point: [this.x2, this.y2],
+      angle: Math.atan2(this.y2-this.y1, this.x2-this.x1)
+    }
+  },
+
+  getBoundingBox : function() {
+    return [this.x1, this.y1, this.x2-this.x1, this.y2-this.y1]
+  },
+
+  getLength : function() {
+    return Curves.lineLength([this.x1, this.y1], [this.x2, this.y2])
+  }
+
+})
+
+
+
+/**
+  Circle is used for creating circular paths.
+
+  Uses context.arc(...).
+
+  Attributes:
+    cx, cy, radius, startAngle, endAngle, clockwise, closePath, includeCenter
+
+  @param radius Radius of the circle.
+  @param config Optional config hash.
+  */
+Circle = Klass(Drawable, {
+  cx : 0,
+  cy : 0,
+  radius : 10,
+  startAngle : 0,
+  endAngle : Math.PI * 2,
+  clockwise : false,
+  closePath : true,
+  includeCenter : false,
+
+  initialize : function(radius, config) {
+    if (radius != null) this.radius = radius
+    Drawable.initialize.call(this, config)
+  },
+
+  /**
+    Creates a circular path using ctx.arc(...).
+
+    @param ctx Canvas drawing context.
+    */
+  drawGeometry : function(ctx) {
+    if (this.radius == 0) return
+    if (this.includeCenter)
+      ctx.moveTo(this.cx, this.cy)
+    ctx.arc(this.cx, this.cy, this.radius, this.startAngle, this.endAngle, this.clockwise)
+    if (this.closePath) {
+      // firefox 2 is buggy without the endpoint
+      var x2 = Math.cos(this.endAngle)
+      var y2 = Math.sin(this.endAngle)
+      ctx.moveTo(this.cx + x2*this.radius, this.cy + y2 * this.radius)
+      ctx.closePath()
+    }
+  },
+
+  /**
+    Returns true if the point x,y is inside the radius of the circle.
+
+    The x,y point is in user-space coordinates, meaning that e.g. the point
+    5,0 will always be inside a circle with radius of 10 and center at origin,
+    regardless of the transform on the circle.
+
+    @param x X-coordinate of the point.
+    @param y Y-coordinate of the point.
+    @return Whether the point is inside the radius of this circle.
+    @type boolean
+    */
+  isPointInPath : function(x,y) {
+    x -= this.cx
+    y -= this.cy
+    return (x*x + y*y) <= (this.radius*this.radius)
+  },
+
+  getBoundingBox : function() {
+    return [this.cx-this.radius, this.cy-this.radius,
+            2*this.radius, 2*this.radius]
+  }
+})
+
+
+/**
+  Ellipse is a scaled circle. Except it isn't. Because that wouldn't work in
+  Opera.
+  */
+Ellipse = Klass(Circle, {
+  radiusX : 0,
+  radiusY : 0,
+
+  initialize : function(radiusX, radiusY, config) {
+    this.radiusX = radiusX
+    this.radiusY = radiusY
+    Circle.initialize.call(this, 1, config)
+  },
+
+  drawGeometry : function(ctx) {
+    if (this.radiusX == 0 || this.radiusY == 0) return
+    var k = 0.5522847498
+    var x = this.cx
+    var y = this.cy
+    var krx = k*this.radiusX
+    var kry = k*this.radiusY
+    ctx.moveTo(x+this.radiusX, y)
+    ctx.bezierCurveTo(x+this.radiusX, y-kry, x+krx, y-this.radiusY, x, y-this.radiusY)
+    ctx.bezierCurveTo(x-krx, y-this.radiusY, x-this.radiusX, y-kry, x-this.radiusX, y)
+    ctx.bezierCurveTo(x-this.radiusX, y+kry, x-krx, y+this.radiusY, x, y+this.radiusY)
+    ctx.bezierCurveTo(x+krx, y+this.radiusY, x+this.radiusX, y+kry, x+this.radiusX, y)
+  },
+
+  isPointInPath : function(x, y) {
+    // does this work?
+    x -= this.cx
+    y -= this.cy
+    x /= this.radiusX
+    y /= this.radiusY
+    return (x*x + y*y) <= 1
+  },
+
+  getBoundingBox : function() {
+    return [this.cx-this.radiusX, this.cy-this.radiusY,
+            this.radiusX*2, this.radiusY*2]
+  }
+})
+
+
+/**
+  A Spiral is a function graph drawn in polar coordinates from startAngle to
+  endAngle. And the source of all life energy, etc.
+  */
+Spiral = Klass(Drawable, {
+  cx : 0,
+  cy : 0,
+  startRadius : 0,
+  startAngle : 0,
+  endAngle : 0,
+
+  radiusFunction : function(a) {
+    return a
+  },
+
+  initialize : function(endAngle, config) {
+    this.endAngle = endAngle
+    Drawable.initialize.call(this, config)
+  },
+
+  drawGeometry : function(ctx) {
+    var x = this.cx
+    var y = this.cy
+    var a = this.startAngle
+    var r = this.startRadius + this.radiusFunction(a)
+    ctx.moveTo(x+Math.cos(a)*r, y-Math.sin(a)*r)
+    if (this.startAngle < this.endAngle) {
+      a += 0.1
+      r = this.startRadius + this.radiusFunction(a)
+      while (a < this.endAngle) {
+        ctx.lineTo(x+Math.cos(a)*r, y-Math.sin(a)*r)
+        a += 0.1
+        r = this.startRadius + this.radiusFunction(a)
+      }
+    } else {
+      a -= 0.1
+      r = this.startRadius + this.radiusFunction(a)
+      while (a > this.endAngle) {
+        ctx.lineTo(x+Math.cos(a)*r, y-Math.sin(a)*r)
+        a -= 0.1
+        r = this.startRadius + this.radiusFunction(a)
+      }
+    }
+    a = this.endAngle
+    r = this.startRadius + this.radiusFunction(a)
+    ctx.lineTo(x+Math.cos(a)*r, y-Math.sin(a)*r)
+  },
+
+  isPointInPath : function(x, y) {
+    return false
+  }
+})
+
+
+/**
+  Rectangle is used for creating rectangular paths.
+
+  Uses context.rect(...).
+
+  Attributes:
+    cx, cy, width, height, centered, rx, ry
+
+  If centered is set to true, centers the rectangle on the origin.
+  Otherwise the top-left corner of the rectangle is on the origin.
+
+  @param width Width of the rectangle.
+  @param height Height of the rectangle.
+  @param config Optional config hash.
+  */
+Rectangle = Klass(Drawable, {
+  cx : 0,
+  cy : 0,
+  x2 : 0,
+  y2 : 0,
+  width : 0,
+  height : 0,
+  rx : 0,
+  ry : 0,
+  centered : false,
+
+  initialize : function(width, height, config) {
+    if (width != null) {
+      this.width = width
+      this.height = width
+    }
+    if (height != null) this.height = height
+    Drawable.initialize.call(this, config)
+  },
+
+  /**
+    Creates a rectangular path using ctx.rect(...).
+
+    @param ctx Canvas drawing context.
+    */
+  drawGeometry : function(ctx) {
+    var x = this.cx
+    var y = this.cy
+    var w = (this.width || (this.x2 - x))
+    var h = (this.height || (this.y2 - y))
+    if (w == 0 || h == 0) return
+    if (this.centered) {
+      x -= 0.5*w
+      y -= 0.5*h
+    }
+    if (this.rx || this.ry) {
+      // hahaa, welcome to the undocumented rounded corners path
+      // using bezier curves approximating ellipse quadrants
+      var rx = Math.min(w * 0.5, this.rx || this.ry)
+      var ry = Math.min(h * 0.5, this.ry || rx)
+      var k = 0.5522847498
+      var krx = k*rx
+      var kry = k*ry
+      ctx.moveTo(x+rx, y)
+      ctx.lineTo(x-rx+w, y)
+      ctx.bezierCurveTo(x-rx+w + krx, y, x+w, y+ry-kry, x+w, y+ry)
+      ctx.lineTo(x+w, y+h-ry)
+      ctx.bezierCurveTo(x+w, y+h-ry+kry, x-rx+w+krx, y+h, x-rx+w, y+h)
+      ctx.lineTo(x+rx, y+h)
+      ctx.bezierCurveTo(x+rx-krx, y+h, x, y+h-ry+kry, x, y+h-ry)
+      ctx.lineTo(x, y+ry)
+      ctx.bezierCurveTo(x, y+ry-kry, x+rx-krx, y, x+rx, y)
+      ctx.closePath()
+    } else {
+      if (w < 0) x += w
+      if (h < 0) y += h
+      ctx.rect(x, y, Math.abs(w), Math.abs(h))
+    }
+  },
+
+  /**
+    Returns true if the point x,y is inside this rectangle.
+
+    The x,y point is in user-space coordinates, meaning that e.g. the point
+    5,5 will always be inside the rectangle [0, 0, 10, 10], regardless of the
+    transform on the rectangle.
+
+    @param x X-coordinate of the point.
+    @param y Y-coordinate of the point.
+    @return Whether the point is inside this rectangle.
+    @type boolean
+    */
+  isPointInPath : function(x,y) {
+    x -= this.cx
+    y -= this.cy
+    if (this.centered) {
+      x += this.width/2
+      y += this.height/2
+    }
+    return (x >= 0 && x <= this.width && y >= 0 && y <= this.height)
+  },
+
+  getBoundingBox : function() {
+    var x = this.cx
+    var y = this.cy
+    if (this.centered) {
+      x -= this.width/2
+      y -= this.height/2
+    }
+    return [x,y,this.width,this.height]
+  }
+})
+
+
+/**
+  Polygon is used for creating paths consisting of straight line
+  segments.
+
+  Attributes:
+    segments - The vertices of the polygon, e.g. [0,0, 1,1, 1,2, 0,1]
+    closePath - Whether to close the path, default is true.
+
+  @param segments The vertices of the polygon.
+  @param closePath Whether to close the path.
+  @param config Optional config hash.
+  */
+Polygon = Klass(Drawable, {
+  segments : [],
+  closePath : true,
+
+  initialize : function(segments, config) {
+    this.segments = segments
+    Drawable.initialize.call(this, config)
+  },
+
+  drawGeometry : function(ctx) {
+    if (!this.segments || this.segments.length < 2) return
+    var s = this.segments
+    ctx.moveTo(s[0], s[1])
+    for (var i=2; i<s.length; i+=2) {
+      ctx.lineTo(s[i], s[i+1])
+    }
+    if (this.closePath)
+      ctx.closePath()
+  },
+
+  isPointInPath : function(px,py) {
+    if (!this.segments || this.segments.length < 2) return false
+    var bbox = this.getBoundingBox()
+    return (px >= bbox[0] && px <= bbox[0]+bbox[2] &&
+            py >= bbox[1] && py <= bbox[1]+bbox[3])
+  },
+
+  getStartPoint : function() {
+    if (!this.segments || this.segments.length < 2)
+      return {point:[0,0], angle:0}
+    var a = 0
+    if (this.segments.length > 2) {
+      a = Curves.lineAngle(this.segments.slice(0,2), this.segments.slice(2,4))
+    }
+    return {point: this.segments.slice(0,2),
+            angle: a}
+  },
+
+  getEndPoint : function() {
+    if (!this.segments || this.segments.length < 2)
+      return {point:[0,0], angle:0}
+    var a = 0
+    if (this.segments.length > 2) {
+      a = Curves.lineAngle(this.segments.slice(-4,-2), this.segments.slice(-2))
+    }
+    return {point: this.segments.slice(-2),
+            angle: a}
+  },
+
+  getMidPoints : function() {
+    if (!this.segments || this.segments.length < 2)
+      return []
+    var segs = this.segments
+    var verts = []
+    for (var i=2; i<segs.length-2; i+=2) {
+      var a = segs.slice(i-2,i)
+      var b = segs.slice(i, i+2)
+      var c = segs.slice(i+2, i+4)
+      var t = 0.5 * (Curves.lineAngle(a,b) + Curves.lineAngle(b,c))
+      verts.push(
+        {point: b, angle: t}
+      )
+    }
+    return verts
+  },
+
+  getBoundingBox : function() {
+    var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
+    var s = this.segments
+    for (var i=0; i<s.length; i+=2) {
+      var x = s[i], y = s[i+1]
+      if (x < minX) minX = x
+      if (x > maxX) maxX = x
+      if (y < minY) minY = y
+      if (y > maxY) maxY = y
+    }
+    return [minX, minY, maxX-minX, maxY-minY]
+  }
+})
+
+
+
+/**
+  CatmullRomSpline draws a Catmull-Rom spline, with optional looping and
+  path closing. Handy for motion paths.
+
+  @param segments Control points for the spline, as [[x,y], [x,y], ...]
+  @param config Optional config hash.
+  */
+CatmullRomSpline = Klass(Drawable, {
+  segments : [],
+  loop : false,
+  closePath : false,
+
+  initialize : function(segments, config) {
+    this.segments = segments
+    Drawable.initialize.call(this, config)
+  },
+
+  drawGeometry : function(ctx) {
+    var x1 = this.currentMatrix[0]
+    var x2 = this.currentMatrix[1]
+    var y1 = this.currentMatrix[2]
+    var y2 = this.currentMatrix[3]
+    var xs = x1*x1 + x2*x2
+    var ys = y1*y1 + y2*y2
+    var s = Math.floor(Math.sqrt(Math.max(xs, ys)))
+    var cmp = this.compiled
+    if (!cmp || cmp.scale != s) {
+      cmp = this.compile(s)
+    }
+    for (var i=0; i<cmp.length; i++) {
+      var cmd = cmp[i]
+      ctx[cmd[0]].apply(ctx, cmd[1])
+    }
+    if (this.closePath)
+      ctx.closePath()
+  },
+
+  compile : function(scale) {
+    if (!scale) scale = 1
+    var compiled = []
+    if (this.segments && this.segments.length >= (this.loop ? 1 : 4)) {
+      var segs = this.segments
+      if (this.loop) {
+        segs = segs.slice(0)
+        segs.unshift(segs[segs.length-1])
+        segs.push(segs[1])
+        segs.push(segs[2])
+      }
+      // FIXME don't be stupid
+      var point_spacing = 1 / (15 * (scale+0.5))
+      var a,b,c,d,p,pp
+      compiled.push(['moveTo', segs[1].slice(0)])
+      p = segs[1]
+      for (var j=1; j<segs.length-2; j++) {
+        a = segs[j-1]
+        b = segs[j]
+        c = segs[j+1]
+        d = segs[j+2]
+        for (var i=0; i<1; i+=point_spacing) {
+          pp = p
+          p = Curves.catmullRomPoint(a,b,c,d,i)
+          compiled.push(['lineTo', p])
+        }
+      }
+      p = Curves.catmullRomPoint(a,b,c,d,1)
+      compiled.push(['lineTo', p])
+    }
+    compiled.scale = scale
+    this.compiled = compiled
+    return compiled
+  },
+
+  getBoundingBox : function() {
+    var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
+    var segments = (this.compiled ? this.compiled : this.compile())
+    for (var i=0; i<segments.length; i++) {
+      var seg = segments[i][1]
+      for (var j=0; j<seg.length; j+=2) {
+        var x = seg[j], y = seg[j+1]
+        if (x < minX) minX = x
+        if (x > maxX) maxX = x
+        if (y < minY) minY = y
+        if (y > maxY) maxY = y
+      }
+    }
+    return [minX, minY, maxX-minX, maxY-minY]
+  },
+
+  pointAt : function(t) {
+    if (!this.segments) return [0,0]
+    if (this.segments.length >= (this.loop ? 1 : 4)) {
+      var segs = this.segments
+      if (this.loop) {
+        segs = segs.slice(0)
+        segs.unshift(segs[segs.length-1])
+        segs.push(segs[1])
+        segs.push(segs[2])
+      }
+      // turn t into segment_index.segment_t
+      var rt = t * (segs.length - 3)
+      var j = Math.floor(rt)
+      var st = rt-j
+      var a = segs[j],
+          b = segs[j+1],
+          c = segs[j+2],
+          d = segs[j+3]
+      return Curves.catmullRomPoint(a,b,c,d,st)
+    } else {
+      return this.segments[0]
+    }
+  },
+
+  pointAngleAt : function(t) {
+    if (!this.segments) return {point: [0,0], angle: 0}
+    if (this.segments.length >= (this.loop ? 1 : 4)) {
+      var segs = this.segments
+      if (this.loop) {
+        segs = segs.slice(0)
+        segs.unshift(segs[segs.length-1])
+        segs.push(segs[1])
+        segs.push(segs[2])
+      }
+      // turn t into segment_index.segment_t
+      var rt = t * (segs.length - 3)
+      var j = Math.floor(rt)
+      var st = rt-j
+      var a = segs[j],
+          b = segs[j+1],
+          c = segs[j+2],
+          d = segs[j+3]
+      return Curves.catmullRomPointAngle(a,b,c,d,st)
+    } else {
+      return {point:this.segments[0] || [0,0], angle: 0}
+    }
+  }
+})
+
+
+/**
+  Path is used for creating custom paths.
+
+  Attributes: segments, closePath.
+
+    var path = new Path([
+      ['moveTo', [-50, -60]],
+      ['lineTo', [30, 50],
+      ['lineTo', [-50, 50]],
+      ['bezierCurveTo', [-50, 100, -50, 100, 0, 100]],
+      ['quadraticCurveTo', [0, 120, -20, 130]],
+      ['quadraticCurveTo', [0, 140, 0, 160]],
+      ['bezierCurveTo', [-10, 160, -20, 170, -30, 180]],
+      ['quadraticCurveTo', [10, 230, -50, 260]]
+    ])
+
+  The path segments are used as [methodName, arguments] on the canvas
+  drawing context, so the possible path segments are:
+
+    ['moveTo', [x, y]]
+    ['lineTo', [x, y]]
+    ['quadraticCurveTo', [control_point_x, control_point_y, x, y]]
+    ['bezierCurveTo', [cp1x, cp1y, cp2x, cp2y, x, y]]
+    ['arc', [x, y, radius, startAngle, endAngle, drawClockwise]]
+    ['arcTo', [x1, y1, x2, y2, radius]]
+    ['rect', [x, y, width, height]]
+
+  You can also pass an SVG path string as segments.
+
+    var path = new Path("M 100 100 L 300 100 L 200 300 z", {
+      stroke: true, strokeStyle: 'blue',
+      fill: true, fillStyle: 'red',
+      lineWidth: 3
+    })
+
+  @param segments The path segments.
+  @param config Optional config hash.
+  */
+Path = Klass(Drawable, {
+  segments : [],
+  closePath : false,
+
+  initialize : function(segments, config) {
+    this.segments = segments
+    Drawable.initialize.call(this, config)
+  },
+
+  /**
+    Creates a path on the given drawing context.
+
+    For each path segment, calls the context method named in the first element
+    of the segment with the rest of the segment elements as arguments.
+
+    SVG paths are parsed and executed.
+
+    Closes the path if closePath is true.
+
+    @param ctx Canvas drawing context.
+    */
+  drawGeometry : function(ctx) {
+    var segments = this.getSegments()
+    for (var i=0; i<segments.length; i++) {
+      var seg = segments[i]
+      ctx[seg[0]].apply(ctx, seg[1])
+    }
+    if (this.closePath)
+      ctx.closePath()
+  },
+
+  /**
+    Returns true if the point x,y is inside the path's bounding rectangle.
+
+    The x,y point is in user-space coordinates, meaning that e.g. the point
+    5,5 will always be inside the rectangle [0, 0, 10, 10], regardless of the
+    transform on the rectangle.
+
+    @param px X-coordinate of the point.
+    @param py Y-coordinate of the point.
+    @return Whether the point is inside the path's bounding rectangle.
+    @type boolean
+    */
+  isPointInPath : function(px,py) {
+    var bbox = this.getBoundingBox()
+    return (px >= bbox[0] && px <= bbox[0]+bbox[2] &&
+            py >= bbox[1] && py <= bbox[1]+bbox[3])
+  },
+
+  getBoundingBox : function() {
+    if (!(this.compiled && this.compiledBoundingBox)) {
+      var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
+      var segments = this.getSegments()
+      for (var i=0; i<segments.length; i++) {
+        var seg = segments[i][1]
+        for (var j=0; j<seg.length; j+=2) {
+          var x = seg[j], y = seg[j+1]
+          if (x < minX) minX = x
+          if (x > maxX) maxX = x
+          if (y < minY) minY = y
+          if (y > maxY) maxY = y
+        }
+      }
+      this.compiledBoundingBox = [minX, minY, maxX-minX, maxY-minY]
+    }
+    return this.compiledBoundingBox
+  },
+
+  getStartPoint : function() {
+    var segs = this.getSegments()
+    if (!segs || !segs[0]) return {point: [0,0], angle: 0}
+    var fs = segs[0]
+    var c = fs[1]
+    var point = [c[c.length-2], c[c.length-1]]
+    var ss = segs[1]
+    var angle = 0
+    if (ss) {
+      c2 = ss[1]
+      angle = Curves.lineAngle(point, [c2[c2.length-2], c2[c2.length-1]])
+    }
+    return {
+      point: point,
+      angle: angle
+    }
+  },
+
+  getEndPoint : function() {
+    var segs = this.getSegments()
+    if (!segs || !segs[0]) return {point: [0,0], angle: 0}
+    var fs = segs[segs.length-1]
+    var c = fs[1]
+    var point = [c[c.length-2], c[c.length-1]]
+    var ss = segs[segs.length-2]
+    var angle = 0
+    if (ss) {
+      c2 = ss[1]
+      angle = Curves.lineAngle([c2[c2.length-2], c2[c2.length-1]], point)
+    }
+    return {
+      point: point,
+      angle: angle
+    }
+  },
+
+  getMidPoints : function() {
+    var segs = this.getSegments()
+    if (this.vertices)
+      return this.vertices.slice(1,-1)
+    var verts = []
+    for (var i=1; i<segs.length-1; i++) {
+      var b = segs[i-1][1].slice(-2)
+      var c = segs[i][1].slice(0,2)
+      if (segs[i-1].length > 2) {
+        var a = segs[i-1][1].slice(-4,-2)
+        var t = 0.5 * (Curves.lineAngle(a,b) + Curves.lineAngle(b,c))
+      } else {
+        var t = Curves.lineAngle(b,c)
+      }
+      verts.push(
+        {point: b, angle: t}
+      )
+      var id = segs[i][2]
+      if (id != null) {
+        i++
+        while (segs[i] && segs[i][2] == id) i++
+        i--
+      }
+    }
+    return verts
+  },
+
+  getSegments : function() {
+    if (typeof(this.segments) == 'string') {
+      if (!this.compiled || this.segments != this.compiledSegments) {
+        this.compiled = this.compileSVGPath(this.segments)
+        this.compiledSegments = this.segments
+      }
+    } else if (!this.compiled) {
+      this.compiled = Object.clone(this.segments)
+    }
+    return this.compiled
+  },
+
+  /**
+    Compiles an SVG path string into an array of canvas context method calls.
+
+    Returns an array of [methodName, [arg1, arg2, ...]] method call arrays.
+    */
+  compileSVGPath : function(svgPath) {
+    var segs = svgPath.split(/(?=[a-z])/i)
+    var x = 0
+    var y = 0
+    var px,py
+    var pc
+    var commands = []
+    for (var i=0; i<segs.length; i++) {
+      var seg = segs[i]
+      var cmd = seg.match(/[a-z]/i)
+      if (!cmd) return [];
+      cmd = cmd[0];
+      var coords = seg.match(/[+-]?\d+(\.\d+(e\d+(\.\d+)?)?)?/gi)
+      if (coords) coords = coords.map(parseFloat)
+      switch(cmd) {
+        case 'M':
+          x = coords[0]
+          y = coords[1]
+          px = py = null
+          commands.push(['moveTo', [x, y]])
+          break
+        case 'm':
+          x += coords[0]
+          y += coords[1]
+          px = py = null
+          commands.push(['moveTo', [x, y]])
+          break
+
+        case 'L':
+          x = coords[0]
+          y = coords[1]
+          px = py = null
+          commands.push(['lineTo', [x, y]])
+          break
+        case 'l':
+          x += coords[0]
+          y += coords[1]
+          px = py = null
+          commands.push(['lineTo', [x, y]])
+          break
+        case 'H':
+          x = coords[0]
+          px = py = null
+          commands.push(['lineTo', [x, y]])
+          break
+        case 'h':
+          x += coords[0]
+          px = py = null
+          commands.push(['lineTo', [x,y]])
+          break
+        case 'V':
+          y = coords[0]
+          px = py = null
+          commands.push(['lineTo', [x,y]])
+          break
+        case 'v':
+          y += coords[0]
+          px = py = null
+          commands.push(['lineTo', [x,y]])
+          break
+
+        case 'C':
+          x = coords[4]
+          y = coords[5]
+          px = coords[2]
+          py = coords[3]
+          commands.push(['bezierCurveTo', coords])
+          break
+        case 'c':
+          commands.push(['bezierCurveTo',[
+            coords[0] + x, coords[1] + y,
+            coords[2] + x, coords[3] + y,
+            coords[4] + x, coords[5] + y
+          ]])
+          px = x + coords[2]
+          py = y + coords[3]
+          x += coords[4]
+          y += coords[5]
+          break
+
+        case 'S':
+          if (px == null || !pc.match(/[sc]/i)) {
+            px = x
+            py = y
+          }
+          commands.push(['bezierCurveTo',[
+            x-(px-x), y-(py-y),
+            coords[0], coords[1],
+            coords[2], coords[3]
+          ]])
+          px = coords[0]
+          py = coords[1]
+          x = coords[2]
+          y = coords[3]
+          break
+        case 's':
+          if (px == null || !pc.match(/[sc]/i)) {
+            px = x
+            py = y
+          }
+          commands.push(['bezierCurveTo',[
+            x-(px-x), y-(py-y),
+            x + coords[0], y + coords[1],
+            x + coords[2], y + coords[3]
+          ]])
+          px = x + coords[0]
+          py = y + coords[1]
+          x += coords[2]
+          y += coords[3]
+          break
+
+        case 'Q':
+          px = coords[0]
+          py = coords[1]
+          x = coords[2]
+          y = coords[3]
+          commands.push(['quadraticCurveTo', coords])
+          break
+        case 'q':
+          commands.push(['quadraticCurveTo',[
+            coords[0] + x, coords[1] + y,
+            coords[2] + x, coords[3] + y
+          ]])
+          px = x + coords[0]
+          py = y + coords[1]
+          x += coords[2]
+          y += coords[3]
+          break
+
+        case 'T':
+          if (px == null || !pc.match(/[qt]/i)) {
+            px = x
+            py = y
+          } else {
+            px = x-(px-x)
+            py = y-(py-y)
+          }
+          commands.push(['quadraticCurveTo',[
+            px, py,
+            coords[0], coords[1]
+          ]])
+          px = x-(px-x)
+          py = y-(py-y)
+          x = coords[0]
+          y = coords[1]
+          break
+        case 't':
+          if (px == null || !pc.match(/[qt]/i)) {
+            px = x
+            py = y
+          } else {
+            px = x-(px-x)
+            py = y-(py-y)
+          }
+          commands.push(['quadraticCurveTo',[
+            px, py,
+            x + coords[0], y + coords[1]
+          ]])
+          x += coords[0]
+          y += coords[1]
+          break
+
+        case 'A':
+          var arc_segs = this.solveArc(x,y, coords)
+          for (var l=0; l<arc_segs.length; l++) arc_segs[l][2] = i
+          commands.push.apply(commands, arc_segs)
+          x = coords[5]
+          y = coords[6]
+          break
+        case 'a':
+          coords[5] += x
+          coords[6] += y
+          var arc_segs = this.solveArc(x,y, coords)
+          for (var l=0; l<arc_segs.length; l++) arc_segs[l][2] = i
+          commands.push.apply(commands, arc_segs)
+          x = coords[5]
+          y = coords[6]
+          break
+
+        case 'Z':
+          commands.push(['closePath', []])
+          break
+        case 'z':
+          commands.push(['closePath', []])
+          break
+      }
+      pc = cmd
+    }
+    return commands
+  },
+
+  solveArc : function(x, y, coords) {
+    var rx = coords[0]
+    var ry = coords[1]
+    var rot = coords[2]
+    var large = coords[3]
+    var sweep = coords[4]
+    var ex = coords[5]
+    var ey = coords[6]
+    var segs = this.arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y)
+    var retval = []
+    for (var i=0; i<segs.length; i++) {
+      retval.push(['bezierCurveTo', this.segmentToBezier.apply(this, segs[i])])
+    }
+    return retval
+  },
+
+
+  // Copied from Inkscape svgtopdf, thanks!
+  arcToSegments : function(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
+    var th = rotateX * (Math.PI/180)
+    var sin_th = Math.sin(th)
+    var cos_th = Math.cos(th)
+    rx = Math.abs(rx)
+    ry = Math.abs(ry)
+    var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5
+    var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5
+    var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry)
+    if (pl > 1) {
+      pl = Math.sqrt(pl)
+      rx *= pl
+      ry *= pl
+    }
+
+    var a00 = cos_th / rx
+    var a01 = sin_th / rx
+    var a10 = (-sin_th) / ry
+    var a11 = (cos_th) / ry
+    var x0 = a00 * ox + a01 * oy
+    var y0 = a10 * ox + a11 * oy
+    var x1 = a00 * x + a01 * y
+    var y1 = a10 * x + a11 * y
+
+    var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0)
+    var sfactor_sq = 1 / d - 0.25
+    if (sfactor_sq < 0) sfactor_sq = 0
+    var sfactor = Math.sqrt(sfactor_sq)
+    if (sweep == large) sfactor = -sfactor
+    var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0)
+    var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0)
+
+    var th0 = Math.atan2(y0-yc, x0-xc)
+    var th1 = Math.atan2(y1-yc, x1-xc)
+
+    var th_arc = th1-th0
+    if (th_arc < 0 && sweep == 1){
+      th_arc += 2*Math.PI
+    } else if (th_arc > 0 && sweep == 0) {
+      th_arc -= 2 * Math.PI
+    }
+
+    var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)))
+    var result = []
+    for (var i=0; i<segments; i++) {
+      var th2 = th0 + i * th_arc / segments
+      var th3 = th0 + (i+1) * th_arc / segments
+      result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th]
+    }
+
+    return result
+  },
+
+  segmentToBezier : function(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
+    var a00 = cos_th * rx
+    var a01 = -sin_th * ry
+    var a10 = sin_th * rx
+    var a11 = cos_th * ry
+
+    var th_half = 0.5 * (th1 - th0)
+    var t = (8/3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half)
+    var x1 = cx + Math.cos(th0) - t * Math.sin(th0)
+    var y1 = cy + Math.sin(th0) + t * Math.cos(th0)
+    var x3 = cx + Math.cos(th1)
+    var y3 = cy + Math.sin(th1)
+    var x2 = x3 + t * Math.sin(th1)
+    var y2 = y3 - t * Math.cos(th1)
+    return [
+      a00 * x1 + a01 * y1,      a10 * x1 + a11 * y1,
+      a00 * x2 + a01 * y2,      a10 * x2 + a11 * y2,
+      a00 * x3 + a01 * y3,      a10 * x3 + a11 * y3
+    ]
+  },
+
+  getLength : function() {
+    var segs = this.getSegments()
+    if (segs.arcLength == null) {
+      segs.arcLength = 0
+      var x=0, y=0
+      for (var i=0; i<segs.length; i++) {
+        var args = segs[i][1]
+        if (args.length < 2) continue
+        switch(segs[i][0]) {
+          case 'bezierCurveTo':
+            segs[i][3] = Curves.cubicLength(
+              [x, y], [args[0], args[1]], [args[2], args[3]], [args[4], args[5]])
+            break
+          case 'quadraticCurveTo':
+            segs[i][3] = Curves.quadraticLength(
+              [x, y], [args[0], args[1]], [args[2], args[3]])
+            break
+          case 'lineTo':
+            segs[i][3] = Curves.lineLength(
+              [x, y], [args[0], args[1]])
+            break
+        }
+        if (segs[i][3])
+          segs.arcLength += segs[i][3]
+        x = args[args.length-2]
+        y = args[args.length-1]
+      }
+    }
+    return segs.arcLength
+  },
+
+  pointAngleAt : function(t, config) {
+    var segments = []
+    var segs = this.getSegments()
+    var length = this.getLength()
+    var x = 0, y = 0
+    for (var i=0; i<segs.length; i++) {
+      var seg = segs[i]
+      if (seg[1].length < 2) continue
+      if (seg[0] != 'moveTo') {
+        segments.push([x, y, seg])
+      }
+      x = seg[1][seg[1].length-2]
+      y = seg[1][seg[1].length-1]
+    }
+    if (segments.length < 1)
+      return {point: [x, y], angle: 0 }
+    if (t >= 1) {
+      var rt = 1
+      var seg = segments[segments.length-1]
+    } else if (config && config.discrete) {
+      var idx = Math.floor(t * segments.length)
+      var seg = segments[idx]
+      var rt = 0
+    } else if (config && config.linear) {
+      var idx = t * segments.length
+      var rt = idx - Math.floor(idx)
+      var seg = segments[Math.floor(idx)]
+    } else {
+      var len = t * length
+      var rlen = 0, idx, rt
+      for (var i=0; i<segments.length; i++) {
+        if (rlen + segments[i][2][3] > len) {
+          idx = i
+          rt = (len - rlen) / segments[i][2][3]
+          break
+        }
+        rlen += segments[i][2][3]
+      }
+      var seg = segments[idx]
+    }
+    var angle = 0
+    var cmd = seg[2][0]
+    var args = seg[2][1]
+    switch (cmd) {
+      case 'bezierCurveTo':
+        return Curves.cubicLengthPointAngle([seg[0], seg[1]], [args[0], args[1]], [args[2], args[3]], [args[4], args[5]], rt)
+        break
+      case 'quadraticCurveTo':
+        return Curves.quadraticLengthPointAngle([seg[0], seg[1]], [args[0], args[1]], [args[2], args[3]], rt)
+        break
+      case 'lineTo':
+        x = Curves.linearValue(seg[0], args[0], rt)
+        y = Curves.linearValue(seg[1], args[1], rt)
+        angle = Curves.lineAngle([seg[0], seg[1]], [args[0], args[1]], rt)
+        break
+    }
+    return {point: [x, y], angle: angle }
+  }
+
+})
+
+
+/**
+  ImageNode is used for drawing images. Creates a rectangular path around
+  the drawn image.
+
+  Attributes:
+
+    centered - If true, image center is at the origin.
+               Otherwise image top-left is at the origin.
+    usePattern - Use a pattern fill for drawing the image (instead of
+                 drawImage.) Doesn't do sub-image drawing, and Safari doesn't
+                 like scaled image patterns.
+    sX, sY, sWidth, sHeight - Area of image to draw. Optional.
+    dX, dY - Coordinates where to draw the image. Default is 0, 0.
+    dWidth, dHeight - Size of the drawn image. Optional.
+
+  Example:
+
+    var img = new Image()
+    img.src = 'foo.jpg'
+    var imageGeo = new ImageNode(img)
+
+  @param image Image to draw.
+  @param config Optional config hash.
+  */
+ImageNode = Klass(Drawable, {
+  centered : false,
+  usePattern : false,
+
+  sX : 0,
+  sY : 0,
+  sWidth : null,
+  sHeight : null,
+
+  dX : 0,
+  dY : 0,
+  dWidth : null,
+  dHeight : null,
+
+  initialize : function(image, config) {
+    this.image = image
+    Drawable.initialize.call(this, config)
+  },
+
+  /**
+    Draws the image on the given drawing context.
+
+    Creates a rectangular path around the drawn image (for possible stroke
+    and/or fill.)
+
+    @param ctx Canvas drawing context.
+    */
+  drawGeometry : function(ctx) {
+    if (Object.isImageLoaded(this.image)) {
+      var w = this.dWidth == null ? this.image.width : this.dWidth
+      var h = this.dHeight == null ? this.image.height : this.dHeight
+      var x = this.dX + (this.centered ? -w * 0.5 : 0)
+      var y = this.dY + (this.centered ? -h * 0.5 : 0)
+      if (this.dWidth != null) {
+        if (this.sWidth != null) {
+          ctx.drawImage(this.image,
+            this.sX, this.sY, this.sWidth, this.sHeight,
+            x, y, w, h)
+        } else {
+          ctx.drawImage(this.image, x, y, w, h)
+        }
+      } else {
+        w = this.image.width
+        h = this.image.height
+        if (this.usePattern) {
+          if (!this.imagePattern)
+            this.imagePattern = new Pattern(this.image, 'repeat')
+          var fs = this.imagePattern.compiled
+          if (!fs)
+            fs = this.imagePattern.compile(ctx)
+          ctx.save()
+          ctx.beginPath()
+          ctx.rect(x, y, w, h)
+          ctx.setFillStyle(fs)
+          ctx.fill()
+          ctx.restore()
+          ctx.beginPath()
+        } else {
+          ctx.drawImage(this.image, x, y)
+        }
+      }
+    } else {
+      var w = this.dWidth
+      var h = this.dHeight
+      if (!( w && h )) return
+      var x = this.dX + (this.centered ? -w * 0.5 : 0)
+      var y = this.dY + (this.centered ? -h * 0.5 : 0)
+    }
+    ctx.rect(x, y, w, h)
+  },
+
+  /**
+    Creates a bounding rectangle path for the image on the given drawing
+    context.
+
+    @param ctx Canvas drawing context.
+    */
+  drawPickingPath : function(ctx) {
+    var x = this.dX + (this.centered ? -this.image.width * 0.5 : 0)
+    var y = this.dY + (this.centered ? -this.image.height * 0.5 : 0)
+    var w = this.dWidth
+    var h = this.dHeight
+    if (this.dWidth == null) {
+      w = this.image.width
+      h = this.image.height
+    }
+    ctx.rect(x, y, w, h)
+  },
+
+  /**
+    Returns true if the point x,y is inside the image rectangle.
+
+    The x,y point is in user-space coordinates, meaning that e.g. the point
+    5,5 will always be inside the rectangle [0, 0, 10, 10], regardless of the
+    transform on the rectangle.
+
+    @param x X-coordinate of the point.
+    @param y Y-coordinate of the point.
+    @return Whether the point is inside the image rectangle.
+    @type boolean
+    */
+  isPointInPath : function(x,y) {
+    x -= this.dX
+    y -= this.dY
+    if (this.centered) {
+      x += this.image.width * 0.5
+      y += this.image.height * 0.5
+    }
+    var w = this.dWidth
+    var h = this.dHeight
+    if (this.dWidth == null) {
+      w = this.image.width
+      h = this.image.height
+    }
+    return ((x >= 0) && (x <= w) && (y >= 0) && (y <= h))
+  },
+
+  getBoundingBox : function() {
+    x = this.dX
+    y = this.dY
+    if (this.centered) {
+      x -= this.image.width * 0.5
+      y -= this.image.height * 0.5
+    }
+    var w = this.dWidth
+    var h = this.dHeight
+    if (this.dWidth == null) {
+      w = this.image.width
+      h = this.image.height
+    }
+    return [x, y, w, h]
+  }
+})
+
+ImageNode.load = function(src) {
+  var img = new Image();
+  img.src = src;
+  var imgn = new ImageNode(img);
+  return imgn;
+}
+
+
+/**
+  TextNode is used for drawing text on a canvas.
+
+  Attributes:
+
+    text - The text string to draw.
+    align - Horizontal alignment for the text.
+            'left', 'right', 'center', 'start' or 'end'
+    baseline - Baseline used for the text.
+               'top', 'hanging', 'middle', 'alphabetic', 'ideographic' or 'bottom'
+    asPath - If true, creates a text path instead of drawing the text.
+    pathGeometry - A geometry object the path of which the text follows.
+
+  Example:
+
+    var text = new TextGeometry('The cake is a lie.')
+
+  @param text The text string to draw.
+  @param config Optional config hash.
+  */
+TextNode = Klass(Drawable, {
+  text : 'Text',
+  align : 'start', // 'left' | 'right' | 'center' | 'start' | 'end'
+  baseline : 'alphabetic', // 'top' | 'hanging' | 'middle' | 'alphabetic' |
+                           // 'ideographic' | 'bottom'
+  accuratePicking : false,
+  asPath : false,
+  pathGeometry : null,
+  maxWidth : null,
+  width : 0,
+  height : 20,
+  cx : 0,
+  cy : 0,
+
+  __drawMethodName : 'draw' + CanvasSupport.getTextBackend(),
+  __pickingMethodName : 'drawPickingPath' + CanvasSupport.getTextBackend(),
+
+  initialize : function(text, config) {
+    this.lastText = this.text
+    this.text = text
+    Drawable.initialize.call(this, config)
+  },
+
+  drawGeometry : function(ctx) {
+    this.drawUsing(ctx, this.__drawMethodName)
+  },
+
+  drawPickingPath : function(ctx) {
+    this.drawUsing(ctx, this.__pickingMethodName)
+  },
+
+  drawUsing : function(ctx, methodName) {
+    if (!this.text || this.text.length == 0)
+      return
+    if (this.lastText != this.text || this.lastStyle != ctx.font) {
+      this.dimensions = this.measureText(ctx)
+      this.lastText = this.text
+      this.lastStyle = ctx.font
+    }
+    if (this[methodName])
+      this[methodName](ctx)
+  },
+
+  measureText : function(ctx) {
+    var mn = 'measureText' + CanvasSupport.getTextBackend().capitalize()
+    if (this[mn]) {
+      return this[mn](ctx)
+    } else {
+      return {width: 0, height: 0}
+    }
+  },
+
+  computeXForAlign : function() {
+    if (this.align == 'left') // most hit branch
+      return 0
+    else if (this.align == 'right')
+      return -this.dimensions.width
+    else if (this.align == 'center')
+      return  -this.dimensions.width * 0.5
+  },
+
+  measureTextHTML5 : function(ctx) {
+    // FIXME measureText is retarded
+    return {width: ctx.measureText(this.text).width, height: 20}
+  },
+
+  drawHTML5 : function(ctx) {
+    ctx.fillText(this.text, this.cx, this.cy, this.maxWidth)
+  },
+
+  drawPickingPathHTML5 : function(ctx) {
+    var ascender = 15 // this.dimensions.ascender
+    var ry = this.cy - ascender
+    ctx.rect(this.cx, ry, this.dimensions.width, this.dimensions.height)
+  },
+
+  measureTextMozText : function(ctx) {
+    return {width: ctx.mozMeasureText(this.text), height: 20}
+  },
+
+  drawMozText : function(ctx) {
+    var x = this.cx + this.computeXForAlign()
+    var y = this.cy + 0
+    if (this.pathGeometry) {
+      this.pathGeometry.draw(ctx)
+      ctx.mozDrawTextAlongPath(this.text, this.path)
+    } else {
+      ctx.save()
+      ctx.translate(x,y)
+      if (this.asPath) {
+        ctx.mozPathText(this.text)
+      } else {
+        ctx.mozDrawText(this.text)
+      }
+      ctx.restore()
+    }
+  },
+
+  drawPickingPathMozText : function(ctx) {
+    var x = this.cx + this.computeXForAlign()
+    var y = this.cy + 0
+    if (this.pathGeometry) { // FIXME how to draw a text path along path?
+        this.pathGeometry.draw(ctx)
+        // ctx.mozDrawTextAlongPath(this.text, this.path)
+    } else if (!this.accuratePicking) {
+      var ascender = 15 // this.dimensions.ascender
+      var ry = y - ascender
+      ctx.rect(x, ry, this.dimensions.width, this.dimensions.height)
+    } else {
+      ctx.save()
+      ctx.translate(x,y)
+      ctx.mozPathText(this.text)
+      ctx.restore()
+    }
+  },
+
+  drawDrawString : function(ctx) {
+    var x = this.cx + this.computeXForAlign()
+    var y = this.cy + 0
+    ctx.drawString(x,y, this.text)
+  },
+
+  measureTextPerfectWorld : function(ctx) {
+    return ctx.measureText(this.text)
+  },
+
+  drawPerfectWorld : function(ctx) {
+    if (this.pathGeometry) {
+      this.pathGeometry.draw(ctx)
+      if (this.asPath)
+        ctx.pathTextAlongPath(this.text)
+      else
+        ctx.drawTextAlongPath(this.text)
+    } else if (this.asPath) {
+      ctx.pathText(this.text)
+    } else {
+      ctx.drawText(this.text)
+    }
+  },
+
+  drawPickingPathPerfectWorld : function(ctx) {
+    if (this.accuratePicking) {
+      if (this.pathGeometry) {
+        ctx.pathTextAlongPath(this.text)
+      } else {
+        ctx.pathText(this.text)
+      }
+    } else { // creates a path of text bounding box
+      if (this.pathGeometry) {
+        ctx.textRectAlongPath(this.text)
+      } else {
+        ctx.textRect(this.text)
+      }
+    }
+  }
+})
+
+
+/**
+  Gradient is a linear or radial color gradient that can be used as a
+  strokeStyle or fillStyle.
+
+  Attributes:
+
+    type - Type of the gradient. 'linear' or 'radial'
+    startX, startY - Coordinates for the starting point of the gradient.
+                     Center of the starting circle of a radial gradient.
+                     Default is 0, 0.
+    endX, endY - Coordinates for the ending point of the gradient.
+                 Center of the ending circle of a radial gradient.
+                 Default is 0, 0.
+    startRadius - The radius of the starting circle of a radial gradient.
+                  Default is 0.
+    endRadius - The radius of the ending circle of a radial gradient.
+                Default is 100.
+    colorStops - The color stops for the gradient. The format for the color
+                 stops is: [[position_1, color_1], [position_2, color_2], ...].
+                 The possible color formats are: 'red', '#000', '#000000',
+                 'rgba(0,0,0, 0.2)', [0,0,0] and [0,0,0, 0.2].
+                 Default color stops are [[0, '#000000'], [1, '#FFFFFF']].
+
+  Example:
+
+    var g = new Gradient({
+      type : 'radial',
+      endRadius : 40,
+      colorStops : [
+        [0, '#000'],
+        [0.2, '#ffffff'],
+        [0.5, [255, 0, 0]],
+        [0.8, [0, 255, 255, 0.5]],
+        [1.0, 'rgba(255, 0, 255, 0.8)']
+      ]
+    })
+
+  @param config Optional config hash.
+  */
+Gradient = Klass({
+  type : 'linear',
+  isPattern : true,
+  startX : 0,
+  startY : 0,
+  endX : 1,
+  endY : 0,
+  startRadius : 0,
+  endRadius : 1,
+  colorStops : [],
+
+  initialize : function(config) {
+    this.colorStops = [[0, '#000000'], [1, '#FFFFFF']]
+    if (config) Object.extend(this, config)
+  },
+
+  /**
+    Compiles the gradient using the given drawing context.
+    Returns a gradient object that can be used as drawing context
+    fill/strokeStyle.
+
+    @param ctx Drawing context to compile pattern on.
+    @return Gradient object.
+    */
+  compile : function(ctx) {
+    if (this.type == 'linear') {
+      var go = ctx.createLinearGradient(
+                    this.startX, this.startY,
+                    this.endX, this.endY)
+    } else {
+      var go = ctx.createRadialGradient(
+                    this.startX, this.startY, this.startRadius,
+                    this.endX, this.endY, this.endRadius)
+    }
+    for(var i=0; i<this.colorStops.length; i++) {
+      var cs = this.colorStops[i]
+      if (typeof(cs[1]) == 'string') {
+        go.addColorStop(cs[0], cs[1])
+      } else {
+        var ca = cs[1]
+        var a = (ca.length == 3) ? 1 : ca[3]
+        var g = 'rgba('+ca.slice(0,3).map(Math.round).join(",")+', '+a+')'
+        go.addColorStop(cs[0], g)
+      }
+    }
+    Object.extend(go, Transformable.prototype)
+    go.transformList = this.transformList
+    go.scale = this.scale
+    go.x = this.x
+    go.y = this.y
+    go.matrix = this.matrix
+    go.rotation = this.rotation
+    go.units = this.units
+    if (!go.isMockObject)
+      this.compiled = go
+    return go
+  }
+})
+
+
+/**
+  Pattern is a possibly repeating image that can be used as a strokeStyle or
+  fillStyle.
+
+    var image = new Image()
+    image.src = 'foo.jpg'
+    var pattern = new Pattern(image, 'no-repeat')
+    var rect = new Rectangle(200, 200, {fill: true, fillStyle: pattern})
+
+  @param image The image object for the pattern. IMG and CANVAS elements, and
+               Image objects all work.
+  @param repeat The repeat mode of the pattern. One of 'repeat', 'repeat-y',
+                'repeat-x' and 'no-repeat'. The default is 'repeat'.
+  */
+Pattern = Klass({
+  isPattern : true,
+  repeat: 'repeat',
+
+  initialize : function(image, repeat) {
+    this.image = image
+    if (repeat)
+      this.repeat = repeat
+  },
+
+  /**
+    Compiles the pattern using the given drawing context.
+    Returns a pattern object that can be used as drawing context
+    fill/strokeStyle.
+
+    @param ctx Drawing context to compile pattern on.
+    @return Pattern object.
+    */
+  compile : function(ctx) {
+    var pat = ctx.createPattern(this.image, this.repeat)
+    Object.extend(pat, Transformable.prototype)
+    pat.transformList = this.transformList
+    pat.scale = this.scale
+    pat.x = this.x
+    pat.y = this.y
+    pat.matrix = this.matrix
+    pat.rotation = this.rotation
+    pat.units = this.units
+    if (!pat.isMockObject)
+      this.compiled = pat
+    return pat
+  }
+})
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+  SVG parser for simple documents. Converts SVG DOM to CAKE scenegraph.
+  Emphasis on graphical images, not on the "HTML-killer" features of SVG.
+
+  var svgNode = SVGParser.parse(
+    svgRootElement, filename, containerWidth, containerHeight, fontSize
+  )
+
+  Features:
+    * <svg> width and height, viewBox clipping.
+    * Clipping (objectBoundingBox clipping too)
+    * Paths, rectangles, ellipses, circles, lines, polylines and polygons
+    * Simple untransformed text using HTML
+    * Nested transforms
+    * Transform lists (transform="rotate(30) translate(2,2) scale(4)")
+    * Gradient and pattern transforms
+    * Strokes with miter, joins and caps
+    * Flat fills and gradient fills, ditto for strokes
+    * Parsing simple stylesheets (tag, class or id)
+    * Images
+    * Non-pixel units (cm, mm, in, pt, pc, em, ex, %)
+    * <use>-tags
+    * preserveAspectRatio
+    * Dynamic gradient sizes (objectBoundingBox, etc.)
+    * Markers (though buggy)
+
+  Some of the several missing features:
+    * Masks
+    * Patterns
+    * viewBox clipping for elements other than <marker> and <svg>
+    * Text styling
+    * tspan, tref, textPath, many things text
+    * Fancy style rules (tag .class + #foo > bob#alice { ... })
+    * Filters
+    * Animation
+    * Dashed strokes
+  */
+SVGParser = {
+  /**
+    Loads an SVG document using XMLHttpRequest and calls the given onSuccess
+    callback with the parsed document. If loading fails, calls onFailure
+    instead if one is given. When loading and an onLoading callback is given,
+    calls onLoading every time xhr.readyState is 3.
+
+    The callbacks will be called with the following parameters:
+
+    onSuccess(svgNode, xmlHttpRequest,
+      filename, config)
+
+    onFailure(xmlHttpRequest, possibleException,
+      filename, config)
+
+    onLoading(xmlHttpRequest,
+      filename, config)
+
+    Config hash parameters:
+      filename: Filename for the SVG document. Used for parsing image paths.
+      width: Width of the bounding box to fit the SVG in.
+      height: Height of the bounding box to fit the SVG in.
+      fontSize: Default font size for the SVG document.
+      onSuccess: Function to call on successful load. Required.
+      onFailure: Function to call on failed load.
+      onLoading: Function to call while loading.
+
+    @param config The config hash.
+    @param filename The URL of the SVG document to load. Must conform to SOP.
+    */
+  load : function(filename, config) {
+    if (!config.onSuccess) throw("Need to provide an onSuccess function.")
+    if (!config.filename)
+      config.filename = filename
+    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() :
+                                      new ActiveXObject("MSXML2.XMLHTTP.3.0")
+    xhr.open('GET', filename, true)
+    xhr.overrideMimeType('text/xml')
+    var failureFired = false
+    xhr.onreadystatechange = function() {
+      if (xhr.readyState == 4) {
+        if (xhr.status == 200 || xhr.status == 0) {
+          try {
+            var svg = xhr.responseXML
+            var svgNode = SVGParser.parse(svg, config)
+            svgNode.svgRootElement = svg
+          } catch(e) {
+            if (config.onFailure)
+              config.onFailure(xhr, e, filename, config)
+            return
+          }
+          config.onSuccess(svgNode, xhr, filename, config)
+        } else {
+          if (config.onFailure && !failureFired)
+            config.onFailure(xhr, null, filename, config)
+        }
+      } else if (xhr.readyState == 3) {
+        if (config.onLoading)
+          config.onLoading(xhr, filename, config)
+      }
+    }
+    try {
+      xhr.send(null)
+    } catch(e) {
+      if (config.onFailure) {
+        config.onFailure(xhr, e, filename, config)
+        failureFired = true
+      }
+      xhr.abort()
+    }
+  },
+
+  /**
+    Parses an SVG DOM into CAKE scenegraph.
+
+    Config hash parameters:
+      filename: Filename for the SVG document. Used for parsing image paths.
+      width: Width of the bounding box to fit the SVG in.
+      height: Height of the bounding box to fit the SVG in.
+      fontSize: Default font size for the SVG document.
+      currentColor: HTML text color of the containing element.
+
+    @param svgRootElement The root element of the SVG DOM.
+    @param config The config hash.
+    @returns The root CanvasNode of the scenegraph created from the SVG document.
+    @type CanvasNode
+    */
+  parse : function(svgRootElement, config) {
+    var n = new CanvasNode()
+    var w = config.width, h = config.height, fs = config.fontSize
+    n.innerWidth = w || window.innerWidth
+    n.innerHeight = h || window.innerHeight
+    n.innerSize = Math.sqrt(w*w + h*h) / Math.sqrt(2)
+    n.fontSize = fs || 12
+    n.filename = config.filename || '.'
+    var defs = {}
+    var style = { ids : {}, classes : {}, tags : {} }
+    n.color = config.currentColor || 'black'
+    this.parseChildren(svgRootElement, n, defs, style)
+    n.defs = defs
+    n.style = style
+    return n
+  },
+
+  parsePreserveAspectRatio : function(aspect, w, h, vpw, vph) {
+    var aspect = aspect || ""
+    var aspa = aspect.split(/\s+/)
+    var defer = (aspa[0] == 'defer')
+    if (defer) aspa.shift()
+    var align = (aspa[0] || 'xMidYMid')
+    var meet = (aspa[1] || 'meet')
+    var wf = w / vpw
+    var hf = h / vph
+    var xywh = {x:0, y:0, w:wf, h:hf}
+    if (align == 'none') return xywh
+    xywh.w = xywh.h = (meet == 'meet' ? Math.min : Math.max)(wf, hf)
+    var xa = align.slice(1, 4).toLowerCase()
+    var ya = align.slice(5, 8).toLowerCase()
+    var xf = (this.SVGAlignMap[xa] || 0)
+    var yf = (this.SVGAlignMap[ya] || 0)
+    xywh.x = xf * (w-vpw*xywh.w)
+    xywh.y = yf * (h-vph*xywh.h)
+    return xywh
+  },
+
+  SVGAlignMap : {
+    min : 0,
+    mid : 0.5,
+    max : 1
+  },
+
+  SVGTagMapping : {
+    svg : function(c, cn, defs, style) {
+      var p = new Rectangle()
+      p.width = 0
+      p.height = 0
+      p.doFill = function(){}
+      p.doStroke = function(){}
+      p.drawMarkers = function(){}
+      p.fill = 'black'
+      p.stroke = 'none'
+      var vb = c.getAttribute('viewBox')
+      var w = c.getAttribute('width')
+      var h = c.getAttribute('height')
+      if (!w) w = h
+      else if (!h) h = w
+      if (w) {
+        var wpx = this.parseUnit(w, cn, 'x')
+        var hpx = this.parseUnit(h, cn, 'y')
+      }
+      if (vb) {
+        xywh = vb.match(/[-+]?\d+/g).map(parseFloat)
+        p.cx = xywh[0]
+        p.cy = xywh[1]
+        p.width = xywh[2]
+        p.height = xywh[3]
+        var iw = cn.innerWidth = p.width
+        var ih = cn.innerHeight = p.height
+        cn.innerSize = Math.sqrt(iw*iw + ih*ih) / Math.sqrt(2)
+        if (c.getAttribute('overflow') != 'visible')
+          p.clip = true
+      }
+      if (w) {
+        if (vb) { // nuts, let's parse the alignment :|
+          var aspect = c.getAttribute('preserveAspectRatio')
+          var align = this.parsePreserveAspectRatio(aspect,
+            wpx, hpx,
+            p.width, p.height)
+          p.cx -= align.x / align.w
+          p.cy -= align.y / align.h
+          p.width = wpx / align.w
+          p.height = hpx / align.h
+          p.x += align.x
+          p.y += align.y
+          p.scale = [align.w, align.h]
+        }
+        // wrong place!
+        cn.docWidth = wpx
+        cn.docHeight = hpx
+      }
+      return p
+    },
+
+    marker : function(c, cn) {
+      var p = new CanvasNode()
+      p.draw = function(ctx) {
+        if (this.overflow != 'hidden' && this.viewBox) return
+        ctx.beginPath()
+        ctx.rect(
+          this.viewBox[0],this.viewBox[1],
+          this.viewBox[2],this.viewBox[3]
+        )
+        ctx.clip()
+      }
+      var x = -this.parseUnit(c.getAttribute('refX'), cn, 'x') || 0
+      var y = -this.parseUnit(c.getAttribute('refY'), cn, 'y') || 0
+      p.transformList = [['translate', [x,y]]]
+      p.markerUnits = c.getAttribute('markerUnits') || 'strokeWidth'
+      p.markerWidth = this.parseUnit(c.getAttribute('markerWidth'), cn, 'x') || 3
+      p.markerHeight = this.parseUnit(
+                          c.getAttribute('markerHeight'), cn, 'y') ||
+                          3
+      p.overflow = c.getAttribute('overflow') || 'hidden'
+      p.viewBox = c.getAttribute('viewBox')
+      p.orient = c.getAttribute('orient') || 0
+      if (p.orient && p.orient != 'auto')
+        p.orient = parseFloat(p.orient)*SVGMapping.DEG_TO_RAD_FACTOR
+      if (p.viewBox) {
+        p.viewBox = p.viewBox.strip().split(/[\s,]+/g).map(parseFloat)
+        var vbw = p.viewBox[2] - p.viewBox[0]
+        var vbh = p.viewBox[3] - p.viewBox[1]
+        if (p.markerWidth) {
+          var sx = sy = Math.min(
+            p.markerWidth / vbw,
+            p.markerHeight / vbh
+          )
+          p.transformList.unshift(['scale', [sx,sy]])
+        }
+      }
+      return p
+    },
+
+    clipPath : function(c,cn) {
+      var p = new CanvasNode()
+      p.units = c.getAttribute('clipPathUnits')
+      return p
+    },
+
+    title : function(c, canvasNode) {
+      canvasNode.root.title = c.textContent
+    },
+
+    desc : function(c,cn) {
+      cn.root.description = c.textContent
+    },
+
+    metadata : function(c, cn) {
+      cn.root.metadata = c
+    },
+
+
+
+
+
+
+
+
+    parseAnimateTag : function(c, cn) {
+      var after = SVGParser.SVGTagMapping.parseTime(c.getAttribute('begin'))
+      var dur = SVGParser.SVGTagMapping.parseTime(c.getAttribute('dur'))
+      var end = SVGParser.SVGTagMapping.parseTime(c.getAttribute('end'))
+      if (dur == null) dur = end-after
+      dur = isNaN(dur) ? 0 : dur
+      var variable = c.getAttribute('attributeName')
+      var fill = c.getAttribute('fill')
+      if (cn.tagName == 'rect') {
+        if (variable == 'x') variable = 'cx'
+        if (variable == 'y') variable = 'cy'
+      }
+      var accum = c.getAttribute('accumulate') == 'sum'
+      var additive = c.getAttribute('additive')
+      if (additive) additive = additive == 'sum'
+      else additive = accum
+      var repeat = c.getAttribute('repeatCount')
+      if (repeat == 'indefinite') repeat = true
+      else repeat = parseFloat(repeat)
+      if (!repeat && dur > 0) {
+        var repeatDur = c.getAttribute('repeatDur')
+        if (repeatDur == 'indefinite') repeat = true
+        else repeat = SVGParser.SVGTagMapping.parseTime(repeatDur) / dur
+      }
+      return {
+        after: isNaN(after) ? 0 : after,
+        duration: dur,
+        restart: c.getAttribute('restart'),
+        calcMode : c.getAttribute('calcMode'),
+        additive : additive,
+        accumulate : accum,
+        repeat : repeat,
+        variable: variable,
+        fill: fill
+      }
+    },
+
+    parseTime : function(value) {
+      if (!value) return null
+      if (value.match(/[0-9]$/)) {
+        var hms = value.split(":")
+        var s = hms[hms.length-1] || 0
+        var m = hms[hms.length-2] || 0
+        var h = hms[hms.length-3] || 0
+        return (parseFloat(h)*3600 + parseFloat(m)*60 + parseFloat(s)) * 1000
+      } else {
+        var fac = 60
+        if (value.match(/s$/i)) fac = 1
+        else if (value.match(/h$/i)) fac = 3600
+        return parseFloat(value) * fac * 1000
+      }
+    },
+
+
+
+
+
+
+    animate : function(c, cn) {
+      var from = this.parseUnit(c.getAttribute('from'), cn, 'x')
+      var to = this.parseUnit(c.getAttribute('to'), cn, 'x')
+      var by = this.parseUnit(c.getAttribute('by'), cn, 'x')
+      var o = SVGParser.SVGTagMapping.parseAnimateTag(c, cn)
+      if (c.getAttribute('values')) {
+        var self = this
+        var vals = c.getAttribute('values')
+        vals = vals.split(";").map(function(v) {
+          var xy = v.split(/[, ]+/)
+          if (xy.length > 2) {
+            return xy.map(function(x){ return self.parseUnit(x, cn, 'x') })
+          } else if (xy.length > 1) {
+            return [
+              self.parseUnit(xy[0], cn, 'x'),
+              self.parseUnit(xy[1], cn, 'y')
+            ]
+          } else {
+            return self.parseUnit(v, cn, 'x')
+          }
+        })
+      } else {
+        if (to == null) to = from + by
+      }
+      cn.after(o.after, function() {
+        if (o.fill == 'remove') {
+          var orig = Object.clone(this[o.variable])
+          this.after(o.duration, function(){ this[o.variable] = orig })
+        }
+        if (vals) {
+          if (o.additive) {
+            var ov = this[o.variable]
+            vals = vals.map(function(v){
+              return Object.sum(v, ov)
+            })
+          }
+          var length = 0
+          var lens = []
+          if (vals[0] instanceof Array) {
+            for (var i=1; i<vals.length; i++) {
+              var diff = Object.sub(vals[i] - vals[i-1])
+              var sl = Math.sqrt(diff.reduce(function(s, i) { return s + i*i }, 0))
+              lens.push(sl)
+              length += sl
+            }
+          } else {
+            for (var i=1; i<vals.length; i++) {
+              var sl = Math.abs(vals[i] - vals[i-1])
+              lens.push(sl)
+              length += sl
+            }
+          }
+          var animator = function(pos) {
+            if (pos == 1) {
+              this[o.variable] = vals[vals.length-1]
+            } else {
+              if (o.calcMode == 'paced') {
+                var len = pos * length
+                var rlen = 0, idx, rt
+                for (var i=0; i<lens.length; i++) {
+                  if (rlen + lens[i] > len) {
+                    idx = i
+                    rt = (len - rlen) / lens[i]
+                    break
+                  }
+                  rlen += lens[i]
+                }
+                var v0 = idx
+                var v1 = v0 + 1
+              } else {
+                var idx = pos * (vals.length-1)
+                var v0 = Math.floor(idx)
+                var rt = idx - v0
+                var v1 = v0 + 1
+              }
+              this.tweenVariable(o.variable, vals[v0], vals[v1], rt, o.calcMode)
+            }
+          }
+          this.animate(animator, from, to, o.duration, 'linear', {
+            repeat: o.repeat,
+            additive: o.additive,
+            accumulate: o.accumulate
+          })
+        } else {
+          if (from == null) {
+            from = this[o.variable]
+            if (by != null) to = from + by
+          }
+          if (o.additive) {
+            from = Object.sum(from, this[o.variable])
+            to = Object.sum(to, this[o.variable])
+          }
+          this.animate(o.variable, from, to, o.duration, o.calcMode, {
+            repeat: o.repeat,
+            additive: o.additive,
+            accumulate: o.accumulate
+          })
+        }
+      })
+    },
+
+    set : function(c, cn) {
+      var to = c.getAttribute('to')
+      var o = SVGParser.SVGTagMapping.parseAnimateTag(c, cn)
+      cn.after(o.after, function() {
+        if (o.fill == 'remove') {
+          var orig = Object.clone(this[o.variable])
+          this.after(o.duration, function(){ this[o.variable] = orig })
+        }
+        this[o.variable] = to
+      })
+    },
+
+    animateMotion : function(c,cn) {
+      var path
+      if (c.getAttribute('path')) {
+        path = new Path(c.getAttribute('path'))
+      } else if (c.getAttribute('values')) {
+        var vals = c.getAttribute('values')
+        path = new Path("M" + vals.split(";").join("L"))
+      } else if (c.getAttribute('from') || c.getAttribute('to') || c.getAttribute('by')) {
+        var from = c.getAttribute('from')
+        var to = c.getAttribute('to')
+        var by = c.getAttribute('by')
+        if (!from) from = "0,0"
+        if (!to) to = "l" + by
+        else to = "L" + to
+        path = new Path("M" + from + to)
+      }
+      var p = new CanvasNode()
+      p.__motionPath = path
+      var rotate = c.getAttribute('rotate')
+      var o = SVGParser.SVGTagMapping.parseAnimateTag(c, cn)
+      cn.after(o.after, function() {
+        if (o.fill == 'remove') {
+          var ox = this.x, oy = this.y
+          this.after(o.duration, function(){ this.x = ox; this.y = oy})
+        }
+        var motion = function(pos) {
+          var pa = p.__motionPath.pointAngleAt(pos, {
+            discrete: o.calcMode == 'discrete',
+            linear : o.calcMode == 'linear'
+          })
+          this.x = pa.point[0]
+          this.y = pa.point[1]
+          if (rotate == 'auto') {
+            this.rotation = pa.angle
+          } else if (rotate == 'auto-reverse') {
+            this.rotation = pa.angle + Math.PI
+          }
+        }
+        this.animate(motion, 0, 1, o.duration, 'linear', {
+          repeat: o.repeat,
+          additive: o.additive,
+          accumulate: o.accumulate
+        })
+      })
+      return p
+    },
+
+    mpath : function(c,cn, defs) {
+      var href = c.getAttribute('xlink:href')
+      href = href.replace(/^#/,'')
+      this.getDef(defs, href, function(obj) {
+        cn.__motionPath = obj
+      })
+    },
+
+    animateColor : function(c, cn, defs) {
+      var from = c.getAttribute('from')
+      var to = c.getAttribute('to')
+      from = SVGParser.SVGMapping.__parseStyle(from, null, defs)
+      to = SVGParser.SVGMapping.__parseStyle(to, null, defs)
+      var o = SVGParser.SVGTagMapping.parseAnimateTag(c, cn)
+      cn.after(o.after, function() {
+        if (o.fill == 'remove') {
+          var orig = Object.clone(this[o.variable])
+          this.after(o.duration, function(){ this[o.variable] = orig })
+        }
+        this.animate(o.variable, from, to, o.duration, 'linear', {
+          repeat: o.repeat,
+          additive: o.additive,
+          accumulate: o.accumulate
+        })
+      })
+    },
+
+    animateTransform : function(c, cn) {
+      var from = c.getAttribute('from')
+      var to = c.getAttribute('to')
+      var by = c.getAttribute('by')
+      var o = SVGParser.SVGTagMapping.parseAnimateTag(c, cn)
+      if (from) from = from.split(/[ ,]+/).map(parseFloat)
+      if (to) to = to.split(/[ ,]+/).map(parseFloat)
+      if (by) by = by.split(/[ ,]+/).map(parseFloat)
+      o.variable = c.getAttribute('type')
+      if (o.variable == 'rotate') {
+        o.variable = 'rotation'
+        if (from) from = from.map(function(v) { return v * Math.PI/180 })
+        if (to) to = to.map(function(v) { return v * Math.PI/180 })
+        if (by) by = by.map(function(v) { return v * Math.PI/180 })
+      } else if (o.variable.match(/^skew/)) {
+        if (from) from = from.map(function(v) { return v * Math.PI/180 })
+        if (to) to = to.map(function(v) { return v * Math.PI/180 })
+        if (by) by = by.map(function(v) { return v * Math.PI/180 })
+      }
+      if (to == null) to = Object.sum(from, by)
+      cn.after(o.after, function() {
+        if (o.variable == 'translate') {
+          if (from == null) {
+            from = [this.x, this.y]
+            if (by != null) to = Object.sum(from, by)
+          }
+          if (o.fill == 'remove') {
+            var ox = this.x
+            var oy = this.y
+            this.after(o.duration, function(){ this.x = ox; this.y = oy })
+          }
+          this.animate('x', from[0], to[0], o.duration, 'linear', {
+            repeat: o.repeat,
+            additive: o.additive,
+            accumulate: o.accumulate
+          })
+          if (from[1] != null) {
+            this.animate('y', from[1], to[1], o.duration, 'linear', {
+              repeat: o.repeat,
+              additive: o.additive,
+              accumulate: o.accumulate
+            })
+          }
+        } else {
+          if (from) {
+            if (from.length == 1) from = from[0]
+          }
+          if (to) {
+            if (to.length == 1) to = to[0]
+          }
+          if (by) {
+            if (by.length == 1) by = by[0]
+          }
+          if (from == null) {
+            from = this[o.variable]
+            if (by != null) to = Object.sum(from, by)
+          }
+          if (o.variable == 'scale' && o.additive) {
+            // +1 in SMIL's additive scale means *1, welcome to brokenville
+            o.additive = false
+          }
+          if (o.fill == 'remove') {
+            var orig = Object.clone(this[o.variable])
+            this.after(o.duration, function(){ this[o.variable] = orig })
+          }
+          this.animate(o.variable, from, to, o.duration, 'linear', {
+            repeat: o.repeat,
+            additive: o.additive,
+            accumulate: o.accumulate
+          })
+        }
+      })
+    },
+
+    a : function(c, cn) {
+      var href = c.getAttribute('xlink:href') ||
+                 c.getAttribute('href')
+      var target = c.getAttribute('target')
+      var p = new LinkNode(href, target)
+      return p
+    },
+
+    use : function(c, cn, defs, style) {
+      var id = c.getAttribute('xlink:href') ||
+                c.getAttribute('href')
+      var p = new CanvasNode()
+      if (id) {
+        id = id.replace(/^#/,'')
+        this.getDef(defs, id, function(obj) {
+          var oc = obj.clone()
+          var par = p.parent
+          if (par) {
+            if (p.stroke) oc.stroke = p.stroke
+            if (p.fill) oc.fill = p.fill
+            p.append(oc)
+          } else {
+            p = oc
+          }
+        })
+      }
+      return p
+    },
+
+    image : function(c, cn, defs, style) {
+      var src = c.getAttribute('xlink:href') ||
+                c.getAttribute('href')
+      if (src && src.search(/^[a-z]+:/i) != 0) {
+        src = cn.root.filename.split("/").slice(0,-1).join("/") + "/" + src
+      }
+      var p = new ImageNode(src ? Object.loadImage(src) : null)
+      p.fill = 'none'
+      p.dX = this.parseUnit(c.getAttribute('x'), cn, 'x') || 0
+      p.dY = this.parseUnit(c.getAttribute('y'), cn, 'y') || 0
+      p.srcWidth = this.parseUnit(c.getAttribute('width'), cn, 'x')
+      p.srcHeight = this.parseUnit(c.getAttribute('height'), cn, 'y')
+      return p
+    },
+
+    path : function(c) {
+      return new Path(c.getAttribute("d"))
+    },
+
+    polygon : function(c) {
+      return new Polygon(c.getAttribute("points").toString().strip()
+                          .split(/[\s,]+/).map(parseFloat))
+    },
+
+    polyline : function(c) {
+      return new Polygon(c.getAttribute("points").toString().strip()
+                          .split(/[\s,]+/).map(parseFloat), {closePath:false})
+    },
+
+    rect : function(c, cn) {
+      var p = new Rectangle(
+        this.parseUnit(c.getAttribute('width'), cn, 'x'),
+        this.parseUnit(c.getAttribute('height'), cn, 'y')
+      )
+      p.cx = this.parseUnit(c.getAttribute('x'), cn, 'x') || 0
+      p.cy = this.parseUnit(c.getAttribute('y'), cn, 'y') || 0
+      p.rx = this.parseUnit(c.getAttribute('rx'), cn, 'x') || 0
+      p.ry = this.parseUnit(c.getAttribute('ry'), cn, 'y') || 0
+      return p
+    },
+
+    line : function(c, cn) {
+      var x1 = this.parseUnit(c.getAttribute('x1'), cn, 'x') || 0
+      var y1 = this.parseUnit(c.getAttribute('y1'), cn, 'y') || 0
+      var x2 = this.parseUnit(c.getAttribute('x2'), cn, 'x') || 0
+      var y2 = this.parseUnit(c.getAttribute('y2'), cn, 'y') || 0
+      var p = new Line(x1,y1, x2,y2)
+      return p
+    },
+
+    circle : function(c, cn) {
+      var p = new Circle(this.parseUnit(c.getAttribute('r'), cn) || 0)
+      p.cx = this.parseUnit(c.getAttribute('cx'), cn, 'x') || 0
+      p.cy = this.parseUnit(c.getAttribute('cy'), cn, 'y') || 0
+      return p
+    },
+
+    ellipse : function(c, cn) {
+      var p = new Ellipse(
+        this.parseUnit(c.getAttribute('rx'), cn, 'x') || 0,
+        this.parseUnit(c.getAttribute('ry'), cn, 'y') || 0
+      )
+      p.cx = this.parseUnit(c.getAttribute('cx'), cn, 'x') || 0
+      p.cy = this.parseUnit(c.getAttribute('cy'), cn, 'y') || 0
+      return p
+    },
+
+    text : function(c, cn) {
+      if (false) {
+        var p = new TextNode(c.textContent.strip())
+        p.setAsPath(true)
+        p.cx = this.parseUnit(c.getAttribute('x'),cn, 'x') || 0
+        p.cy = this.parseUnit(c.getAttribute('y'),cn, 'y') || 0
+        return p
+      } else {
+        var e = E('div', c.textContent.strip())
+        e.style.marginTop = '-1em'
+        e.style.whiteSpace = 'nowrap'
+        var p = new ElementNode(e)
+        p.xOffset = this.parseUnit(c.getAttribute('x'),cn, 'x') || 0
+        p.yOffset = this.parseUnit(c.getAttribute('y'),cn, 'y') || 0
+        return p
+      }
+    },
+
+    style : function(c, cn, defs, style) {
+      this.parseStyle(c, style)
+    },
+
+    defs : function(c, cn, defs, style) {
+      return new CanvasNode({visible: false})
+    },
+
+    linearGradient : function(c, cn,defs,style) {
+      var g = new Gradient({type:'linear'})
+      g.color = cn.color
+      if (c.getAttribute('color')) {
+        SVGParser.SVGMapping.color(g, c.getAttribute('color'), defs, style)
+      }
+      g.svgNode = c
+      var x1 = c.getAttribute('x1')
+      var y1 = c.getAttribute('y1')
+      var x2 = c.getAttribute('x2')
+      var y2 = c.getAttribute('y2')
+      var transform = c.getAttribute('gradientTransform')
+      g.units = c.getAttribute('gradientUnits') || "objectBoundingBox"
+      if (x1) g.startX = parseFloat(x1) * (x1.charAt(x1.length-1) == '%' ? 0.01 : 1)
+      if (y1) g.startY = parseFloat(y1) * (y1.charAt(y1.length-1) == '%' ? 0.01 : 1)
+      if (x2) g.endX = parseFloat(x2) * (x2.charAt(x2.length-1) == '%' ? 0.01 : 1)
+      if (y2) g.endY = parseFloat(y2) * (y2.charAt(y2.length-1) == '%' ? 0.01 : 1)
+      if (transform) this.applySVGTransform(g, transform, defs, style)
+      this.parseStops(g, c, defs, style)
+      return g
+    },
+
+    radialGradient : function(c, cn, defs, style) {
+      var g = new Gradient({type:'radial'})
+      g.color = cn.color
+      if (c.getAttribute('color')) {
+        SVGParser.SVGMapping.color(g, c.getAttribute('color'), defs, style)
+      }
+      g.svgNode = c
+      var r = c.getAttribute('r')
+      var fx = c.getAttribute('fx')
+      var fy = c.getAttribute('fy')
+      var cx = c.getAttribute('cx')
+      var cy = c.getAttribute('cy')
+      var transform = c.getAttribute('gradientTransform')
+      g.units = c.getAttribute('gradientUnits') || "objectBoundingBox"
+      if (r) g.endRadius = parseFloat(r) * (r.charAt(r.length-1) == '%' ? 0.01 : 1)
+      if (fx) g.startX = parseFloat(fx) * (fx.charAt(fx.length-1) == '%' ? 0.01 : 1)
+      if (fy) g.startY = parseFloat(fy) * (fy.charAt(fy.length-1) == '%' ? 0.01 : 1)
+      if (cx) g.endX = parseFloat(cx) * (cx.charAt(cx.length-1) == '%' ? 0.01 : 1)
+      if (cy) g.endY = parseFloat(cy) * (cy.charAt(cy.length-1) == '%' ? 0.01 : 1)
+      if (transform) this.applySVGTransform(g, transform, defs, style)
+      this.parseStops(g, c, defs, style)
+      return g
+    }
+  },
+
+  parseChildren : function(node, canvasNode, defs, style) {
+    var childNodes = []
+    var cn = canvasNode
+    for (var i=0; i<node.childNodes.length; i++) {
+      var c = node.childNodes[i]
+      var p = false // argh, remember to initialize vars inside loops
+      if (c.childNodes) {
+        if (c.tagName) {
+          if (this.SVGTagMapping[c.tagName]) {
+            p = this.SVGTagMapping[c.tagName].call(
+                  this, c, canvasNode, defs, style)
+          } else {
+            p = new CanvasNode()
+          }
+          if (p) {
+            p.root = canvasNode.root
+            p.fontSize = cn.fontSize
+            p.strokeWidth = cn.strokeWidth
+            if (c.attributes) {
+              for (var j=0; j<c.attributes.length; j++) {
+                var attr = c.attributes[j]
+                if (this.SVGMapping[attr.nodeName])
+                  this.SVGMapping[attr.nodeName](p, attr.nodeValue, defs, style)
+              }
+            }
+            if (p.id) {
+              this.setDef(defs, p.id, p)
+            }
+            p.tagName = c.tagName
+            this.applySVGTransform(p, c.getAttribute("transform"), defs, style)
+            this.applySVGStyle(p, c.getAttribute("style"), defs, style)
+            if (p.tagName && style.tags[p.tagName])
+              this.applySVGStyle(p, style.tags[p.tagName], defs, style)
+            if (p.className && style.classes[p.className])
+              this.applySVGStyle(p, style.classes[p.className], defs, style)
+            if (p.id && style.ids[p.id])
+              this.applySVGStyle(p, style.ids[p.id], defs, style)
+            if (!p.marker) p.marker = cn.marker
+            if (!p.markerStart) p.markerStart = cn.markerStart
+            if (!p.markerEnd) p.markerEnd = cn.markerEnd
+            if (!p.markerMid) p.markerMid = cn.markerMid
+          }
+        }
+        if (p && p.setRoot) {
+          p.zIndex = i
+          canvasNode.append(p)
+          this.parseChildren(c, p, defs, style)
+        }
+      }
+    }
+  },
+
+  parseStyle : function(node, style) {
+    var text = node.textContent
+    var segs = text.split(/\}/m)
+    for (var i=0; i<segs.length; i++) {
+      var seg = segs[i]
+      var kv = seg.split(/\{/m)
+      if (kv.length < 2) continue
+      var key = kv[0].strip()
+      var value = kv[1].strip()
+      switch (key.charAt(0)) {
+        case '.':
+          style.classes[key.slice(1)] = value
+          break;
+        case '#':
+          style.ids[key.slice(1)] = value
+          break;
+        default:
+          style.tags[key] = value
+          break;
+      }
+    }
+  },
+
+  parseStops : function(g, node, defs, style) {
+    var href = node.getAttribute('xlink:href')
+    g.colorStops = []
+    if (href) {
+      href = href.replace(/^#/,'')
+      this.getDef(defs, href, function(g2) {
+        if (g.colorStops.length == 0)
+          g.colorStops = g2.colorStops
+      })
+    }
+    var stops = []
+    for (var i=0; i<node.childNodes.length; i++) {
+      var c = node.childNodes[i]
+      if (c.tagName == 'stop') {
+        var offset = parseFloat(c.getAttribute('offset'))
+        if (c.getAttribute('offset').search(/%/) != -1)
+          offset *= 0.01
+        var stop = [offset]
+        stop.color = g.color
+        for (var j=0; j<c.attributes.length; j++) {
+          var attr = c.attributes[j]
+          if (this.SVGMapping[attr.nodeName])
+            this.SVGMapping[attr.nodeName](stop, attr.nodeValue, defs, style)
+        }
+        this.applySVGStyle(stop, c.getAttribute('style'), defs, style)
+        var id = c.getAttribute('id')
+        if (id) this.setDef(defs, id, stop)
+        stops.push(stop)
+      }
+    }
+    if (stops.length > 0)
+      g.colorStops = stops
+  },
+
+  applySVGTransform : function(node, transform, defs, style) {
+    if (!transform) return
+    node.transformList = []
+    var segs = transform.match(/[a-z]+\s*\([^)]*\)/ig)
+    for (var i=0; i<segs.length; i++) {
+      var kv = segs[i].split("(")
+      var k = kv[0].strip()
+      if (this.SVGMapping[k]) {
+        var v = kv[1].strip().slice(0,-1)
+        this.SVGMapping[k](node, v, defs, style)
+      }
+    }
+    this.breakDownTransformList(node)
+  },
+
+  breakDownTransformList : function(node) {
+    var tl = node.transformList
+    if (node.transformList.length == 1) {
+      var tr = tl[0]
+      if (tr[0] == 'translate') {
+        node.x = tr[1][0]
+        node.y = tr[1][1]
+      } else if (tr[0] == 'scale') {
+        node.scale = tr[1]
+      } else if (tr[0] == 'rotate') {
+        node.rotation = tr[1]
+      } else if (tr[0] == 'matrix') {
+        node.matrix = tr[1]
+      } else if (tr[0] == 'skewX') {
+        node.skewX = tr[1][0]
+      } else if (tr[0] == 'skewY') {
+        node.skewY = tr[1][0]
+      } else {
+        return
+      }
+      node.transformList = null
+    }
+  },
+
+  applySVGStyle : function(node, style, defs, st) {
+    if (!style) return
+    var segs = style.split(";")
+    for (var i=0; i<segs.length; i++) {
+      var kv = segs[i].split(":")
+      var k = kv[0].strip()
+      if (this.SVGMapping[k]) {
+        var v = kv[1].strip()
+        this.SVGMapping[k](node, v, defs, st)
+      }
+    }
+  },
+
+  getDef : function(defs, id, f) {
+    if (defs[id] && defs[id] instanceof Array) {
+      defs[id].push(f)
+    } else if (defs[id]) {
+      f(defs[id])
+    } else {
+      defs[id] = [f]
+    }
+  },
+
+  setDef : function(defs, id, obj) {
+    if (defs[id] && defs[id] instanceof Array) {
+      for (var i=0; i<defs[id].length; i++) {
+        defs[id][i](obj)
+      }
+    }
+    defs[id] = obj
+  },
+
+  parseUnit : function(v, parent, dir) {
+    if (v == null) {
+      return null
+    } else {
+      return this.parseUnitMultiplier(v, parent, dir) * parseFloat(v.strip())
+    }
+  },
+
+  parseUnitMultiplier : function(str, parent, dir) {
+    var cm = this.getCmInPixels()
+    if (str.search(/cm$/i) != -1)
+      return cm
+    else if (str.search(/mm$/i) != -1)
+      return 0.1 * cm
+    else if (str.search(/pt$/i) != -1)
+      return 0.0352777778 * cm
+    else if (str.search(/pc$/i) != -1)
+      return 0.4233333333 * cm
+    else if (str.search(/in$/i) != -1)
+      return 2.54 * cm
+    else if (str.search(/em$/i) != -1)
+      return parent.fontSize
+    else if (str.search(/ex$/i) != -1)
+      return parent.fontSize / 2
+    else if (str.search(/%$/i) != -1)
+      if (dir == 'x')
+        return parent.root.innerWidth * 0.01
+      else if (dir == 'y')
+        return parent.root.innerHeight * 0.01
+      else
+        return parent.root.innerSize * 0.01
+    else
+      return 1
+  },
+
+  getCmInPixels : function() {
+    if (!this.cmInPixels) {
+      var e = E('div',{ style: {
+        margin: '0px',
+        padding: '0px',
+        width: '1cm',
+        height: '1cm',
+        position: 'absolute',
+        visibility: 'hidden'
+      }})
+      document.body.appendChild(e)
+      var cm = e.offsetWidth
+      document.body.removeChild(e)
+      this.cmInPixels = cm || 38
+    }
+    return this.cmInPixels
+  },
+
+  getEmInPixels : function() {
+    if (!this.emInPixels) {
+      var e = E('div',{ style: {
+        margin: '0px',
+        padding: '0px',
+        width: '1em',
+        height: '1em',
+        position: 'absolute',
+        visibility: 'hidden'
+      }})
+      document.body.appendChild(e)
+      var em = e.offsetWidth
+      document.body.removeChild(e)
+      this.emInPixels = em || 12
+    }
+    return this.emInPixels
+  },
+
+  getExInPixels : function() {
+    if (!this.exInPixels) {
+      var e = E('div',{ style: {
+        margin: '0px',
+        padding: '0px',
+        width: '1ex',
+        height: '1ex',
+        position: 'absolute',
+        visibility: 'hidden'
+      }})
+      document.body.appendChild(e)
+      var ex = e.offsetWidth
+      document.body.removeChild(e)
+      this.exInPixels = ex || 6
+    }
+    return this.exInPixels
+  },
+
+  SVGMapping : {
+    DEG_TO_RAD_FACTOR : Math.PI / 180,
+    RAD_TO_DEG_FACTOR : 180 / Math.PI,
+
+    parseUnit : function(v, cn, dir) {
+      return SVGParser.parseUnit(v, cn, dir)
+    },
+
+    "class" : function(node, v) {
+      node.className = v
+    },
+
+    marker : function(node, v, defs) {
+      SVGParser.getDef(defs, v.replace(/^url\(#|\)$/g, ''), function(g) {
+        node.marker = g
+      })
+    },
+
+    "marker-start" : function(node, v, defs) {
+      SVGParser.getDef(defs, v.replace(/^url\(#|\)$/g, ''), function(g) {
+        node.markerStart = g
+      })
+    },
+
+    "marker-end" : function(node, v, defs) {
+      SVGParser.getDef(defs, v.replace(/^url\(#|\)$/g, ''), function(g) {
+        node.markerEnd = g
+      })
+    },
+
+    "marker-mid" : function(node, v, defs) {
+      SVGParser.getDef(defs, v.replace(/^url\(#|\)$/g, ''), function(g) {
+        node.markerMid = g
+      })
+    },
+
+    "clip-path" : function(node, v, defs) {
+      SVGParser.getDef(defs, v.replace(/^url\(#|\)$/g, ''), function(g) {
+        node.clipPath = g
+      })
+    },
+
+    id : function(node, v) {
+      node.id = v
+    },
+
+    translate : function(node, v) {
+      var xy = v.split(/[\s,]+/).map(parseFloat)
+      node.transformList.push(['translate', [xy[0], xy[1] || 0]])
+    },
+
+    rotate : function(node, v) {
+      if (v == 'auto' || v == 'auto-reverse') return
+      var rot = v.split(/[\s,]+/).map(parseFloat)
+      var angle = rot[0] * this.DEG_TO_RAD_FACTOR
+      if (rot.length > 1)
+        node.transformList.push(['rotate', [angle, rot[1], rot[2] || 0]])
+      else
+        node.transformList.push(['rotate', [angle]])
+    },
+
+    scale : function(node, v) {
+      var xy = v.split(/[\s,]+/).map(parseFloat)
+      var trans = ['scale']
+      if (xy.length > 1)
+        trans[1] = [xy[0], xy[1]]
+      else
+        trans[1] = [xy[0], xy[0]]
+      node.transformList.push(trans)
+    },
+
+    matrix : function(node, v) {
+      var mat = v.split(/[\s,]+/).map(parseFloat)
+      node.transformList.push(['matrix', mat])
+    },
+
+    skewX : function(node, v) {
+      var angle = parseFloat(v)*this.DEG_TO_RAD_FACTOR
+      node.transformList.push(['skewX', [angle]])
+    },
+
+    skewY : function(node, v) {
+      var angle = parseFloat(v)*this.DEG_TO_RAD_FACTOR
+      node.transformList.push(['skewY', [angle]])
+    },
+
+    opacity : function(node, v) {
+      node.opacity = parseFloat(v)
+    },
+
+    display : function (node, v) {
+      node.display = v
+    },
+
+    visibility : function (node, v) {
+      node.visibility = v
+    },
+
+    'stroke-miterlimit' : function(node, v) {
+      node.miterLimit = parseFloat(v)
+    },
+
+    'stroke-linecap' : function(node, v) {
+      node.lineCap = v
+    },
+
+    'stroke-linejoin' : function(node, v) {
+      node.lineJoin = v
+    },
+
+    'stroke-width' : function(node, v) {
+      node.strokeWidth = this.parseUnit(v, node)
+    },
+
+    fill : function(node, v, defs, style) {
+      node.fill = this.__parseStyle(v, node.fill, defs, node.color)
+    },
+
+    stroke : function(node, v, defs, style) {
+      node.stroke = this.__parseStyle(v, node.stroke, defs, node.color)
+    },
+
+    color : function(node, v, defs, style) {
+      if (v == 'inherit') return
+      node.color = this.__parseStyle(v, false, defs, node.color)
+    },
+
+    'stop-color' : function(node, v, defs, style) {
+      if (v == 'none') {
+        node[1] = [0,0,0,0]
+      } else {
+        node[1] = this.__parseStyle(v, node[1], defs, node.color)
+      }
+    },
+
+    'fill-opacity' : function(node, v) {
+      node.fillOpacity = Math.min(1,Math.max(0,parseFloat(v)))
+    },
+
+    'stroke-opacity' : function(node, v) {
+      node.strokeOpacity = Math.min(1,Math.max(0,parseFloat(v)))
+    },
+
+    'stop-opacity' : function(node, v) {
+      node[1] = node[1] || [0,0,0]
+      node[1][3] = Math.min(1,Math.max(0,parseFloat(v)))
+    },
+
+    'text-anchor' : function(node, v) {
+      node.textAnchor = v
+      if (node.setAlign) {
+        if (v == 'middle')
+          node.setAlign('center')
+        else
+          node.setAlign(v)
+      }
+    },
+
+    'font-family' : function(node, v) {
+      node.fontFamily = v
+    },
+
+    'font-size' : function(node, v) {
+      node.fontSize = this.parseUnit(v, node)
+    },
+
+    __parseStyle : function(v, currentStyle, defs, currentColor) {
+
+      if (v.charAt(0) == '#') {
+        if (v.length == 4)
+          v = v.replace(/([^#])/g, '$1$1')
+        var a = v.slice(1).match(/../g).map(
+          function(i) { return parseInt(i, 16) })
+        return a
+
+      } else if (v.search(/^rgb\(/) != -1) {
+        var a = v.slice(4,-1).split(",")
+        for (var i=0; i<a.length; i++) {
+          var c = a[i].strip()
+          if (c.charAt(c.length-1) == '%')
+            a[i] = Math.round(parseFloat(c.slice(0,-1)) * 2.55)
+          else
+            a[i] = parseInt(c)
+        }
+        return a
+
+      } else if (v.search(/^rgba\(/) != -1) {
+        var a = v.slice(5,-1).split(",")
+        for (var i=0; i<3; i++) {
+          var c = a[i].strip()
+          if (c.charAt(c.length-1) == '%')
+            a[i] = Math.round(parseFloat(c.slice(0,-1)) * 2.55)
+          else
+            a[i] = parseInt(c)
+        }
+        var c = a[3].strip()
+        if (c.charAt(c.length-1) == '%')
+          a[3] = Math.round(parseFloat(c.slice(0,-1)) * 0.01)
+        else
+          a[3] = Math.max(0, Math.min(1, parseFloat(c)))
+        return a
+
+      } else if (v.search(/^url\(/) != -1) {
+        var id = v.match(/\([^)]+\)/)[0].slice(1,-1).replace(/^#/, '')
+        if (defs[id]) {
+          return defs[id]
+        } else { // missing defs, let's make it known that we're screwed
+          return 'rgba(255,0,255,1)'
+        }
+
+      } else if (v == 'currentColor') {
+        return currentColor
+
+      } else if (v == 'none') {
+        return 'none'
+
+      } else if (v == 'freeze') { // SMIL is evil, but so are we
+        return null
+
+      } else if (v == 'remove') {
+        return null
+
+      } else { // unknown value, maybe it's an ICC color
+        return v
+      }
+    }
+  }
+}
diff --git a/lib/excanvas.js b/lib/excanvas.js
new file mode 100644 (file)
index 0000000..367764b
--- /dev/null
@@ -0,0 +1,924 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns are not implemented.
+// * Radial gradient are not implemented. The VML version of these look very
+//   different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+//   width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+//   Quirks mode will draw the canvas using border-box. Either change your
+//   doctype to HTML5
+//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+//   or use Box Sizing Behavior from WebFX
+//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+  var abs = m.abs;
+  var sqrt = m.sqrt;
+
+  // this is used for sub pixel precision
+  var Z = 10;
+  var Z2 = Z / 2;
+
+  /**
+   * This funtion is assigned to the <canvas> elements as element.getContext().
+   * @this {HTMLElement}
+   * @return {CanvasRenderingContext2D_}
+   */
+  function getContext() {
+    return this.context_ ||
+        (this.context_ = new CanvasRenderingContext2D_(this));
+  }
+
+  var slice = Array.prototype.slice;
+
+  /**
+   * Binds a function to an object. The returned function will always use the
+   * passed in {@code obj} as {@code this}.
+   *
+   * Example:
+   *
+   *   g = bind(f, obj, a, b)
+   *   g(c, d) // will do f.call(obj, a, b, c, d)
+   *
+   * @param {Function} f The function to bind the object to
+   * @param {Object} obj The object that should act as this when the function
+   *     is called
+   * @param {*} var_args Rest arguments that will be used as the initial
+   *     arguments when the function is called
+   * @return {Function} A new function that has bound this
+   */
+  function bind(f, obj, var_args) {
+    var a = slice.call(arguments, 2);
+    return function() {
+      return f.apply(obj, a.concat(slice.call(arguments)));
+    };
+  }
+
+  var G_vmlCanvasManager_ = {
+    init: function(opt_doc) {
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var doc = opt_doc || document;
+        // Create a dummy element so that IE will allow canvas elements to be
+        // recognized.
+        doc.createElement('canvas');
+        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+      }
+    },
+
+    init_: function(doc) {
+      // create xmlns
+      if (!doc.namespaces['g_vml_']) {
+        doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
+                           '#default#VML');
+
+      }
+      if (!doc.namespaces['g_o_']) {
+        doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
+                           '#default#VML');
+      }
+
+      // Setup default CSS.  Only add one style sheet per document
+      if (!doc.styleSheets['ex_canvas_']) {
+        var ss = doc.createStyleSheet();
+        ss.owningElement.id = 'ex_canvas_';
+        ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+            // default size is 300x150 in Gecko and Opera
+            'text-align:left;width:300px;height:150px}' +
+            'g_vml_\\:*{behavior:url(#default#VML)}' +
+            'g_o_\\:*{behavior:url(#default#VML)}';
+
+      }
+
+      // find all canvas elements
+      var els = doc.getElementsByTagName('canvas');
+      for (var i = 0; i < els.length; i++) {
+        this.initElement(els[i]);
+      }
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function(el) {
+      if (!el.getContext) {
+
+        el.getContext = getContext;
+
+        // Remove fallback content. There is no way to hide text nodes so we
+        // just remove all childNodes. We could hide all elements and remove
+        // text nodes but who really cares about the fallback content.
+        el.innerHTML = '';
+
+        // do not use inline function because that will leak memory
+        el.attachEvent('onpropertychange', onPropertyChange);
+        el.attachEvent('onresize', onResize);
+
+        var attrs = el.attributes;
+        if (attrs.width && attrs.width.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setWidth_(attrs.width.nodeValue);
+          el.style.width = attrs.width.nodeValue + 'px';
+        } else {
+          el.width = el.clientWidth;
+        }
+        if (attrs.height && attrs.height.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setHeight_(attrs.height.nodeValue);
+          el.style.height = attrs.height.nodeValue + 'px';
+        } else {
+          el.height = el.clientHeight;
+        }
+        //el.getContext().setCoordsize_()
+      }
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    var el = e.srcElement;
+
+    switch (e.propertyName) {
+      case 'width':
+        el.style.width = el.attributes.width.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+      case 'height':
+        el.style.height = el.attributes.height.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+    o2.globalAlpha   = o1.globalAlpha;
+    o2.arcScaleX_    = o1.arcScaleX_;
+    o2.arcScaleY_    = o1.arcScaleY_;
+    o2.lineScale_    = o1.lineScale_;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == 'rgb') {
+      var start = styleString.indexOf('(', 3);
+      var end = styleString.indexOf(')', start + 1);
+      var guts = styleString.substring(start + 1, end).split(',');
+
+      str = '#';
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[Number(guts[i])];
+      }
+
+      if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return {color: str, alpha: alpha};
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case 'butt':
+        return 'flat';
+      case 'round':
+        return 'round';
+      case 'square':
+      default:
+        return 'square';
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+  function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = '#000';
+    this.fillStyle = '#000';
+
+    this.lineWidth = 1;
+    this.lineJoin = 'miter';
+    this.lineCap = 'butt';
+    this.miterLimit = Z * 1;
+    this.globalAlpha = 1;
+    this.canvas = surfaceElement;
+
+    var el = surfaceElement.ownerDocument.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+    this.lineScale_ = 1;
+  }
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = '';
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    var cp1 = this.getCoords_(aCP1x, aCP1y);
+    var cp2 = this.getCoords_(aCP2x, aCP2y);
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  // Helper function that takes the already fixed cordinates.
+  function bezierCurveTo(self, cp1, cp2, p) {
+    self.currentPath_.push({
+      type: 'bezierCurveTo',
+      cp1x: cp1.x,
+      cp1y: cp1.y,
+      cp2x: cp2.x,
+      cp2y: cp2.y,
+      x: p.x,
+      y: p.y
+    });
+    self.currentX_ = p.x;
+    self.currentY_ = p.y;
+  }
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // the following is lifted almost directly from
+    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+    var cp = this.getCoords_(aCPx, aCPy);
+    var p = this.getCoords_(aX, aY);
+
+    var cp1 = {
+      x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+      y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+    };
+    var cp2 = {
+      x: cp1.x + (p.x - this.currentX_) / 3.0,
+      y: cp1.y + (p.y - this.currentY_) / 3.0
+    };
+
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= Z;
+    var arcType = aClockwise ? 'at' : 'wa';
+
+    var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+    var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+    var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+    var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+    // IE won't render arches drawn counter clockwise if xStart == xEnd.
+    if (xStart == xEnd && !aClockwise) {
+      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+                       // that can be represented in binary
+    }
+
+    var p = this.getCoords_(aX, aY);
+    var pStart = this.getCoords_(xStart, yStart);
+    var pEnd = this.getCoords_(xEnd, yEnd);
+
+    this.currentPath_.push({type: arcType,
+                           x: p.x,
+                           y: p.y,
+                           radius: aRadius,
+                           xStart: pStart.x,
+                           yStart: pStart.y,
+                           xEnd: pEnd.x,
+                           yEnd: pEnd.y});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_('gradient');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+                                                   aX1, aY1, aR1) {
+    var gradient = new CanvasGradient_('gradientradial');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.r0_ = aR0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    gradient.r1_ = aR1;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function(image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+
+    // to find the original width we overide the width and height
+    var oldRuntimeWidth = image.runtimeStyle.width;
+    var oldRuntimeHeight = image.runtimeStyle.height;
+    image.runtimeStyle.width = 'auto';
+    image.runtimeStyle.height = 'auto';
+
+    // get the original size
+    var w = image.width;
+    var h = image.height;
+
+    // and remove overides
+    image.runtimeStyle.width = oldRuntimeWidth;
+    image.runtimeStyle.height = oldRuntimeHeight;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw Error('Invalid number of arguments');
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = sw / 2;
+    var h2 = sh / 2;
+
+    var vmlStr = [];
+
+    var W = 10;
+    var H = 10;
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="', Z * W, ',', Z * H, '"',
+                ' coordorigin="0,0"' ,
+                ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push('M11=', this.m_[0][0], ',',
+                  'M12=', this.m_[1][0], ',',
+                  'M21=', this.m_[0][1], ',',
+                  'M22=', this.m_[1][1], ',',
+                  'Dx=', mr(d.x / Z), ',',
+                  'Dy=', mr(d.y / Z), '');
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx + dw, dy);
+      var c3 = this.getCoords_(dx, dy + dh);
+      var c4 = this.getCoords_(dx + dw, dy + dh);
+
+      max.x = m.max(max.x, c2.x, c3.x, c4.x);
+      max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+                  'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+                  filter.join(''), ", sizingmethod='clip');")
+    } else {
+      vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', Z * dw, 'px;',
+                ' height:', Z * dh, 'px;"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML('BeforeEnd',
+                                    vmlStr.join(''));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a.color;
+    var opacity = a.alpha * this.globalAlpha;
+
+    var W = 10;
+    var H = 10;
+
+    lineStr.push('<g_vml_:shape',
+                 ' filled="', !!aFill, '"',
+                 ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+                 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+                 ' stroked="', !aFill, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+      var c;
+
+      switch (p.type) {
+        case 'moveTo':
+          c = p;
+          lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'lineTo':
+          lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'close':
+          lineStr.push(' x ');
+          p = null;
+          break;
+        case 'bezierCurveTo':
+          lineStr.push(' c ',
+                       mr(p.cp1x), ',', mr(p.cp1y), ',',
+                       mr(p.cp2x), ',', mr(p.cp2y), ',',
+                       mr(p.x), ',', mr(p.y));
+          break;
+        case 'at':
+        case 'wa':
+          lineStr.push(' ', p.type, ' ',
+                       mr(p.x - this.arcScaleX_ * p.radius), ',',
+                       mr(p.y - this.arcScaleY_ * p.radius), ' ',
+                       mr(p.x + this.arcScaleX_ * p.radius), ',',
+                       mr(p.y + this.arcScaleY_ * p.radius), ' ',
+                       mr(p.xStart), ',', mr(p.yStart), ' ',
+                       mr(p.xEnd), ',', mr(p.yEnd));
+          break;
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if (p) {
+        if (min.x == null || p.x < min.x) {
+          min.x = p.x;
+        }
+        if (max.x == null || p.x > max.x) {
+          max.x = p.x;
+        }
+        if (min.y == null || p.y < min.y) {
+          min.y = p.y;
+        }
+        if (max.y == null || p.y > max.y) {
+          max.y = p.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (!aFill) {
+      var lineWidth = this.lineScale_ * this.lineWidth;
+
+      // VML cannot correctly render a line if the width is less than 1px.
+      // In that case, we dilute the color to make the line look thinner.
+      if (lineWidth < 1) {
+        opacity *= lineWidth;
+      }
+
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity, '"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap), '"',
+        ' weight="', lineWidth, 'px"',
+        ' color="', color, '" />'
+      );
+    } else if (typeof this.fillStyle == 'object') {
+      var fillStyle = this.fillStyle;
+      var angle = 0;
+      var focus = {x: 0, y: 0};
+
+      // additional offset
+      var shift = 0;
+      // scale factor for offset
+      var expansion = 1;
+
+      if (fillStyle.type_ == 'gradient') {
+        var x0 = fillStyle.x0_ / this.arcScaleX_;
+        var y0 = fillStyle.y0_ / this.arcScaleY_;
+        var x1 = fillStyle.x1_ / this.arcScaleX_;
+        var y1 = fillStyle.y1_ / this.arcScaleY_;
+        var p0 = this.getCoords_(x0, y0);
+        var p1 = this.getCoords_(x1, y1);
+        var dx = p1.x - p0.x;
+        var dy = p1.y - p0.y;
+        angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+        // The angle should be a non-negative number.
+        if (angle < 0) {
+          angle += 360;
+        }
+
+        // Very small angles produce an unexpected result because they are
+        // converted to a scientific notation string.
+        if (angle < 1e-6) {
+          angle = 0;
+        }
+      } else {
+        var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
+        var width  = max.x - min.x;
+        var height = max.y - min.y;
+        focus = {
+          x: (p0.x - min.x) / width,
+          y: (p0.y - min.y) / height
+        };
+
+        width  /= this.arcScaleX_ * Z;
+        height /= this.arcScaleY_ * Z;
+        var dimension = m.max(width, height);
+        shift = 2 * fillStyle.r0_ / dimension;
+        expansion = 2 * fillStyle.r1_ / dimension - shift;
+      }
+
+      // We need to sort the color stops in ascending order by offset,
+      // otherwise IE won't interpret it correctly.
+      var stops = fillStyle.colors_;
+      stops.sort(function(cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      var length = stops.length;
+      var color1 = stops[0].color;
+      var color2 = stops[length - 1].color;
+      var opacity1 = stops[0].alpha * this.globalAlpha;
+      var opacity2 = stops[length - 1].alpha * this.globalAlpha;
+
+      var colors = [];
+      for (var i = 0; i < length; i++) {
+        var stop = stops[i];
+        colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+      }
+
+      // When colors attribute is used, the meanings of opacity and o:opacity2
+      // are reversed.
+      lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+                   ' method="none" focus="100%"',
+                   ' color="', color1, '"',
+                   ' color2="', color2, '"',
+                   ' colors="', colors.join(','), '"',
+                   ' opacity="', opacity2, '"',
+                   ' g_o_:opacity2="', opacity1, '"',
+                   ' angle="', angle, '"',
+                   ' focusposition="', focus.x, ',', focus.y, '" />');
+    } else {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+                   '" />');
+    }
+
+    lineStr.push('</g_vml_:shape>');
+
+    this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  }
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: 'close'});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    var m = this.m_;
+    return {
+      x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+      y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  function matrixIsFinite(m) {
+    for (var j = 0; j < 3; j++) {
+      for (var k = 0; k < 2; k++) {
+        if (!isFinite(m[j][k]) || isNaN(m[j][k])) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  function setM(ctx, m, updateLineScale) {
+    if (!matrixIsFinite(m)) {
+      return;
+    }
+    ctx.m_ = m;
+
+    if (updateLineScale) {
+      // Get the line scale.
+      // Determinant of this.m_ means how much the area is enlarged by the
+      // transformation. So its square root can be used as a scale factor
+      // for width.
+      var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+      ctx.lineScale_ = sqrt(abs(det));
+    }
+  }
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), false);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), false);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), true);
+  };
+
+  contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+    var m1 = [
+      [m11, m12, 0],
+      [m21, m22, 0],
+      [dx,  dy,  1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), true);
+  };
+
+  contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+    var m = [
+      [m11, m12, 0],
+      [m21, m22, 0],
+      [dx,  dy,  1]
+    ];
+
+    setM(this, m, true);
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.x0_ = 0;
+    this.y0_ = 0;
+    this.r0_ = 0;
+    this.x1_ = 0;
+    this.y1_ = 0;
+    this.r1_ = 0;
+    this.colors_ = [];
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: aOffset,
+                       color: aColor.color,
+                       alpha: aColor.alpha});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
diff --git a/lib/excanvas.min.js b/lib/excanvas.min.js
new file mode 100644 (file)
index 0000000..a34ca1d
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+document.createElement("canvas").getContext||(function(){var s=Math,j=s.round,F=s.sin,G=s.cos,V=s.abs,W=s.sqrt,k=10,v=k/2;function X(){return this.context_||(this.context_=new H(this))}var L=Array.prototype.slice;function Y(b,a){var c=L.call(arguments,2);return function(){return b.apply(a,c.concat(L.call(arguments)))}}var M={init:function(b){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var a=b||document;a.createElement("canvas");a.attachEvent("onreadystatechange",Y(this.init_,this,a))}},init_:function(b){b.namespaces.g_vml_||
+b.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML");b.namespaces.g_o_||b.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML");if(!b.styleSheets.ex_canvas_){var a=b.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=b.getElementsByTagName("canvas"),d=0;for(;d<c.length;d++)this.initElement(c[d])},
+initElement:function(b){if(!b.getContext){b.getContext=X;b.innerHTML="";b.attachEvent("onpropertychange",Z);b.attachEvent("onresize",$);var a=b.attributes;if(a.width&&a.width.specified)b.style.width=a.width.nodeValue+"px";else b.width=b.clientWidth;if(a.height&&a.height.specified)b.style.height=a.height.nodeValue+"px";else b.height=b.clientHeight}return b}};function Z(b){var a=b.srcElement;switch(b.propertyName){case "width":a.style.width=a.attributes.width.nodeValue+"px";a.getContext().clearRect();
+break;case "height":a.style.height=a.attributes.height.nodeValue+"px";a.getContext().clearRect();break}}function $(b){var a=b.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}M.init();var N=[],B=0;for(;B<16;B++){var C=0;for(;C<16;C++)N[B*16+C]=B.toString(16)+C.toString(16)}function I(){return[[1,0,0],[0,1,0],[0,0,1]]}function y(b,a){var c=I(),d=0;for(;d<3;d++){var f=0;for(;f<3;f++){var h=0,g=0;for(;g<3;g++)h+=b[d][g]*a[g][f];c[d][f]=
+h}}return c}function O(b,a){a.fillStyle=b.fillStyle;a.lineCap=b.lineCap;a.lineJoin=b.lineJoin;a.lineWidth=b.lineWidth;a.miterLimit=b.miterLimit;a.shadowBlur=b.shadowBlur;a.shadowColor=b.shadowColor;a.shadowOffsetX=b.shadowOffsetX;a.shadowOffsetY=b.shadowOffsetY;a.strokeStyle=b.strokeStyle;a.globalAlpha=b.globalAlpha;a.arcScaleX_=b.arcScaleX_;a.arcScaleY_=b.arcScaleY_;a.lineScale_=b.lineScale_}function P(b){var a,c=1;b=String(b);if(b.substring(0,3)=="rgb"){var d=b.indexOf("(",3),f=b.indexOf(")",d+
+1),h=b.substring(d+1,f).split(",");a="#";var g=0;for(;g<3;g++)a+=N[Number(h[g])];if(h.length==4&&b.substr(3,1)=="a")c=h[3]}else a=b;return{color:a,alpha:c}}function aa(b){switch(b){case "butt":return"flat";case "round":return"round";case "square":default:return"square"}}function H(b){this.m_=I();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=k*1;this.globalAlpha=1;this.canvas=b;
+var a=b.ownerDocument.createElement("div");a.style.width=b.clientWidth+"px";a.style.height=b.clientHeight+"px";a.style.overflow="hidden";a.style.position="absolute";b.appendChild(a);this.element_=a;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}var i=H.prototype;i.clearRect=function(){this.element_.innerHTML=""};i.beginPath=function(){this.currentPath_=[]};i.moveTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"moveTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};
+i.lineTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"lineTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};i.bezierCurveTo=function(b,a,c,d,f,h){var g=this.getCoords_(f,h),l=this.getCoords_(b,a),e=this.getCoords_(c,d);Q(this,l,e,g)};function Q(b,a,c,d){b.currentPath_.push({type:"bezierCurveTo",cp1x:a.x,cp1y:a.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});b.currentX_=d.x;b.currentY_=d.y}i.quadraticCurveTo=function(b,a,c,d){var f=this.getCoords_(b,a),h=this.getCoords_(c,d),g={x:this.currentX_+
+0.6666666666666666*(f.x-this.currentX_),y:this.currentY_+0.6666666666666666*(f.y-this.currentY_)};Q(this,g,{x:g.x+(h.x-this.currentX_)/3,y:g.y+(h.y-this.currentY_)/3},h)};i.arc=function(b,a,c,d,f,h){c*=k;var g=h?"at":"wa",l=b+G(d)*c-v,e=a+F(d)*c-v,m=b+G(f)*c-v,r=a+F(f)*c-v;if(l==m&&!h)l+=0.125;var n=this.getCoords_(b,a),o=this.getCoords_(l,e),q=this.getCoords_(m,r);this.currentPath_.push({type:g,x:n.x,y:n.y,radius:c,xStart:o.x,yStart:o.y,xEnd:q.x,yEnd:q.y})};i.rect=function(b,a,c,d){this.moveTo(b,
+a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath()};i.strokeRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.stroke();this.currentPath_=f};i.fillRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.fill();this.currentPath_=f};i.createLinearGradient=function(b,
+a,c,d){var f=new D("gradient");f.x0_=b;f.y0_=a;f.x1_=c;f.y1_=d;return f};i.createRadialGradient=function(b,a,c,d,f,h){var g=new D("gradientradial");g.x0_=b;g.y0_=a;g.r0_=c;g.x1_=d;g.y1_=f;g.r1_=h;return g};i.drawImage=function(b){var a,c,d,f,h,g,l,e,m=b.runtimeStyle.width,r=b.runtimeStyle.height;b.runtimeStyle.width="auto";b.runtimeStyle.height="auto";var n=b.width,o=b.height;b.runtimeStyle.width=m;b.runtimeStyle.height=r;if(arguments.length==3){a=arguments[1];c=arguments[2];h=g=0;l=d=n;e=f=o}else if(arguments.length==
+5){a=arguments[1];c=arguments[2];d=arguments[3];f=arguments[4];h=g=0;l=n;e=o}else if(arguments.length==9){h=arguments[1];g=arguments[2];l=arguments[3];e=arguments[4];a=arguments[5];c=arguments[6];d=arguments[7];f=arguments[8]}else throw Error("Invalid number of arguments");var q=this.getCoords_(a,c),t=[];t.push(" <g_vml_:group",' coordsize="',k*10,",",k*10,'"',' coordorigin="0,0"',' style="width:',10,"px;height:",10,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]){var E=[];E.push("M11=",
+this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",j(q.x/k),",","Dy=",j(q.y/k),"");var p=q,z=this.getCoords_(a+d,c),w=this.getCoords_(a,c+f),x=this.getCoords_(a+d,c+f);p.x=s.max(p.x,z.x,w.x,x.x);p.y=s.max(p.y,z.y,w.y,x.y);t.push("padding:0 ",j(p.x/k),"px ",j(p.y/k),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",E.join(""),", sizingmethod='clip');")}else t.push("top:",j(q.y/k),"px;left:",j(q.x/k),"px;");t.push(' ">','<g_vml_:image src="',b.src,
+'"',' style="width:',k*d,"px;"," height:",k*f,'px;"',' cropleft="',h/n,'"',' croptop="',g/o,'"',' cropright="',(n-h-l)/n,'"',' cropbottom="',(o-g-e)/o,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",t.join(""))};i.stroke=function(b){var a=[],c=P(b?this.fillStyle:this.strokeStyle),d=c.color,f=c.alpha*this.globalAlpha;a.push("<g_vml_:shape",' filled="',!!b,'"',' style="position:absolute;width:',10,"px;height:",10,'px;"',' coordorigin="0 0" coordsize="',k*10," ",k*10,'"',' stroked="',
+!b,'"',' path="');var h={x:null,y:null},g={x:null,y:null},l=0;for(;l<this.currentPath_.length;l++){var e=this.currentPath_[l];switch(e.type){case "moveTo":a.push(" m ",j(e.x),",",j(e.y));break;case "lineTo":a.push(" l ",j(e.x),",",j(e.y));break;case "close":a.push(" x ");e=null;break;case "bezierCurveTo":a.push(" c ",j(e.cp1x),",",j(e.cp1y),",",j(e.cp2x),",",j(e.cp2y),",",j(e.x),",",j(e.y));break;case "at":case "wa":a.push(" ",e.type," ",j(e.x-this.arcScaleX_*e.radius),",",j(e.y-this.arcScaleY_*e.radius),
+" ",j(e.x+this.arcScaleX_*e.radius),",",j(e.y+this.arcScaleY_*e.radius)," ",j(e.xStart),",",j(e.yStart)," ",j(e.xEnd),",",j(e.yEnd));break}if(e){if(h.x==null||e.x<h.x)h.x=e.x;if(g.x==null||e.x>g.x)g.x=e.x;if(h.y==null||e.y<h.y)h.y=e.y;if(g.y==null||e.y>g.y)g.y=e.y}}a.push(' ">');if(b)if(typeof this.fillStyle=="object"){var m=this.fillStyle,r=0,n={x:0,y:0},o=0,q=1;if(m.type_=="gradient"){var t=m.x1_/this.arcScaleX_,E=m.y1_/this.arcScaleY_,p=this.getCoords_(m.x0_/this.arcScaleX_,m.y0_/this.arcScaleY_),
+z=this.getCoords_(t,E);r=Math.atan2(z.x-p.x,z.y-p.y)*180/Math.PI;if(r<0)r+=360;if(r<1.0E-6)r=0}else{var p=this.getCoords_(m.x0_,m.y0_),w=g.x-h.x,x=g.y-h.y;n={x:(p.x-h.x)/w,y:(p.y-h.y)/x};w/=this.arcScaleX_*k;x/=this.arcScaleY_*k;var R=s.max(w,x);o=2*m.r0_/R;q=2*m.r1_/R-o}var u=m.colors_;u.sort(function(ba,ca){return ba.offset-ca.offset});var J=u.length,da=u[0].color,ea=u[J-1].color,fa=u[0].alpha*this.globalAlpha,ga=u[J-1].alpha*this.globalAlpha,S=[],l=0;for(;l<J;l++){var T=u[l];S.push(T.offset*q+
+o+" "+T.color)}a.push('<g_vml_:fill type="',m.type_,'"',' method="none" focus="100%"',' color="',da,'"',' color2="',ea,'"',' colors="',S.join(","),'"',' opacity="',ga,'"',' g_o_:opacity2="',fa,'"',' angle="',r,'"',' focusposition="',n.x,",",n.y,'" />')}else a.push('<g_vml_:fill color="',d,'" opacity="',f,'" />');else{var K=this.lineScale_*this.lineWidth;if(K<1)f*=K;a.push("<g_vml_:stroke",' opacity="',f,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',aa(this.lineCap),
+'"',' weight="',K,'px"',' color="',d,'" />')}a.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",a.join(""))};i.fill=function(){this.stroke(true)};i.closePath=function(){this.currentPath_.push({type:"close"})};i.getCoords_=function(b,a){var c=this.m_;return{x:k*(b*c[0][0]+a*c[1][0]+c[2][0])-v,y:k*(b*c[0][1]+a*c[1][1]+c[2][1])-v}};i.save=function(){var b={};O(this,b);this.aStack_.push(b);this.mStack_.push(this.m_);this.m_=y(I(),this.m_)};i.restore=function(){O(this.aStack_.pop(),
+this);this.m_=this.mStack_.pop()};function ha(b){var a=0;for(;a<3;a++){var c=0;for(;c<2;c++)if(!isFinite(b[a][c])||isNaN(b[a][c]))return false}return true}function A(b,a,c){if(!!ha(a)){b.m_=a;if(c)b.lineScale_=W(V(a[0][0]*a[1][1]-a[0][1]*a[1][0]))}}i.translate=function(b,a){A(this,y([[1,0,0],[0,1,0],[b,a,1]],this.m_),false)};i.rotate=function(b){var a=G(b),c=F(b);A(this,y([[a,c,0],[-c,a,0],[0,0,1]],this.m_),false)};i.scale=function(b,a){this.arcScaleX_*=b;this.arcScaleY_*=a;A(this,y([[b,0,0],[0,a,
+0],[0,0,1]],this.m_),true)};i.transform=function(b,a,c,d,f,h){A(this,y([[b,a,0],[c,d,0],[f,h,1]],this.m_),true)};i.setTransform=function(b,a,c,d,f,h){A(this,[[b,a,0],[c,d,0],[f,h,1]],true)};i.clip=function(){};i.arcTo=function(){};i.createPattern=function(){return new U};function D(b){this.type_=b;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}D.prototype.addColorStop=function(b,a){a=P(a);this.colors_.push({offset:b,color:a.color,alpha:a.alpha})};function U(){}G_vmlCanvasManager=
+M;CanvasRenderingContext2D=H;CanvasGradient=D;CanvasPattern=U})();
diff --git a/lib/functional.js b/lib/functional.js
new file mode 100644 (file)
index 0000000..c1742f3
--- /dev/null
@@ -0,0 +1,1050 @@
+/*
+ * Author: Oliver Steele
+ * Copyright: Copyright 2007 by Oliver Steele.  All rights reserved.
+ * License: MIT License
+ * Homepage: http://osteele.com/javascripts/functional
+ * Source: http://osteele.com/javascripts/functional/functional.js
+ * Changes: http://osteele.com/javascripts/functional/CHANGES
+ * Created: 2007-07-11
+ * Version: 1.0.2
+ *
+ *
+ * This file defines some higher-order methods and functions for
+ * functional and function-level programming.
+ */
+
+/// `Functional` is the namespace for higher-order functions.
+var Functional = this.Functional || {};
+
+/**
+ * This function copies all the public functions in `Functional` except itself
+ * into the global namespace.  If the optional argument $except$ is present,
+ * functions named by its property names are not copied.
+ * >> Functional.install()
+ */
+Functional.install = function(except) {
+    var source = Functional,
+        target = (function() { return this; })();  // References the global object.
+    for (var name in source)
+        name == 'install'
+        || name.charAt(0) == '_'
+        || except && name in except
+        || !source.hasOwnProperty(name) // work around Prototype
+        || (target[name] = source[name]);
+}
+
+/// ^ Higher-order functions
+
+/**
+ * Returns a function that applies the last argument of this
+ * function to its input, and the penultimate argument to the
+ * result of the application, and so on.
+ * == compose(f1, f2, f3..., fn)(args) == f1(f2(f3(...(fn(args...)))))
+ * :: (a2 -> a1) (a3 -> a2)... (a... -> a_{n}) -> a... -> a1
+ * >> compose('1+', '2*')(2) -> 5
+ */
+Functional.compose = function(/*fn...*/) {
+    var fns = Functional.map(Function.toFunction, arguments),
+        arglen = fns.length;
+    return function() {
+        for (var i = arglen; --i >= 0; )
+            arguments = [fns[i].apply(this, arguments)];
+        return arguments[0];
+    }
+}
+
+/**
+ * Same as `compose`, except applies the functions in argument-list order.
+ * == sequence(f1, f2, f3..., fn)(args...) == fn(...(f3(f2(f1(args...)))))
+ * :: (a... -> a1) (a1 -> a2) (a2 -> a3)... (a_{n-1} -> a_{n})  -> a... -> a_{n}
+ * >> sequence('1+', '2*')(2) -> 6
+ */
+Functional.sequence = function(/*fn...*/) {
+    var fns = Functional.map(Function.toFunction, arguments),
+        arglen = fns.length;
+    return function() {
+        for (var i = 0; i < arglen; i++)
+            arguments = [fns[i].apply(this, arguments)];
+        return arguments[0];
+    }
+}
+
+/**
+ * Applies `fn` to each element of `sequence`.
+ * == map(f, [x1, x2...]) = [f(x, 0), f(x2, 1), ...]
+ * :: (a ix -> boolean) [a] -> [a]
+ * >> map('1+', [1,2,3]) -> [2, 3, 4]
+ *
+ * If `object` is supplied, it is the object of the call.
+ *
+ * The fusion rule:
+ * >> map('+1', map('*2', [1,2,3])) -> [3, 5, 7]
+ * >> map(compose('+1', '*2'), [1,2,3]) -> [3, 5, 7]
+ */
+Functional.map = function(fn, sequence, object) {
+    fn = Function.toFunction(fn);
+    var len = sequence.length,
+        result = new Array(len);
+    for (var i = 0; i < len; i++)
+        result[i] = fn.apply(object, [sequence[i], i]);
+    return result;
+}
+
+/**
+ * Applies `fn` to `init` and the first element of `sequence`,
+ * and then to the result and the second element, and so on.
+ * == reduce(f, init, [x0, x1, x2]) == f(f(f(init, x0), x1), x2)
+ * :: (a b -> a) a [b] -> a
+ * >> reduce('x y -> 2*x+y', 0, [1,0,1,0]) -> 10
+ */
+Functional.reduce = function(fn, init, sequence, object) {
+    fn = Function.toFunction(fn);
+    var len = sequence.length,
+        result = init;
+    for (var i = 0; i < len; i++)
+        result = fn.apply(object, [result, sequence[i]]);
+    return result;
+}
+
+/**
+ * Returns a list of those elements $x$ of `sequence` such that
+ * $fn(x)$ returns true.
+ * :: (a -> boolean) [a] -> [a]
+ * >> select('%2', [1,2,3,4]) -> [1, 3]
+ */
+Functional.select = function(fn, sequence, object) {
+    fn = Function.toFunction(fn);
+    var len = sequence.length,
+        result = [];
+    for (var i = 0; i < len; i++) {
+        var x = sequence[i];
+        fn.apply(object, [x, i]) && result.push(x);
+    }
+    return result;
+}
+
+/// A synonym for `select`.
+Functional.filter = Functional.select;
+
+/// A synonym for `reduce`.
+Functional.foldl = Functional.reduce;
+
+/**
+ * Same as `foldl`, but applies the function from right to left.
+ * == foldr(f, init, [x0, x1, x2]) == fn(x0, f(x1, f(x2, init)))
+ * :: (a b -> b) b [a] -> b
+ * >> foldr('x y -> 2*x+y', 100, [1,0,1,0]) -> 104
+ */
+Functional.foldr = function(fn, init, sequence, object) {
+    fn = Function.toFunction(fn);
+    var len = sequence.length,
+        result = init;
+    for (var i = len; --i >= 0; )
+        result = fn.apply(object, [sequence[i], result]);
+    return result;
+}
+
+/// ^^ Predicates
+
+/**
+ * Returns a function that returns `true` when all the arguments, applied
+ * to the returned function's arguments, returns true.
+ * == and(f1, f2...)(args...) == f1(args...) && f2(args...)...
+ * :: [a -> boolean] a -> a
+ * >> and('>1', '>2')(2) -> false
+ * >> and('>1', '>2')(3) -> true
+ * >> and('>1', 'error()')(1) -> false
+ */
+Functional.and = function(/*functions...*/) {
+    var args = Functional.map(Function.toFunction, arguments),
+        arglen = args.length;
+    return function() {
+        var value = true;
+        for (var i = 0; i < arglen; i++)
+            if (!(value = args[i].apply(this, arguments)))
+                break;
+        return value;
+    }
+}
+
+/**
+ * Returns a function that returns `true` when any argument, applied
+ * to the returned function's arguments, returns true.
+ * == or(f1, f2...)(args...) == f1(args...) || f2(args...)...
+ * :: [a -> boolean] a -> a
+ * >> or('>1', '>2')(1) -> false
+ * >> or('>1', '>2')(2) -> true
+ * >> or('>1', 'error()')(2) -> true
+ */
+Functional.or = function(/*functions...*/) {
+    var args = Functional.map(Function.toFunction, arguments),
+        arglen = args.length;
+    return function() {
+        var value = false;
+        for (var i = 0; i < arglen; i++)
+            if ((value = args[i].apply(this, arguments)))
+                break;
+        return value;
+    }
+}
+
+/**
+ * Returns true when $fn(x)$ returns true for some element $x$ of
+ * `sequence`.  The returned function short-circuits.
+ * == some(f, [x1, x2, x3, ...]) == f(x1) || f(x2) || f(x3)...
+ * :: (a -> boolean) [a] -> boolean
+ * >> some('>2', [1,2,3]) -> true
+ * >> some('>10', [1,2,3]) -> false
+ */
+Functional.some = function(fn, sequence, object) {
+    fn = Function.toFunction(fn);
+    var len = sequence.length,
+        value = false;
+    for (var i = 0; i < len; i++)
+        if ((value = fn.call(object, sequence[i])))
+            break;
+    return value;
+}
+
+/**
+ * Returns true when $fn(x)$ returns true for every element $x$ of
+ * `sequence`.  The returned function short-circuits.
+ * == every(f, [x1, x2, x3, ...]) == f(x1) && f(x2) && f(x3)...
+ * :: (a -> boolean) [a] -> boolean
+ * >> every('<2', [1,2,3]) -> false
+ * >> every('<10', [1,2,3]) -> true
+ */
+Functional.every = function(fn, sequence, object) {
+    fn = Function.toFunction(fn);
+    var len = sequence.length,
+        value = true;
+    for (var i = 0; i < len; i++)
+        if (!(value = fn.call(object, sequence[i])))
+            break;
+    return value;
+}
+
+/**
+ * Returns a function that returns `true` when $fn()$ returns false.
+ * == f.not()(args...) == !f(args...)
+ * :: (a -> boolean) -> (a -> boolean)
+ * >> not(Functional.K(true))() -> false
+ * >> not(Functional.K(false))() -> true
+ */
+Functional.not = function(fn) {
+    fn = Function.toFunction(fn);
+    return function() {
+        return !fn.apply(null, arguments);
+    }
+}
+
+/**
+ * Returns a function that returns true when this function's arguments
+ * applied to that functions are always the same.  The returned function
+ * short-circuits.
+ * == equal(f1, f2...)(args...) == f1(args...) == f2(args...)...
+ * :: [a... -> b] -> a... -> b
+ * >> equal()() -> true
+ * >> equal(K(1))() -> true
+ * >> equal(K(1), K(1))() -> true
+ * >> equal(K(1), K(2))() -> false
+ * >> equal(K(1), K(2), 'error()')() -> false
+ */
+Functional.equal = function(/*fn...*/) {
+    var arglen = arguments.length,
+        args = Functional.map(Function.toFunction, arguments);
+    if (!arglen) return Functional.K(true);
+    // if arglen == 1 it's also constant true, but
+    // call it for effect.
+    return function() {
+        var value = args[0].apply(this, arguments);
+        for (var i = 1; i < arglen; i++)
+            if (value != args[i].apply(this, args))
+                return false;
+        return true;
+    }
+}
+
+
+/// ^^ Utilities
+
+/**
+  * Returns its argument coerced to a function.
+  * >> lambda('1+')(2) -> 3
+  * >> lambda(function(n){return n+1})(2) -> 3
+  */
+Functional.lambda = function(object) {
+    return object.toFunction();
+}
+
+
+/**
+ * Returns a function that takes an object as an argument, and applies
+ * `object`'s `methodName` method to `arguments`.
+ * == invoke(name)(object, args...) == object[name](args...)
+ * :: name args... -> object args2... -> object[name](args... args2...)
+ * >> invoke('toString')(123) -> "123"
+ */
+Functional.invoke = function(methodName/*, arguments*/) {
+    var args = Array.slice(arguments, 1);
+    return function(object) {
+        return object[methodName].apply(object, Array.slice(arguments, 1).concat(args));
+    }
+}
+
+/**
+ * Returns a function that takes an object, and returns the value of its
+ * `name` property.  `pluck(name)` is equivalent to `'_.name'.lambda()`.
+ * == pluck(name)(object) == object[name]
+ * :: name -> object -> object[name]
+ * >> pluck('length')("abc") -> 3
+ */
+Functional.pluck = function(name) {
+    return function(object) {
+        return object[name];
+    }
+}
+
+/**
+ * Returns a function that, while $pred(value)$ is true, applies `fn` to
+ * $value$ to produce a new value, which is used as an input for the next round.
+ * The returned function returns the first $value$ for which $pred(value)$
+ * is false.
+ * :: (a -> boolean) (a -> a) -> a
+ * >> until('>10', '2*')(1) -> 16
+ */
+Functional.until = function(pred, fn) {
+    fn = Function.toFunction(fn);
+    pred = Function.toFunction(pred);
+    return function(value) {
+        while (!pred.call(null, value))
+            value = fn.call(null, value);
+        return value;
+    }
+}
+
+/**
+ * :: [a] [b]... -> [[a b]...]
+ * == zip(a, b...) == [[a0, b0], [a1, b1], ...]
+ * Did you know that `zip` can transpose a matrix?
+ * >> zip.apply(null, [[1,2],[3,4]]) -> [[1, 3], [2, 4]]
+ */
+Functional.zip = function(/*args...*/) {
+    var n = Math.min.apply(null, Functional.map('.length', arguments));
+    var results = new Array(n);
+    for (var i = 0; i < n; i++) {
+        var key = String(i);
+        results[key] = Functional.map(pluck(key), arguments);
+    };
+    return results;
+}
+
+Functional._startRecordingMethodChanges = function(object) {
+    var initialMethods = {};
+    for (var name in object)
+        initialMethods[name] = object[name];
+    return {getChangedMethods: function() {
+        var changedMethods = {};
+        for (var name in object)
+        if (object[name] != initialMethods[name])
+            changedMethods[name] = object[name];
+        return changedMethods;
+    }};
+}
+
+// For each method that this file defined on `Function.prototype`,
+// define a function on `Functional` that delegates to it.
+Functional._attachMethodDelegates = function(methods) {
+    for (var name in methods)
+        Functional[name] = Functional[name] || (function(name) {
+            var fn = methods[name];
+            return function(object) {
+                return fn.apply(Function.toFunction(object), Array.slice(arguments, 1));
+            }
+        })(name);
+}
+
+// Record the current contents of `Function.prototype`, so that we
+// can see what we've added later.
+Functional.__initalFunctionState = Functional._startRecordingMethodChanges(Function.prototype);
+
+/// ^ Higher-order methods
+
+/// ^^ Partial function application
+
+/**
+ * Returns a bound method on `object`, optionally currying `args`.
+ * == f.bind(obj, args...)(args2...) == f.apply(obj, [args..., args2...])
+ */
+Function.prototype.bind = function(object/*, args...*/) {
+    var fn = this;
+    var args = Array.slice(arguments, 1);
+    return function() {
+        return fn.apply(object, args.concat(Array.slice(arguments, 0)));
+    }
+}
+
+/**
+ * Returns a function that applies the underlying function to `args`, and
+ * ignores its own arguments.
+ * :: (a... -> b) a... -> (... -> b)
+ * == f.saturate(args...)(args2...) == f(args...)
+ * >> Math.max.curry(1, 2)(3, 4) -> 4
+ * >> Math.max.saturate(1, 2)(3, 4) -> 2
+ * >> Math.max.curry(1, 2).saturate()(3, 4) -> 2
+ */
+Function.prototype.saturate = function(/*args*/) {
+    var fn = this;
+    var args = Array.slice(arguments, 0);
+    return function() {
+        return fn.apply(this, args);
+    }
+}
+
+/**
+ * Invoking the function returned by this function only passes `n`
+ * arguments to the underlying function.  If the underlying function
+ * is not saturated, the result is a function that passes all its
+ * arguments to the underlying function.  (That is, `aritize` only
+ * affects its immediate caller, and not subsequent calls.)
+ * >> '[a,b]'.lambda()(1,2) -> [1, 2]
+ * >> '[a,b]'.lambda().aritize(1)(1,2) -> [1, undefined]
+ * >> '+'.lambda()(1,2)(3) -> error
+ * >> '+'.lambda().ncurry(2).aritize(1)(1,2)(3) -> 4
+ *
+ * `aritize` is useful to remove optional arguments from a function that
+ * is passed to a higher-order function that supplies *different* optional
+ * arguments.
+ *
+ * For example, many implementations of `map` and other collection
+ * functions, including those in this library, call the function argument
+ *  with both the collection element
+ * and its position.  This is convenient when expected, but can wreak
+ * havoc when the function argument is a curried function that expects
+ * a single argument from `map` and the remaining arguments from when
+ * the result of `map` is applied.
+ */
+Function.prototype.aritize = function(n) {
+    var fn = this;
+    return function() {
+        return fn.apply(this, Array.slice(arguments, 0, n));
+    }
+}
+
+/**
+ * Returns a function that, applied to an argument list $arg2$,
+ * applies the underlying function to $args ++ arg2$.
+ * :: (a... b... -> c) a... -> (b... -> c)
+ * == f.curry(args1...)(args2...) == f(args1..., args2...)
+ *
+ * Note that, unlike in languages with true partial application such as Haskell,
+ * `curry` and `uncurry` are not inverses.  This is a repercussion of the
+ * fact that in JavaScript, unlike Haskell, a fully saturated function is
+ * not equivalent to the value that it returns.  The definition of `curry`
+ * here matches semantics that most people have used when implementing curry
+ * for procedural languages.
+ *
+ * This implementation is adapted from
+ * [http://www.coryhudson.com/blog/2007/03/10/javascript-currying-redux/].
+ */
+Function.prototype.curry = function(/*args...*/) {
+    var fn = this;
+    var args = Array.slice(arguments, 0);
+    return function() {
+        return fn.apply(this, args.concat(Array.slice(arguments, 0)));
+    };
+}
+
+/*
+ * Right curry.  Returns a function that, applied to an argument list $args2$,
+ * applies the underlying function to $args2 + args$.
+ * == f.curry(args1...)(args2...) == f(args2..., args1...)
+ * :: (a... b... -> c) b... -> (a... -> c)
+ */
+Function.prototype.rcurry = function(/*args...*/) {
+    var fn = this;
+    var args = Array.slice(arguments, 0);
+    return function() {
+        return fn.apply(this, Array.slice(arguments, 0).concat(args));
+    };
+}
+
+/**
+ * Same as `curry`, except only applies the function when all
+ * `n` arguments are saturated.
+ */
+Function.prototype.ncurry = function(n/*, args...*/) {
+    var fn = this;
+    var largs = Array.slice(arguments, 1);
+    return function() {
+        var args = largs.concat(Array.slice(arguments, 0));
+        if (args.length < n) {
+            args.unshift(n);
+            return fn.ncurry.apply(fn, args);
+        }
+        return fn.apply(this, args);
+    };
+}
+
+/**
+ * Same as `rcurry`, except only applies the function when all
+ * `n` arguments are saturated.
+ */
+Function.prototype.rncurry = function(n/*, args...*/) {
+    var fn = this;
+    var rargs = Array.slice(arguments, 1);
+    return function() {
+        var args = Array.slice(arguments, 0).concat(rargs);
+        if (args.length < n) {
+            args.unshift(n);
+            return fn.rncurry.apply(fn, args);
+        }
+        return fn.apply(this, args);
+    };
+}
+
+/**
+ * `_` (underscore) is bound to a unique value for use in `partial`, below.
+ * This is a global variable, but it's also a property of `Function` in case
+ * you overwrite or bind over the global one.
+ */
+_ = Function._ = {};
+
+/**
+ * Returns a function $f$ such that $f(args2)$ is equivalent to
+ * the underlying function applied to a combination of $args$ and $args2$.
+ *
+ * `args` is a partially-specified argument: it's a list with "holes",
+ * specified by the special value `_`.  It is combined with $args2$ as
+ * follows:
+ *
+ * From left to right, each value in $args2$ fills in the leftmost
+ * remaining hole in `args`.  Any remaining values
+ * in $args2$ are appended to the result of the filling-in process
+ * to produce the combined argument list.
+ *
+ * If the combined argument list contains any occurrences of `_`, the result
+ * of the application of $f$ is another partial function.  Otherwise, the
+ * result is the same as the result of applying the underlying function to
+ * the combined argument list.
+ */
+Function.prototype.partial = function(/*args*/) {
+    var fn = this;
+    var _ = Function._;
+    var args = Array.slice(arguments, 0);
+    //substitution positions
+    var subpos = [], value;
+    for (var i = 0; i < arguments.length; i++)
+        arguments[i] == _ && subpos.push(i);
+    return function() {
+        var specialized = args.concat(Array.slice(arguments, subpos.length));
+        for (var i = 0; i < Math.min(subpos.length, arguments.length); i++)
+            specialized[subpos[i]] = arguments[i];
+        for (var i = 0; i < specialized.length; i++)
+            if (specialized[i] == _)
+                return fn.partial.apply(fn, specialized);
+        return fn.apply(this, specialized);
+    }
+}
+
+/// ^^ Combinators
+
+/// ^^^ Combinator Functions
+
+/**
+ * The identity function: $x -> x$.
+ * == I(x) == x
+ * == I == 'x'.lambda()
+ * :: a -> a
+ * >> Functional.I(1) -> 1
+ */
+Functional.I = function(x) {return x};
+
+/**
+ * Returns a constant function that returns `x`.
+ * == K(x)(y) == x
+ * :: a -> b -> a
+ * >> Functional.K(1)(2) -> 1
+ */
+Functional.K = function(x) {return function() {return x}};
+
+/// A synonym for `Functional.I`
+Functional.id = Functional.I;
+
+/// A synonym for `Functional.K`
+Functional.constfn = Functional.K;
+
+
+/**
+ * Returns a function that applies the first function to the
+ * result of the second, but passes all its arguments too.
+ * == S(f, g)(args...) == f(g(args...), args...)
+ *
+ * This is useful for composing functions when each needs access
+ * to the arguments to the composed function.  For example,
+ * the following function multiples its last two arguments,
+ * and adds the first to that.
+ * >> Function.S('+', '_ a b -> a*b')(2,3,4) -> 14
+ *
+ * Curry this to get a version that takes its arguments in
+ * separate calls:
+ * >> Function.S.curry('+')('_ a b -> a*b')(2,3,4) -> 14
+ */
+Function.S = function(f, g) {
+    f = Function.toFunction(f);
+    g = Function.toFunction(g);
+    return function() {
+        return f.apply(this, [g.apply(this, arguments)].concat(Array.slice(arguments, 0)));
+    }
+}
+
+/// ^^^ Combinator methods
+
+/**
+ * Returns a function that swaps its first two arguments before
+ * passing them to the underlying function.
+ * == f.flip()(a, b, c...) == f(b, a, c...)
+ * :: (a b c...) -> (b a c...)
+ * >> ('a/b'.lambda()).flip()(1,2) -> 2
+ *
+ * For more general derangements, you can also use `prefilterSlice`
+ * with a string lambda:
+ * >> '100*a+10*b+c'.lambda().prefilterSlice('a b c -> [b, c, a]')(1,2,3) -> 231
+ */
+Function.prototype.flip = function() {
+    var fn = this;
+    return function() {
+        var args = Array.slice(arguments, 0);
+        args = args.slice(1,2).concat(args.slice(0,1)).concat(args.slice(2));
+        return fn.apply(this, args);
+    }
+}
+
+/**
+ * Returns a function that applies the underlying function to its
+ * first argument, and the result of that application to the remaining
+ * arguments.
+ * == f.uncurry(a, b...) == f(a)(b...)
+ * :: (a -> b -> c) -> (a, b) -> c
+ * >> 'a -> b -> a/b'.lambda().uncurry()(1,2) -> 0.5
+ *
+ * Note that `uncurry` is *not* the inverse of `curry`.
+ */
+Function.prototype.uncurry = function() {
+    var fn = this;
+    return function() {
+        var f1 = fn.apply(this, Array.slice(arguments, 0, 1));
+        return f1.apply(this, Array.slice(arguments, 1));
+    }
+}
+
+/**
+ * ^^ Filtering
+ *
+ * Filters intercept a value before it is passed to a function, and apply the
+ * underlying function to the modified value.
+ */
+
+/**
+ * `prefilterObject` returns a function that applies the underlying function
+ * to the same arguments, but to an object that is the result of appyling
+ * `filter` to the invocation object.
+ * == fn.prefilterObject(filter).apply(object, args...) == fn.apply(filter(object), args...)
+ * == fn.bind(object) == compose(fn.prefilterObject, Functional.K(object))
+ * >> 'this'.lambda().prefilterObject('n+1').apply(1) -> 2
+ */
+Function.prototype.prefilterObject = function(filter) {
+    filter = Function.toFunction(filter);
+    var fn = this;
+    return function() {
+        return fn.apply(filter(this), arguments);
+    }
+}
+
+/**
+ * `prefilterAt` returns a function that applies the underlying function
+ * to a copy of the arguments, where the `index`th argument has been
+ * replaced by the value of `filter(argument[index])`.
+ * == fn.prefilterAt(i, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(a_{i}), ..., a_{n})
+ * >> '[a,b,c]'.lambda().prefilterAt(1, '2*')(2,3,4) -> [2, 6, 4]
+ */
+Function.prototype.prefilterAt = function(index, filter) {
+    filter = Function.toFunction(filter);
+    var fn = this;
+    return function() {
+        var args = Array.slice(arguments, 0);
+        args[index] = filter.call(this, args[index]);
+        return fn.apply(this, args);
+    }
+}
+
+/**
+ * `prefilterSlice` returns a function that applies the underlying function
+ * to a copy of the arguments, where the arguments `start` through
+ * `end` have been replaced by the value of `filter(argument.slice(start,end))`,
+ * which must return a list.
+ * == fn.prefilterSlice(i0, i1, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(args_{i0}, ..., args_{i1}), ..., a_{n})
+ * >> '[a,b,c]'.lambda().prefilterSlice('[a+b]', 1, 3)(1,2,3,4) -> [1, 5, 4]
+ * >> '[a,b]'.lambda().prefilterSlice('[a+b]', 1)(1,2,3) -> [1, 5]
+ * >> '[a]'.lambda().prefilterSlice(compose('[_]', Math.max))(1,2,3) -> [3]
+ */
+Function.prototype.prefilterSlice = function(filter, start, end) {
+    filter = Function.toFunction(filter);
+    start = start || 0;
+    var fn = this;
+    return function() {
+        var args = Array.slice(arguments, 0);
+        var e = end < 0 ? args.length + end : end || args.length;
+        args.splice.apply(args, [start, (e||args.length)-start].concat(filter.apply(this, args.slice(start, e))));
+        return fn.apply(this, args);
+    }
+}
+
+/// ^^ Method Composition
+
+/**
+ * `compose` returns a function that applies the underlying function
+ * to the result of the application of `fn`.
+ * == f.compose(g)(args...) == f(g(args...))
+ * >> '1+'.lambda().compose('2*')(3) -> 7
+ *
+ * Note that, unlike `Functional.compose`, the `compose` method on
+ * function only takes a single argument.
+ * == Functional.compose(f, g) == f.compose(g)
+ * == Functional.compose(f, g, h) == f.compose(g).compose(h)
+ */
+Function.prototype.compose = function(fn) {
+    var self = this;
+    fn = Function.toFunction(fn);
+    return function() {
+        return self.apply(this, [fn.apply(this, arguments)]);
+    }
+}
+
+/**
+ * `sequence` returns a function that applies the underlying function
+ * to the result of the application of `fn`.
+ * == f.sequence(g)(args...) == g(f(args...))
+ * == f.sequence(g) == g.compose(f)
+ * >> '1+'.lambda().sequence('2*')(3) -> 8
+ *
+ * Note that, unlike `Functional.compose`, the `sequence` method on
+ * function only takes a single argument.
+ * == Functional.sequence(f, g) == f.sequence(g)
+ * == Functional.sequence(f, g, h) == f.sequence(g).sequence(h)
+ */
+Function.prototype.sequence = function(fn) {
+    var self = this;
+    fn = Function.toFunction(fn);
+    return function() {
+        return fn.apply(this, [self.apply(this, arguments)]);
+    }
+}
+
+/**
+ * Returns a function that is equivalent to the underlying function when
+ * `guard` returns true, and otherwise is equivalent to the application
+ * of `otherwise` to the same arguments.
+ *
+ * `guard` and `otherwise` default to `Functional.I`.  `guard` with
+ * no arguments therefore returns a function that applies the
+ * underlying function to its value only if the value is true,
+ * and returns the value otherwise.
+ * == f.guard(g, h)(args...) == f(args...), when g(args...) is true
+ * == f.guard(g ,h)(args...) == h(args...), when g(args...) is false
+ * >> '[_]'.lambda().guard()(1) -> [1]
+ * >> '[_]'.lambda().guard()(null) -> null
+ * >> '[_]'.lambda().guard(null, Functional.K('n/a'))(null) -> "n/a"
+ * >> 'x+1'.lambda().guard('<10', Functional.K(null))(1) -> 2
+ * >> 'x+1'.lambda().guard('<10', Functional.K(null))(10) -> null
+ * >> '/'.lambda().guard('p q -> q', Functional.K('n/a'))(1, 2) -> 0.5
+ * >> '/'.lambda().guard('p q -> q', Functional.K('n/a'))(1, 0) -> "n/a"
+ * >> '/'.lambda().guard('p q -> q', '-> "n/a"')(1, 0) -> "n/a"
+ */
+Function.prototype.guard = function(guard, otherwise) {
+    var fn = this;
+    guard = Function.toFunction(guard || Functional.I);
+    otherwise = Function.toFunction(otherwise || Functional.I);
+    return function() {
+        return (guard.apply(this, arguments) ? fn : otherwise).apply(this, arguments);
+    }
+}
+
+/// ^^ Utilities
+
+/**
+ * Returns a function identical to this function except that
+ * it prints its arguments on entry and its return value on exit.
+ * This is useful for debugging function-level programs.
+ */
+Function.prototype.traced = function(name) {
+    var self   = this,
+        global = (function() { return this; })(),
+        log    = function() {};
+
+    if (typeof console != 'undefined' && typeof console.info == 'function') {
+      log = console.info;
+    } else if (typeof print == 'function') {
+      log = print;
+    }
+
+    name = name || self;
+    return function() {
+        log('[', name, 'apply(', this!=global && this, ',', arguments, ')');
+        var result = self.apply(this, arguments);
+        log(']', name, ' -> ', result);
+        return result;
+    }
+}
+
+
+/**
+ * ^^ Function methods as functions
+ *
+ * In addition to the functions defined above, every method defined
+ * on `Function` is also available as a function in `Functional`, that
+ * coerces its first argument to a `Function` and applies
+ * the remaining arguments to this.
+ *
+ * A few examples make this clearer:
+ * == curry(fn, args...) == fn.curry(args...)
+ * >> Functional.flip('a/b')(1, 2) -> 2
+ * >> Functional.curry('a/b', 1)(2) -> 0.5
+
+ * For each method that this file defined on Function.prototype,
+ * define a function on Functional that delegates to it.
+ */
+Functional._attachMethodDelegates(Functional.__initalFunctionState.getChangedMethods());
+delete Functional.__initalFunctionState;
+
+
+// In case to-function.js isn't loaded.
+Function.toFunction = Function.toFunction || Functional.K;
+
+if (!Array.slice) { // mozilla already supports this
+    Array.slice = (function(slice) {
+        return function(object) {
+            return slice.apply(object, slice.call(arguments, 1));
+        };
+    })(Array.prototype.slice);
+}
+/*
+ * Author: Oliver Steele
+ * Copyright: Copyright 2007 by Oliver Steele.  All rights reserved.
+ * License: MIT License
+ * Homepage: http://osteele.com/javascripts/functional
+ * Created: 2007-07-11
+ * Version: 1.0.2
+ *
+ *
+ * This defines "string lambdas", that allow strings such as `x+1` and
+ * `x -> x+1` to be used in some contexts as functions.
+ */
+
+
+/// ^ String lambdas
+
+/**
+ * Turns a string that contains a JavaScript expression into a
+ * `Function` that returns the value of that expression.
+ *
+ * If the string contains a `->`, this separates the parameters from the body:
+ * >> 'x -> x + 1'.lambda()(1) -> 2
+ * >> 'x y -> x + 2*y'.lambda()(1, 2) -> 5
+ * >> 'x, y -> x + 2*y'.lambda()(1, 2) -> 5
+ *
+ * Otherwise, if the string contains a `_`, this is the parameter:
+ * >> '_ + 1'.lambda()(1) -> 2
+ *
+ * Otherwise if the string begins or ends with an operator or relation,
+ * prepend or append a parameter.  (The documentation refers to this type
+ * of string as a "section".)
+ * >> '/2'.lambda()(4) -> 2
+ * >> '2/'.lambda()(4) -> 0.5
+ * >> '/'.lambda()(2,4) -> 0.5
+ * Sections can end, but not begin with, `-`.  (This is to avoid interpreting
+ * e.g. `-2*x` as a section).  On the other hand, a string that either begins
+ * or ends with `/` is a section, so an expression that begins or ends with a
+ * regular expression literal needs an explicit parameter.
+ *
+ * Otherwise, each variable name is an implicit parameter:
+ * >> 'x + 1'.lambda()(1) -> 2
+ * >> 'x + 2*y'.lambda()(1, 2) -> 5
+ * >> 'y + 2*x'.lambda()(1, 2) -> 5
+ *
+ * Implicit parameter detection ignores strings literals, variable names that
+ * start with capitals, and identifiers that precede `:` or follow `.`:
+ * >> map('"im"+root', ["probable", "possible"]) -> ["improbable", "impossible"]
+ * >> 'Math.cos(angle)'.lambda()(Math.PI) -> -1
+ * >> 'point.x'.lambda()({x:1, y:2}) -> 1
+ * >> '({x:1, y:2})[key]'.lambda()('x') -> 1
+ *
+ * Implicit parameter detection mistakenly looks inside regular expression
+ * literals for variable names.  It also doesn't know to ignore JavaScript
+ * keywords and bound variables.  (The only way you can get these last two is
+ * with a function literal inside the string.  This is outside the intended use
+ * case for string lambdas.)
+ *
+ * Use `_` (to define a unary function) or `->`, if the string contains anything
+ * that looks like a free variable but shouldn't be used as a parameter, or
+ * to specify parameters that are ordered differently from their first
+ * occurrence in the string.
+ *
+ * Chain `->`s to create a function in uncurried form:
+ * >> 'x -> y -> x + 2*y'.lambda()(1)(2) -> 5
+ * >> 'x -> y -> z -> x + 2*y+3*z'.lambda()(1)(2)(3) -> 14
+ *
+ * `this` and `arguments` are special:
+ * >> 'this'.call(1) -> 1
+ * >> '[].slice.call(arguments, 0)'.call(null,1,2) -> [1, 2]
+ */
+String.prototype.lambda = function() {
+    var params = [],
+        expr = this,
+        sections = expr.ECMAsplit(/\s*->\s*/m);
+    if (sections.length > 1) {
+        while (sections.length) {
+            expr = sections.pop();
+            params = sections.pop().split(/\s*,\s*|\s+/m);
+            sections.length && sections.push('(function('+params+'){return ('+expr+')})');
+        }
+    } else if (expr.match(/\b_\b/)) {
+        params = '_';
+    } else {
+        // test whether an operator appears on the left (or right), respectively
+        var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m),
+            rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m);
+        if (leftSection || rightSection) {
+            if (leftSection) {
+                params.push('$1');
+                expr = '$1' + expr;
+            }
+            if (rightSection) {
+                params.push('$2');
+                expr = expr + '$2';
+            }
+        } else {
+            // `replace` removes symbols that are capitalized, follow '.',
+            // precede ':', are 'this' or 'arguments'; and also the insides of
+            // strings (by a crude test).  `match` extracts the remaining
+            // symbols.
+            var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*\s*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '').match(/([a-z_$][a-z_$\d]*)/gi) || []; // '
+            for (var i = 0, v; v = vars[i++]; )
+                params.indexOf(v) >= 0 || params.push(v);
+        }
+    }
+    return new Function(params, 'return (' + expr + ')');
+}
+
+/// Turn on caching for `string` -> `Function` conversion.
+String.prototype.lambda.cache = function() {
+    var proto = String.prototype,
+        cache = {},
+        uncached = proto.lambda,
+        cached = function() {
+            var key = '#' + this; // avoid hidden properties on Object.prototype
+            return cache[key] || (cache[key] = uncached.call(this));
+        };
+    cached.cached = function(){};
+    cached.uncache = function(){proto.lambda = uncached};
+    proto.lambda = cached;
+}
+
+/**
+ * ^^ Duck-Typing
+ *
+ * Strings support `call` and `apply`.  This duck-types them as
+ * functions, to some callers.
+ */
+
+/**
+ * Coerce the string to a function and then apply it.
+ * >> 'x+1'.apply(null, [2]) -> 3
+ * >> '/'.apply(null, [2, 4]) -> 0.5
+ */
+String.prototype.apply = function(thisArg, args) {
+    return this.toFunction().apply(thisArg, args);
+}
+
+/**
+ * Coerce the string to a function and then call it.
+ * >> 'x+1'.call(null, 2) -> 3
+ * >> '/'.call(null, 2, 4) -> 0.5
+ */
+String.prototype.call = function() {
+    return this.toFunction().apply(arguments[0],
+                                   Array.prototype.slice.call(arguments, 1));
+}
+
+/// ^^ Coercion
+
+/**
+ * Returns a `Function` that perfoms the action described by this
+ * string.  If the string contains a `return`, applies
+ * `new Function` to it.  Otherwise, this function returns
+ *  the result of `this.lambda()`.
+ * >> '+1'.toFunction()(2) -> 3
+ * >> 'return 1'.toFunction()(1) -> 1
+ */
+String.prototype.toFunction = function() {
+    var body = this;
+    if (body.match(/\breturn\b/))
+        return new Function(this);
+    return this.lambda();
+}
+
+/**
+ * Returns this function.  `Function.toFunction` calls this.
+ * >> '+1'.lambda().toFunction()(2) -> 3
+ */
+Function.prototype.toFunction = function() {
+    return this;
+}
+
+/**
+ * Coerces `fn` into a function if it is not already one,
+ * by calling its `toFunction` method.
+ * >> Function.toFunction(function() {return 1})() -> 1
+ * >> Function.toFunction('+1')(2) -> 3
+ *
+ * `Function.toFunction` requires an argument that can be
+ * coerced to a function.  A nullary version can be
+ * constructed via `guard`:
+ * >> Function.toFunction.guard()('1+') -> function()
+ * >> Function.toFunction.guard()(null) -> null
+ *
+ * `Function.toFunction` doesn't coerce arbitrary values to functions.
+ * It might seem convenient to treat
+ * `Function.toFunction(value)` as though it were the
+ * constant function that returned `value`, but it's rarely
+ * useful and it hides errors.  Use `Functional.K(value)` instead,
+ * or a lambda string when the value is a compile-time literal:
+ * >> Functional.K('a string')() -> "a string"
+ * >> Function.toFunction('"a string"')() -> "a string"
+ */
+Function.toFunction = function(value) {
+    return value.toFunction();
+}
+
+// Utilities
+
+// IE6 split is not ECMAScript-compliant.  This breaks '->1'.lambda().
+// ECMAsplit is an ECMAScript-compliant `split`, although only for
+// one argument.
+String.prototype.ECMAsplit =
+    // The test is from the ECMAScript reference.
+    ('ab'.split(/a*/).length > 1
+     ? String.prototype.split
+     : function(separator, limit) {
+         if (typeof limit != 'undefined')
+             throw "ECMAsplit: limit is unimplemented";
+         var result = this.split.apply(this, arguments),
+             re = RegExp(separator),
+             savedIndex = re.lastIndex,
+             match = re.exec(this);
+         if (match && match.index == 0)
+             result.unshift('');
+         // in case `separator` was already a RegExp:
+         re.lastIndex = savedIndex;
+         return result;
+     });
diff --git a/lib/gury.js b/lib/gury.js
new file mode 100644 (file)
index 0000000..172dd88
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+  gury.js - A jQuery inspired canvas utility library
+
+  Copyright (c) 2010 Ryan Sandor Richards
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+*/
+
+window.$g = window.Gury = (function() {
+  /*
+   * Utility functions
+   */
+  function isObject(v) { return typeof v == "object"; }
+  function isFunction(v) { return typeof v == "function"; }
+  function isString(v) { return typeof v == "string"; }
+  function isObjectOrFunction(v) { return typeof v == "function" || typeof v == "object"; }
+  
+  function _each(closure) {
+    for (var i = 0; i < this.length; i++) {
+      closure(this[i], i);
+    }
+  }
+  
+  /*
+   * Internal exception handling
+   */
+  var _failWithException = true;
+  
+  function GuryException(msg) {
+    if (_failWithException) {
+      throw "Gury: " + msg;
+    }
+  }
+  
+  /*
+   * These handle mappings from Canvas DOM elements to Gury instances
+   * to allow for persistant states between calls to the module.
+   */
+  var guryId = 1;
+  var canvasToGury = {};
+  
+  function nextGuryId() { 
+    return "gury_id_" + (guryId++); 
+  }
+  
+  function getGury(canvas) {
+    if (!isString(canvas._gury_id) || !(canvasToGury[canvas._gury_id] instanceof Gury)) {
+      return null;
+    }
+    return canvasToGury[canvas._gury_id];
+  }
+  
+  function setGury(canvas, gury) {
+    if (typeof canvas._gury_id == "string") {
+      gury.id = canvas._gury_id;
+    }
+    else {
+      gury.id = canvas._gury_id = nextGuryId();
+    }
+
+    return canvasToGury[gury.id] = gury;
+  }
+  
+  /*
+   * Tag Namespace Object
+   */
+  function TagSpace(name, objects) {
+    this.name = name;
+    this._children = {};
+    this._objects = objects || [];
+  }
+  
+  TagSpace.TAG_REGEX = /^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*$/;
+  
+  TagSpace.prototype.hasChild = function(name) {
+    return isObject(this._children[name]);
+  };
+  
+  TagSpace.prototype.addChild = function(name) {
+    return this._children[name] = new TagSpace(name);
+  };
+  
+  TagSpace.prototype.getChild = function(name) {
+    return this._children[name];
+  };
+  
+  TagSpace.prototype.getObjects = function() {
+    // This might be a little slow, but it helps us keep spaces consistent
+    var objects = [];
+    for (var i = 0; i < this._objects.length; i++) {
+      objects.push(this._objects[i]);
+    }
+    
+    // And lets us annotate what we return :)
+    objects.each = _each;
+    
+    return objects;
+  };
+  
+  TagSpace.prototype.find = function(tag) {
+    if (!tag.match(TagSpace.TAG_REGEX)) {
+      return null;
+    }
+    
+    var currentSpace = this;
+    var tags = tag.split('.');
+    var lastName = tags[tags.length - 1];
+    
+    for (var i = 0; i < tags.length; i++) {
+      if (!currentSpace.hasChild(tags[i]))
+        return null;
+      currentSpace = currentSpace.getChild(tags[i]);
+    }
+    
+    return currentSpace;
+  };
+  
+  TagSpace.prototype.add = function(tag, object) {
+    if (!tag.match(TagSpace.TAG_REGEX)) {
+      return null;
+    }
+    
+    var currentSpace = this;
+    var tags = tag.split('.');
+    var lastName = tags[tags.length - 1];
+    
+    for (var i = 0; i < tags.length; i++) {
+      if (currentSpace.hasChild(tags[i])) {
+        currentSpace = currentSpace.getChild(tags[i]);
+      }
+      else {
+        currentSpace = currentSpace.addChild(tags[i]);
+      }
+    }
+  
+    // TODO: There's probably a better way to check for duplicates
+    //  ... but that's why they call it "iterative" development :P
+    
+    for (i = 0; i < currentSpace._objects.length; i++) {
+      if (currentSpace._objects[i] == object) {
+        return object;
+      }
+    }
+    
+    currentSpace._objects.push(object);
+    
+    return object;
+  };
+  
+  /*
+   * Core Gury Class
+   */
+  
+  function Gury(canvas) {
+    if (canvas == null) {
+      canvas = document.createElement('canvas');
+    }
+    
+    // Check for an existing mapping from the canvas to a Gury instance
+    if (getGury(canvas)) {
+      return getGury(canvas);
+    }
+    
+    // Otherwise create a new instance
+    this.canvas = canvas;
+    this.ctx = canvas.getContext('2d');
+    
+    this._objects = [];
+    this._objects.each = _each;
+    
+    this._tags = new TagSpace('__global');
+    
+    this._paused = false;
+    this._loop_interval = null;
+  
+    return setGury(canvas, this);
+  }
+  
+  Gury.prototype.place = function(node) {
+    if (typeof node == "string" && typeof $ == "function") {
+      $(node).append(this.canvas);
+    }
+    else if (typeof node == "object" && typeof node.addChild == "function") {
+      node.addChild(this.canvas);
+    }
+    else {
+      GuryException("place() - Unable to place canvas tag (is jQuery loaded?)");
+    }
+    return this;
+  };
+  
+  /*
+   * Canvas style methods
+   */
+  
+  Gury.prototype.size = function(w, h) {
+    this.canvas.width = w;
+    this.canvas.height = h;
+    return this;
+  };
+  
+  Gury.prototype.background = function(bg) {
+    this.canvas.style.background = bg;
+    return this;
+  };
+  
+  /*
+   * Objects and Rendering
+   */
+  
+  function _annotate_object(object) {
+    object._gury = {
+      visible: true
+    };
+  }
+  
+  Gury.prototype.add = function() {
+    var tag = null, obj;
+    
+    if (arguments.length < 1) {
+      return this;
+    }
+    else if (arguments.length < 2) {
+      obj = arguments[0];
+      if (!isObjectOrFunction(obj)) {
+        return this;
+      }
+    }
+    else {
+      tag = arguments[0];
+      obj = arguments[1];
+      if (!isString(name) || !isObjectOrFunction(obj)) {
+        return this;
+      }
+    }
+    
+    // Annotate the object with gury specific members
+    _annotate_object(obj);
+    
+    // Add the object to the global tag space (if a tag was provided)
+    if (tag != null) {
+      this._tags.add(tag, obj);
+    }
+    
+    // Add to the rendering list
+    this._objects.push(obj);
+    
+    return this;
+  };
+  
+  Gury.prototype.clear = function() {
+    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+    return this;
+  };
+  
+  Gury.prototype.draw = function() {
+    this.clear();
+    
+    for (var i = 0; i < this._objects.length; i++) {
+      var ob = this._objects[i];
+      
+      if (!ob._gury.visible) {
+        continue;
+      }
+      
+      if (typeof ob == "function") {
+        ob.call(this, this.ctx);
+      }
+      else if (typeof ob == "object" && typeof ob.draw != "undefined") {
+        ob.draw(this.ctx, this.canvas);
+      }
+    }
+    return this;
+  };
+  
+  /*
+   * Animation Controls
+   */
+  
+  Gury.prototype.play = function(interval) {
+    // Ignore multiple play attempts
+    if (this._loop_interval != null) {
+      return this;
+    }
+      
+    var _gury = this;
+    this._loop_interval = setInterval(function() {
+      if (!_gury._paused) {
+        _gury.draw();
+      }
+    }, interval);
+    return this;
+  };
+  
+  Gury.prototype.pause = function() {
+    this._paused = !this._paused;
+    return this;
+  };
+  
+  Gury.prototype.stop = function() {
+    if (this._loop_interval != null) {
+      clearInterval(this._loop_interval);
+      this._paused = false;
+    }
+    return this;
+  };
+  
+  /*
+   * Object / Tag Methods
+   */
+  Gury.prototype.each = function() {
+    var tag, closure;
+    
+    if (arguments.length < 2 && isFunction(arguments[0])) {
+      closure = arguments[0];
+      this._objects.each(closure);
+    }
+    else if (isString(arguments[0]) && isFunction(arguments[1])) {
+      tag = arguments[0];
+      closure = arguments[1];
+      var space = this._tags.find(tag);
+      if (space) {
+        space.getObjects().each(closure);
+      }
+    }
+    else if (isFunction(arguments[0])) {
+      closure = arguments[0];
+      this._objects.each(closure);
+    }
+    else if (isFunction(arguments[1])) {
+      closure = arguments[1];
+      this._objects.each(closure);
+    }
+    
+    return this;
+  };
+   
+  Gury.prototype.hide = function(tag) {
+    return this.each(tag, function(obj, index) {
+      obj._gury.visible = false;
+    });
+  };
+  
+  Gury.prototype.show = function(tag) {
+    return this.each(tag, function(obj, index) {
+      obj._gury.visible = true;
+    });
+  };
+  
+  Gury.prototype.toggle = function(tag) {
+    return this.each(tag, function(obj, index) {
+      obj._gury.visible = !obj._gury.visible;
+    });
+  };
+  
+  /*
+   * Public interface
+   */
+  
+  function GuryInterface(id) {
+    return new Gury(id ? document.getElementById(id) : null);
+  }
+  
+  GuryInterface.failWithException = function(b) {
+    if (!b) {
+      return _failWithException;
+    }
+    return _failWithException = b ? true : false;
+  };
+  
+  return GuryInterface;
+})();
+
+// "There's a star man waiting in the sky. He'd like to come and meet us but 
+// he think's he'll blow our minds."
\ No newline at end of file
diff --git a/lib/jquery-1.4.3.js b/lib/jquery-1.4.3.js
new file mode 100644 (file)
index 0000000..ad9a79c
--- /dev/null
@@ -0,0 +1,6883 @@
+/*!
+ * jQuery JavaScript Library v1.4.3
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Oct 14 23:10:06 2010 -0400
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+               // The jQuery object is actually just the init constructor 'enhanced'
+               return new jQuery.fn.init( selector, context );
+       },
+
+       // Map over jQuery in case of overwrite
+       _jQuery = window.jQuery,
+
+       // Map over the $ in case of overwrite
+       _$ = window.$,
+
+       // A central reference to the root jQuery(document)
+       rootjQuery,
+
+       // A simple way to check for HTML strings or ID strings
+       // (both of which we optimize for)
+       quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,
+
+       // Is it a simple selector
+       isSimple = /^.[^:#\[\.,]*$/,
+
+       // Check if a string has a non-whitespace character in it
+       rnotwhite = /\S/,
+       rwhite = /\s/,
+
+       // Used for trimming whitespace
+       trimLeft = /^\s+/,
+       trimRight = /\s+$/,
+
+       // Check for non-word characters
+       rnonword = /\W/,
+
+       // Check for digits
+       rdigit = /\d/,
+
+       // Match a standalone tag
+       rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+       // JSON RegExp
+       rvalidchars = /^[\],:{}\s]*$/,
+       rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+       rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+       rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+       // Useragent RegExp
+       rwebkit = /(webkit)[ \/]([\w.]+)/,
+       ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+       rmsie = /(msie) ([\w.]+)/,
+       rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+       // Keep a UserAgent string for use with jQuery.browser
+       userAgent = navigator.userAgent,
+
+       // For matching the engine and version of the browser
+       browserMatch,
+       
+       // Has the ready events already been bound?
+       readyBound = false,
+       
+       // The functions to execute on DOM ready
+       readyList = [],
+
+       // The ready event handler
+       DOMContentLoaded,
+
+       // Save a reference to some core methods
+       toString = Object.prototype.toString,
+       hasOwn = Object.prototype.hasOwnProperty,
+       push = Array.prototype.push,
+       slice = Array.prototype.slice,
+       trim = String.prototype.trim,
+       indexOf = Array.prototype.indexOf,
+       
+       // [[Class]] -> type pairs
+       class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+       init: function( selector, context ) {
+               var match, elem, ret, doc;
+
+               // Handle $(""), $(null), or $(undefined)
+               if ( !selector ) {
+                       return this;
+               }
+
+               // Handle $(DOMElement)
+               if ( selector.nodeType ) {
+                       this.context = this[0] = selector;
+                       this.length = 1;
+                       return this;
+               }
+               
+               // The body element only exists once, optimize finding it
+               if ( selector === "body" && !context && document.body ) {
+                       this.context = document;
+                       this[0] = document.body;
+                       this.selector = "body";
+                       this.length = 1;
+                       return this;
+               }
+
+               // Handle HTML strings
+               if ( typeof selector === "string" ) {
+                       // Are we dealing with HTML string or an ID?
+                       match = quickExpr.exec( selector );
+
+                       // Verify a match, and that no context was specified for #id
+                       if ( match && (match[1] || !context) ) {
+
+                               // HANDLE: $(html) -> $(array)
+                               if ( match[1] ) {
+                                       doc = (context ? context.ownerDocument || context : document);
+
+                                       // If a single string is passed in and it's a single tag
+                                       // just do a createElement and skip the rest
+                                       ret = rsingleTag.exec( selector );
+
+                                       if ( ret ) {
+                                               if ( jQuery.isPlainObject( context ) ) {
+                                                       selector = [ document.createElement( ret[1] ) ];
+                                                       jQuery.fn.attr.call( selector, context, true );
+
+                                               } else {
+                                                       selector = [ doc.createElement( ret[1] ) ];
+                                               }
+
+                                       } else {
+                                               ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+                                               selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
+                                       }
+                                       
+                                       return jQuery.merge( this, selector );
+                                       
+                               // HANDLE: $("#id")
+                               } else {
+                                       elem = document.getElementById( match[2] );
+
+                                       // Check parentNode to catch when Blackberry 4.6 returns
+                                       // nodes that are no longer in the document #6963
+                                       if ( elem && elem.parentNode ) {
+                                               // Handle the case where IE and Opera return items
+                                               // by name instead of ID
+                                               if ( elem.id !== match[2] ) {
+                                                       return rootjQuery.find( selector );
+                                               }
+
+                                               // Otherwise, we inject the element directly into the jQuery object
+                                               this.length = 1;
+                                               this[0] = elem;
+                                       }
+
+                                       this.context = document;
+                                       this.selector = selector;
+                                       return this;
+                               }
+
+                       // HANDLE: $("TAG")
+                       } else if ( !context && !rnonword.test( selector ) ) {
+                               this.selector = selector;
+                               this.context = document;
+                               selector = document.getElementsByTagName( selector );
+                               return jQuery.merge( this, selector );
+
+                       // HANDLE: $(expr, $(...))
+                       } else if ( !context || context.jquery ) {
+                               return (context || rootjQuery).find( selector );
+
+                       // HANDLE: $(expr, context)
+                       // (which is just equivalent to: $(context).find(expr)
+                       } else {
+                               return jQuery( context ).find( selector );
+                       }
+
+               // HANDLE: $(function)
+               // Shortcut for document ready
+               } else if ( jQuery.isFunction( selector ) ) {
+                       return rootjQuery.ready( selector );
+               }
+
+               if (selector.selector !== undefined) {
+                       this.selector = selector.selector;
+                       this.context = selector.context;
+               }
+
+               return jQuery.makeArray( selector, this );
+       },
+
+       // Start with an empty selector
+       selector: "",
+
+       // The current version of jQuery being used
+       jquery: "1.4.3",
+
+       // The default length of a jQuery object is 0
+       length: 0,
+
+       // The number of elements contained in the matched element set
+       size: function() {
+               return this.length;
+       },
+
+       toArray: function() {
+               return slice.call( this, 0 );
+       },
+
+       // 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 a 'clean' array
+                       this.toArray() :
+
+                       // Return just the object
+                       ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );
+       },
+
+       // Take an array of elements and push it onto the stack
+       // (returning the new matched element set)
+       pushStack: function( elems, name, selector ) {
+               // Build a new jQuery matched element set
+               var ret = jQuery();
+
+               if ( jQuery.isArray( elems ) ) {
+                       push.apply( ret, elems );
+               
+               } else {
+                       jQuery.merge( ret, elems );
+               }
+
+               // Add the old object onto the stack (as a reference)
+               ret.prevObject = this;
+
+               ret.context = this.context;
+
+               if ( name === "find" ) {
+                       ret.selector = this.selector + (this.selector ? " " : "") + selector;
+               } else if ( name ) {
+                       ret.selector = this.selector + "." + name + "(" + selector + ")";
+               }
+
+               // Return the newly-formed element set
+               return ret;
+       },
+
+       // Execute a callback for every element in the matched set.
+       // (You can seed the arguments with an array of args, but this is
+       // only used internally.)
+       each: function( callback, args ) {
+               return jQuery.each( this, callback, args );
+       },
+       
+       ready: function( fn ) {
+               // Attach the listeners
+               jQuery.bindReady();
+
+               // If the DOM is already ready
+               if ( jQuery.isReady ) {
+                       // Execute the function immediately
+                       fn.call( document, jQuery );
+
+               // Otherwise, remember the function for later
+               } else if ( readyList ) {
+                       // Add the function to the wait list
+                       readyList.push( fn );
+               }
+
+               return this;
+       },
+       
+       eq: function( i ) {
+               return i === -1 ?
+                       this.slice( i ) :
+                       this.slice( i, +i + 1 );
+       },
+
+       first: function() {
+               return this.eq( 0 );
+       },
+
+       last: function() {
+               return this.eq( -1 );
+       },
+
+       slice: function() {
+               return this.pushStack( slice.apply( this, arguments ),
+                       "slice", slice.call(arguments).join(",") );
+       },
+
+       map: function( callback ) {
+               return this.pushStack( jQuery.map(this, function( elem, i ) {
+                       return callback.call( elem, i, elem );
+               }));
+       },
+       
+       end: function() {
+               return this.prevObject || jQuery(null);
+       },
+
+       // For internal use only.
+       // Behaves like an Array's method, not like a jQuery method.
+       push: push,
+       sort: [].sort,
+       splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+       // copy reference to target object
+       var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy, copyIsArray;
+
+       // Handle a deep copy situation
+       if ( typeof target === "boolean" ) {
+               deep = target;
+               target = arguments[1] || {};
+               // skip the boolean and the target
+               i = 2;
+       }
+
+       // 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 ( length === i ) {
+               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({
+       noConflict: function( deep ) {
+               window.$ = _$;
+
+               if ( deep ) {
+                       window.jQuery = _jQuery;
+               }
+
+               return jQuery;
+       },
+       
+       // 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,
+       
+       // Handle when the DOM is ready
+       ready: function( wait ) {
+               // A third-party is pushing the ready event forwards
+               if ( wait === true ) {
+                       jQuery.readyWait--;
+               }
+
+               // Make sure that the DOM is not already loaded
+               if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) {
+                       // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+                       if ( !document.body ) {
+                               return setTimeout( jQuery.ready, 1 );
+                       }
+
+                       // 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
+                       if ( readyList ) {
+                               // Execute all of them
+                               var fn, i = 0;
+                               while ( (fn = readyList[ i++ ]) ) {
+                                       fn.call( document, jQuery );
+                               }
+
+                               // Reset the list of functions
+                               readyList = null;
+                       }
+
+                       // Trigger any bound ready events
+                       if ( jQuery.fn.triggerHandler ) {
+                               jQuery( document ).triggerHandler( "ready" );
+                       }
+               }
+       },
+       
+       bindReady: function() {
+               if ( readyBound ) {
+                       return;
+               }
+
+               readyBound = true;
+
+               // Catch cases where $(document).ready() is called after the
+               // browser event has already occurred.
+               if ( document.readyState === "complete" ) {
+                       // Handle it asynchronously to allow scripts the opportunity to delay ready
+                       return setTimeout( jQuery.ready, 1 );
+               }
+
+               // Mozilla, Opera and webkit nightlies currently support this event
+               if ( document.addEventListener ) {
+                       // Use the handy event callback
+                       document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+                       
+                       // A fallback to window.onload, that will always work
+                       window.addEventListener( "load", jQuery.ready, false );
+
+               // If IE event model is used
+               } else if ( document.attachEvent ) {
+                       // ensure firing before onload,
+                       // maybe late but safe also for iframes
+                       document.attachEvent("onreadystatechange", DOMContentLoaded);
+                       
+                       // A fallback to window.onload, that will always work
+                       window.attachEvent( "onload", jQuery.ready );
+
+                       // If IE and not a frame
+                       // continually check to see if the document is ready
+                       var toplevel = false;
+
+                       try {
+                               toplevel = window.frameElement == null;
+                       } catch(e) {}
+
+                       if ( document.documentElement.doScroll && toplevel ) {
+                               doScrollCheck();
+                       }
+               }
+       },
+
+       // See test/unit/core.js for details concerning isFunction.
+       // Since version 1.3, DOM methods and functions like alert
+       // aren't supported. They return false on IE (#2968).
+       isFunction: function( obj ) {
+               return jQuery.type(obj) === "function";
+       },
+
+       isArray: Array.isArray || function( obj ) {
+               return jQuery.type(obj) === "array";
+       },
+
+       // A crude way of determining if an object is a window
+       isWindow: function( obj ) {
+               return obj && typeof obj === "object" && "setInterval" in obj;
+       },
+
+       isNaN: function( obj ) {
+               return obj == null || !rdigit.test( obj ) || isNaN( obj );
+       },
+
+       type: function( obj ) {
+               return obj == null ?
+                       String( obj ) :
+                       class2type[ toString.call(obj) ] || "object";
+       },
+
+       isPlainObject: function( obj ) {
+               // Must be an Object.
+               // Because of IE, we also have to check the presence of the constructor property.
+               // Make sure that DOM nodes and window objects don't pass through, as well
+               if ( !obj || 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.
+       
+               var key;
+               for ( key in obj ) {}
+               
+               return key === undefined || hasOwn.call( obj, key );
+       },
+
+       isEmptyObject: function( obj ) {
+               for ( var name in obj ) {
+                       return false;
+               }
+               return true;
+       },
+       
+       error: function( msg ) {
+               throw msg;
+       },
+       
+       parseJSON: function( data ) {
+               if ( typeof data !== "string" || !data ) {
+                       return null;
+               }
+
+               // Make sure leading/trailing whitespace is removed (IE can't handle it)
+               data = jQuery.trim( data );
+               
+               // Make sure the incoming data is actual JSON
+               // Logic borrowed from http://json.org/json2.js
+               if ( rvalidchars.test(data.replace(rvalidescape, "@")
+                       .replace(rvalidtokens, "]")
+                       .replace(rvalidbraces, "")) ) {
+
+                       // Try to use the native JSON parser first
+                       return window.JSON && window.JSON.parse ?
+                               window.JSON.parse( data ) :
+                               (new Function("return " + data))();
+
+               } else {
+                       jQuery.error( "Invalid JSON: " + data );
+               }
+       },
+
+       noop: function() {},
+
+       // Evalulates a script in a global context
+       globalEval: function( data ) {
+               if ( data && rnotwhite.test(data) ) {
+                       // Inspired by code by Andrea Giammarchi
+                       // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+                       var head = document.getElementsByTagName("head")[0] || document.documentElement,
+                               script = document.createElement("script");
+
+                       script.type = "text/javascript";
+
+                       if ( jQuery.support.scriptEval ) {
+                               script.appendChild( document.createTextNode( data ) );
+                       } else {
+                               script.text = data;
+                       }
+
+                       // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+                       // This arises when a base node is used (#2709).
+                       head.insertBefore( script, head.firstChild );
+                       head.removeChild( script );
+               }
+       },
+
+       nodeName: function( elem, name ) {
+               return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+       },
+
+       // args is for internal usage only
+       each: function( object, callback, args ) {
+               var name, i = 0,
+                       length = object.length,
+                       isObj = length === undefined || jQuery.isFunction(object);
+
+               if ( args ) {
+                       if ( isObj ) {
+                               for ( name in object ) {
+                                       if ( callback.apply( object[ name ], args ) === false ) {
+                                               break;
+                                       }
+                               }
+                       } else {
+                               for ( ; i < length; ) {
+                                       if ( callback.apply( object[ i++ ], args ) === false ) {
+                                               break;
+                                       }
+                               }
+                       }
+
+               // A special, fast, case for the most common use of each
+               } else {
+                       if ( isObj ) {
+                               for ( name in object ) {
+                                       if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+                                               break;
+                                       }
+                               }
+                       } else {
+                               for ( var value = object[0];
+                                       i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
+                       }
+               }
+
+               return object;
+       },
+
+       // Use native String.trim function wherever possible
+       trim: trim ?
+               function( text ) {
+                       return text == null ?
+                               "" :
+                               trim.call( text );
+               } :
+
+               // Otherwise use our own trimming functionality
+               function( text ) {
+                       return text == null ?
+                               "" :
+                               text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+               },
+
+       // results is for internal usage only
+       makeArray: function( array, results ) {
+               var ret = results || [];
+
+               if ( array != null ) {
+                       // The window, strings (and functions) also have 'length'
+                       // The extra typeof function check is to prevent crashes
+                       // in Safari 2 (See: #3039)
+                       // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+                       var type = jQuery.type(array);
+
+                       if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+                               push.call( ret, array );
+                       } else {
+                               jQuery.merge( ret, array );
+                       }
+               }
+
+               return ret;
+       },
+
+       inArray: function( elem, array ) {
+               if ( array.indexOf ) {
+                       return array.indexOf( elem );
+               }
+
+               for ( var i = 0, length = array.length; i < length; i++ ) {
+                       if ( array[ i ] === elem ) {
+                               return i;
+                       }
+               }
+
+               return -1;
+       },
+
+       merge: function( first, second ) {
+               var i = first.length, j = 0;
+
+               if ( typeof second.length === "number" ) {
+                       for ( var l = second.length; j < l; j++ ) {
+                               first[ i++ ] = second[ j ];
+                       }
+               
+               } else {
+                       while ( second[j] !== undefined ) {
+                               first[ i++ ] = second[ j++ ];
+                       }
+               }
+
+               first.length = i;
+
+               return first;
+       },
+
+       grep: function( elems, callback, inv ) {
+               var ret = [], retVal;
+               inv = !!inv;
+
+               // Go through the array, only saving the items
+               // that pass the validator function
+               for ( var i = 0, length = elems.length; i < length; i++ ) {
+                       retVal = !!callback( elems[ i ], i );
+                       if ( inv !== retVal ) {
+                               ret.push( elems[ i ] );
+                       }
+               }
+
+               return ret;
+       },
+
+       // arg is for internal usage only
+       map: function( elems, callback, arg ) {
+               var ret = [], value;
+
+               // Go through the array, translating each of the items to their
+               // new value (or values).
+               for ( var i = 0, length = elems.length; i < length; i++ ) {
+                       value = callback( elems[ i ], i, arg );
+
+                       if ( value != null ) {
+                               ret[ ret.length ] = value;
+                       }
+               }
+
+               return ret.concat.apply( [], ret );
+       },
+
+       // A global GUID counter for objects
+       guid: 1,
+
+       proxy: function( fn, proxy, thisObject ) {
+               if ( arguments.length === 2 ) {
+                       if ( typeof proxy === "string" ) {
+                               thisObject = fn;
+                               fn = thisObject[ proxy ];
+                               proxy = undefined;
+
+                       } else if ( proxy && !jQuery.isFunction( proxy ) ) {
+                               thisObject = proxy;
+                               proxy = undefined;
+                       }
+               }
+
+               if ( !proxy && fn ) {
+                       proxy = function() {
+                               return fn.apply( thisObject || this, arguments );
+                       };
+               }
+
+               // Set the guid of unique handler to the same of original handler, so it can be removed
+               if ( fn ) {
+                       proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+               }
+
+               // So proxy can be declared as an argument
+               return proxy;
+       },
+
+       // Mutifunctional method to get and set values to a collection
+       // The value/s can be optionally by executed if its a function
+       access: function( elems, key, value, exec, fn, pass ) {
+               var length = elems.length;
+       
+               // Setting many attributes
+               if ( typeof key === "object" ) {
+                       for ( var k in key ) {
+                               jQuery.access( elems, k, key[k], exec, fn, value );
+                       }
+                       return elems;
+               }
+       
+               // Setting one attribute
+               if ( value !== undefined ) {
+                       // Optionally, function values get executed if exec is true
+                       exec = !pass && exec && jQuery.isFunction(value);
+               
+                       for ( var i = 0; i < length; i++ ) {
+                               fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+                       }
+               
+                       return elems;
+               }
+       
+               // Getting an attribute
+               return length ? fn( elems[0], key ) : undefined;
+       },
+
+       now: function() {
+               return (new Date()).getTime();
+       },
+
+       // Use of jQuery.browser is frowned upon.
+       // More details: http://docs.jquery.com/Utilities/jQuery.browser
+       uaMatch: function( ua ) {
+               ua = ua.toLowerCase();
+
+               var match = rwebkit.exec( ua ) ||
+                       ropera.exec( ua ) ||
+                       rmsie.exec( ua ) ||
+                       ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+                       [];
+
+               return { browser: match[1] || "", version: match[2] || "0" };
+       },
+
+       browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+       class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+       jQuery.browser[ browserMatch.browser ] = true;
+       jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+       jQuery.browser.safari = true;
+}
+
+if ( indexOf ) {
+       jQuery.inArray = function( elem, array ) {
+               return indexOf.call( array, elem );
+       };
+}
+
+// Verify that \s matches non-breaking spaces
+// (IE fails on this test)
+if ( !rwhite.test( "\xA0" ) ) {
+       trimLeft = /^[\s\xA0]+/;
+       trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+       DOMContentLoaded = function() {
+               document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+               jQuery.ready();
+       };
+
+} else if ( document.attachEvent ) {
+       DOMContentLoaded = function() {
+               // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+               if ( document.readyState === "complete" ) {
+                       document.detachEvent( "onreadystatechange", DOMContentLoaded );
+                       jQuery.ready();
+               }
+       };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+       if ( jQuery.isReady ) {
+               return;
+       }
+
+       try {
+               // If IE is used, use the trick by Diego Perini
+               // http://javascript.nwbox.com/IEContentLoaded/
+               document.documentElement.doScroll("left");
+       } catch(e) {
+               setTimeout( doScrollCheck, 1 );
+               return;
+       }
+
+       // and execute any waiting functions
+       jQuery.ready();
+}
+
+// Expose jQuery to the global object
+return (window.jQuery = window.$ = jQuery);
+
+})();
+
+
+(function() {
+
+       jQuery.support = {};
+
+       var root = document.documentElement,
+               script = document.createElement("script"),
+               div = document.createElement("div"),
+               id = "script" + jQuery.now();
+
+       div.style.display = "none";
+       div.innerHTML = "   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+       var all = div.getElementsByTagName("*"),
+               a = div.getElementsByTagName("a")[0],
+               select = document.createElement("select"),
+               opt = select.appendChild( document.createElement("option") );
+
+       // Can't get basic test support
+       if ( !all || !all.length || !a ) {
+               return;
+       }
+
+       jQuery.support = {
+               // IE strips leading whitespace when .innerHTML is used
+               leadingWhitespace: div.firstChild.nodeType === 3,
+
+               // Make sure that tbody elements aren't automatically inserted
+               // IE will insert them into empty tables
+               tbody: !div.getElementsByTagName("tbody").length,
+
+               // Make sure that link elements get serialized correctly by innerHTML
+               // This requires a wrapper element in IE
+               htmlSerialize: !!div.getElementsByTagName("link").length,
+
+               // Get the style information from getAttribute
+               // (IE uses .cssText insted)
+               style: /red/.test( a.getAttribute("style") ),
+
+               // Make sure that URLs aren't manipulated
+               // (IE normalizes it by default)
+               hrefNormalized: a.getAttribute("href") === "/a",
+
+               // Make sure that element opacity exists
+               // (IE uses filter instead)
+               // Use a regex to work around a WebKit issue. See #5145
+               opacity: /^0.55$/.test( a.style.opacity ),
+
+               // Verify style float existence
+               // (IE uses styleFloat instead of cssFloat)
+               cssFloat: !!a.style.cssFloat,
+
+               // Make sure that if no value is specified for a checkbox
+               // that it defaults to "on".
+               // (WebKit defaults to "" instead)
+               checkOn: div.getElementsByTagName("input")[0].value === "on",
+
+               // Make sure that a selected-by-default option has a working selected property.
+               // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+               optSelected: opt.selected,
+
+               // Will be defined later
+               optDisabled: false,
+               checkClone: false,
+               scriptEval: false,
+               noCloneEvent: true,
+               boxModel: null,
+               inlineBlockNeedsLayout: false,
+               shrinkWrapBlocks: false,
+               reliableHiddenOffsets: true
+       };
+
+       // Make sure that the options inside disabled selects aren't marked as disabled
+       // (WebKit marks them as diabled)
+       select.disabled = true;
+       jQuery.support.optDisabled = !opt.disabled;
+
+       script.type = "text/javascript";
+       try {
+               script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+       } catch(e) {}
+
+       root.insertBefore( script, root.firstChild );
+
+       // Make sure that the execution of code works by injecting a script
+       // tag with appendChild/createTextNode
+       // (IE doesn't support this, fails, and uses .text instead)
+       if ( window[ id ] ) {
+               jQuery.support.scriptEval = true;
+               delete window[ id ];
+       }
+
+       root.removeChild( script );
+
+       if ( div.attachEvent && div.fireEvent ) {
+               div.attachEvent("onclick", function click() {
+                       // Cloning a node shouldn't copy over any
+                       // bound event handlers (IE does this)
+                       jQuery.support.noCloneEvent = false;
+                       div.detachEvent("onclick", click);
+               });
+               div.cloneNode(true).fireEvent("onclick");
+       }
+
+       div = document.createElement("div");
+       div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
+
+       var fragment = document.createDocumentFragment();
+       fragment.appendChild( div.firstChild );
+
+       // WebKit doesn't clone checked state correctly in fragments
+       jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
+
+       // Figure out if the W3C box model works as expected
+       // document.body must exist before we can do this
+       jQuery(function() {
+               var div = document.createElement("div");
+               div.style.width = div.style.paddingLeft = "1px";
+
+               document.body.appendChild( div );
+               jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+
+               if ( "zoom" in div.style ) {
+                       // Check if natively block-level elements act like inline-block
+                       // elements when setting their display to 'inline' and giving
+                       // them layout
+                       // (IE < 8 does this)
+                       div.style.display = "inline";
+                       div.style.zoom = 1;
+                       jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2;
+
+                       // Check if elements with layout shrink-wrap their children
+                       // (IE 6 does this)
+                       div.style.display = "";
+                       div.innerHTML = "<div style='width:4px;'></div>";
+                       jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2;
+               }
+
+               div.innerHTML = "<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>";
+               var tds = div.getElementsByTagName("td");
+
+               // Check if table cells still have offsetWidth/Height when they are set
+               // to display:none and there are still other visible table cells in a
+               // table row; if so, offsetWidth/Height are not reliable for use when
+               // determining if an element has been hidden directly using
+               // display:none (it is still safe to use offsets if a parent element is
+               // hidden; don safety goggles and see bug #4512 for more information).
+               // (only IE 8 fails this test)
+               jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0;
+
+               tds[0].style.display = "";
+               tds[1].style.display = "none";
+
+               // Check if empty table cells still have offsetWidth/Height
+               // (IE < 8 fail this test)
+               jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0;
+               div.innerHTML = "";
+
+               document.body.removeChild( div ).style.display = "none";
+               div = tds = null;
+       });
+
+       // Technique from Juriy Zaytsev
+       // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+       var eventSupported = function( eventName ) {
+               var el = document.createElement("div");
+               eventName = "on" + eventName;
+
+               var isSupported = (eventName in el);
+               if ( !isSupported ) {
+                       el.setAttribute(eventName, "return;");
+                       isSupported = typeof el[eventName] === "function";
+               }
+               el = null;
+
+               return isSupported;
+       };
+
+       jQuery.support.submitBubbles = eventSupported("submit");
+       jQuery.support.changeBubbles = eventSupported("change");
+
+       // release memory in IE
+       root = script = div = all = a = null;
+})();
+
+jQuery.props = {
+       "for": "htmlFor",
+       "class": "className",
+       readonly: "readOnly",
+       maxlength: "maxLength",
+       cellspacing: "cellSpacing",
+       rowspan: "rowSpan",
+       colspan: "colSpan",
+       tabindex: "tabIndex",
+       usemap: "useMap",
+       frameborder: "frameBorder"
+};
+
+
+
+
+var windowData = {},
+       rbrace = /^(?:\{.*\}|\[.*\])$/;
+
+jQuery.extend({
+       cache: {},
+
+       // Please use with caution
+       uuid: 0,
+
+       // Unique for each copy of jQuery on the page   
+       expando: "jQuery" + jQuery.now(),
+
+       // The following elements throw uncatchable exceptions if you
+       // attempt to add expando properties to them.
+       noData: {
+               "embed": true,
+               // Ban all objects except for Flash (which handle expandos)
+               "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+               "applet": true
+       },
+
+       data: function( elem, name, data ) {
+               if ( !jQuery.acceptData( elem ) ) {
+                       return;
+               }
+
+               elem = elem == window ?
+                       windowData :
+                       elem;
+
+               var isNode = elem.nodeType,
+                       id = isNode ? elem[ jQuery.expando ] : null,
+                       cache = jQuery.cache, thisCache;
+
+               if ( isNode && !id && typeof name === "string" && data === undefined ) {
+                       return;
+               }
+
+               // Get the data from the object directly
+               if ( !isNode ) {
+                       cache = elem;
+
+               // Compute a unique ID for the element
+               } else if ( !id ) {
+                       elem[ jQuery.expando ] = id = ++jQuery.uuid;
+               }
+
+               // Avoid generating a new cache unless none exists and we
+               // want to manipulate it.
+               if ( typeof name === "object" ) {
+                       if ( isNode ) {
+                               cache[ id ] = jQuery.extend(cache[ id ], name);
+
+                       } else {
+                               jQuery.extend( cache, name );
+                       }
+
+               } else if ( isNode && !cache[ id ] ) {
+                       cache[ id ] = {};
+               }
+
+               thisCache = isNode ? cache[ id ] : cache;
+
+               // Prevent overriding the named cache with undefined values
+               if ( data !== undefined ) {
+                       thisCache[ name ] = data;
+               }
+
+               return typeof name === "string" ? thisCache[ name ] : thisCache;
+       },
+
+       removeData: function( elem, name ) {
+               if ( !jQuery.acceptData( elem ) ) {
+                       return;
+               }
+
+               elem = elem == window ?
+                       windowData :
+                       elem;
+
+               var isNode = elem.nodeType,
+                       id = isNode ? elem[ jQuery.expando ] : elem,
+                       cache = jQuery.cache,
+                       thisCache = isNode ? cache[ id ] : id;
+
+               // If we want to remove a specific section of the element's data
+               if ( name ) {
+                       if ( thisCache ) {
+                               // Remove the section of cache data
+                               delete thisCache[ name ];
+
+                               // If we've removed all the data, remove the element's cache
+                               if ( isNode && jQuery.isEmptyObject(thisCache) ) {
+                                       jQuery.removeData( elem );
+                               }
+                       }
+
+               // Otherwise, we want to remove all of the element's data
+               } else {
+                       if ( isNode && jQuery.support.deleteExpando ) {
+                               delete elem[ jQuery.expando ];
+
+                       } else if ( elem.removeAttribute ) {
+                               elem.removeAttribute( jQuery.expando );
+
+                       // Completely remove the data cache
+                       } else if ( isNode ) {
+                               delete cache[ id ];
+
+                       // Remove all fields from the object
+                       } else {
+                               for ( var n in elem ) {
+                                       delete elem[ n ];
+                               }
+                       }
+               }
+       },
+
+       // A method for determining if a DOM node can handle the data expando
+       acceptData: function( elem ) {
+               if ( elem.nodeName ) {
+                       var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+                       if ( match ) {
+                               return !(match === true || elem.getAttribute("classid") !== match);
+                       }
+               }
+
+               return true;
+       }
+});
+
+jQuery.fn.extend({
+       data: function( key, value ) {
+               if ( typeof key === "undefined" ) {
+                       return this.length ? jQuery.data( this[0] ) : null;
+
+               } else if ( typeof key === "object" ) {
+                       return this.each(function() {
+                               jQuery.data( this, key );
+                       });
+               }
+
+               var parts = key.split(".");
+               parts[1] = parts[1] ? "." + parts[1] : "";
+
+               if ( value === undefined ) {
+                       var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+                       // Try to fetch any internally stored data first
+                       if ( data === undefined && this.length ) {
+                               data = jQuery.data( this[0], key );
+
+                               // If nothing was found internally, try to fetch any
+                               // data from the HTML5 data-* attribute
+                               if ( data === undefined && this[0].nodeType === 1 ) {
+                                       data = this[0].getAttribute( "data-" + key );
+
+                                       if ( typeof data === "string" ) {
+                                               try {
+                                                       data = data === "true" ? true :
+                                                               data === "false" ? false :
+                                                               data === "null" ? null :
+                                                               !jQuery.isNaN( data ) ? parseFloat( data ) :
+                                                               rbrace.test( data ) ? jQuery.parseJSON( data ) :
+                                                               data;
+                                               } catch( e ) {}
+
+                                       } else {
+                                               data = undefined;
+                                       }
+                               }
+                       }
+
+                       return data === undefined && parts[1] ?
+                               this.data( parts[0] ) :
+                               data;
+
+               } else {
+                       return this.each(function() {
+                               var $this = jQuery( this ), args = [ parts[0], value ];
+
+                               $this.triggerHandler( "setData" + parts[1] + "!", args );
+                               jQuery.data( this, key, value );
+                               $this.triggerHandler( "changeData" + parts[1] + "!", args );
+                       });
+               }
+       },
+
+       removeData: function( key ) {
+               return this.each(function() {
+                       jQuery.removeData( this, key );
+               });
+       }
+});
+
+
+
+
+jQuery.extend({
+       queue: function( elem, type, data ) {
+               if ( !elem ) {
+                       return;
+               }
+
+               type = (type || "fx") + "queue";
+               var q = jQuery.data( elem, type );
+
+               // Speed up dequeue by getting out quickly if this is just a lookup
+               if ( !data ) {
+                       return q || [];
+               }
+
+               if ( !q || jQuery.isArray(data) ) {
+                       q = jQuery.data( elem, type, jQuery.makeArray(data) );
+
+               } else {
+                       q.push( data );
+               }
+
+               return q;
+       },
+
+       dequeue: function( elem, type ) {
+               type = type || "fx";
+
+               var queue = jQuery.queue( elem, type ), fn = queue.shift();
+
+               // If the fx queue is dequeued, always remove the progress sentinel
+               if ( fn === "inprogress" ) {
+                       fn = queue.shift();
+               }
+
+               if ( fn ) {
+                       // Add a progress sentinel to prevent the fx queue from being
+                       // automatically dequeued
+                       if ( type === "fx" ) {
+                               queue.unshift("inprogress");
+                       }
+
+                       fn.call(elem, function() {
+                               jQuery.dequeue(elem, type);
+                       });
+               }
+       }
+});
+
+jQuery.fn.extend({
+       queue: function( type, data ) {
+               if ( typeof type !== "string" ) {
+                       data = type;
+                       type = "fx";
+               }
+
+               if ( data === undefined ) {
+                       return jQuery.queue( this[0], type );
+               }
+               return this.each(function( i ) {
+                       var queue = jQuery.queue( this, type, data );
+
+                       if ( type === "fx" && queue[0] !== "inprogress" ) {
+                               jQuery.dequeue( this, type );
+                       }
+               });
+       },
+       dequeue: function( type ) {
+               return this.each(function() {
+                       jQuery.dequeue( this, type );
+               });
+       },
+
+       // Based off of the plugin by Clint Helfers, with permission.
+       // http://blindsignals.com/index.php/2009/07/jquery-delay/
+       delay: function( time, type ) {
+               time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
+               type = type || "fx";
+
+               return this.queue( type, function() {
+                       var elem = this;
+                       setTimeout(function() {
+                               jQuery.dequeue( elem, type );
+                       }, time );
+               });
+       },
+
+       clearQueue: function( type ) {
+               return this.queue( type || "fx", [] );
+       }
+});
+
+
+
+
+var rclass = /[\n\t]/g,
+       rspaces = /\s+/,
+       rreturn = /\r/g,
+       rspecialurl = /^(?:href|src|style)$/,
+       rtype = /^(?:button|input)$/i,
+       rfocusable = /^(?:button|input|object|select|textarea)$/i,
+       rclickable = /^a(?:rea)?$/i,
+       rradiocheck = /^(?:radio|checkbox)$/i;
+
+jQuery.fn.extend({
+       attr: function( name, value ) {
+               return jQuery.access( this, name, value, true, jQuery.attr );
+       },
+
+       removeAttr: function( name, fn ) {
+               return this.each(function(){
+                       jQuery.attr( this, name, "" );
+                       if ( this.nodeType === 1 ) {
+                               this.removeAttribute( name );
+                       }
+               });
+       },
+
+       addClass: function( value ) {
+               if ( jQuery.isFunction(value) ) {
+                       return this.each(function(i) {
+                               var self = jQuery(this);
+                               self.addClass( value.call(this, i, self.attr("class")) );
+                       });
+               }
+
+               if ( value && typeof value === "string" ) {
+                       var classNames = (value || "").split( rspaces );
+
+                       for ( var i = 0, l = this.length; i < l; i++ ) {
+                               var elem = this[i];
+
+                               if ( elem.nodeType === 1 ) {
+                                       if ( !elem.className ) {
+                                               elem.className = value;
+
+                                       } else {
+                                               var className = " " + elem.className + " ", setClass = elem.className;
+                                               for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+                                                       if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
+                                                               setClass += " " + classNames[c];
+                                                       }
+                                               }
+                                               elem.className = jQuery.trim( setClass );
+                                       }
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       removeClass: function( value ) {
+               if ( jQuery.isFunction(value) ) {
+                       return this.each(function(i) {
+                               var self = jQuery(this);
+                               self.removeClass( value.call(this, i, self.attr("class")) );
+                       });
+               }
+
+               if ( (value && typeof value === "string") || value === undefined ) {
+                       var classNames = (value || "").split( rspaces );
+
+                       for ( var i = 0, l = this.length; i < l; i++ ) {
+                               var elem = this[i];
+
+                               if ( elem.nodeType === 1 && elem.className ) {
+                                       if ( value ) {
+                                               var className = (" " + elem.className + " ").replace(rclass, " ");
+                                               for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+                                                       className = className.replace(" " + classNames[c] + " ", " ");
+                                               }
+                                               elem.className = jQuery.trim( className );
+
+                                       } else {
+                                               elem.className = "";
+                                       }
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       toggleClass: function( value, stateVal ) {
+               var type = typeof value, isBool = typeof stateVal === "boolean";
+
+               if ( jQuery.isFunction( value ) ) {
+                       return this.each(function(i) {
+                               var self = jQuery(this);
+                               self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
+                       });
+               }
+
+               return this.each(function() {
+                       if ( type === "string" ) {
+                               // toggle individual class names
+                               var className, i = 0, self = jQuery(this),
+                                       state = stateVal,
+                                       classNames = value.split( rspaces );
+
+                               while ( (className = classNames[ i++ ]) ) {
+                                       // check each className given, space seperated list
+                                       state = isBool ? state : !self.hasClass( className );
+                                       self[ state ? "addClass" : "removeClass" ]( className );
+                               }
+
+                       } else if ( type === "undefined" || type === "boolean" ) {
+                               if ( this.className ) {
+                                       // store className if set
+                                       jQuery.data( this, "__className__", this.className );
+                               }
+
+                               // toggle whole className
+                               this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || "";
+                       }
+               });
+       },
+
+       hasClass: function( selector ) {
+               var className = " " + selector + " ";
+               for ( var i = 0, l = this.length; i < l; i++ ) {
+                       if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       },
+
+       val: function( value ) {
+               if ( !arguments.length ) {
+                       var elem = this[0];
+
+                       if ( elem ) {
+                               if ( jQuery.nodeName( elem, "option" ) ) {
+                                       // attributes.value is undefined in Blackberry 4.7 but
+                                       // uses .value. See #6932
+                                       var val = elem.attributes.value;
+                                       return !val || val.specified ? elem.value : elem.text;
+                               }
+
+                               // We need to handle select boxes special
+                               if ( jQuery.nodeName( elem, "select" ) ) {
+                                       var index = elem.selectedIndex,
+                                               values = [],
+                                               options = elem.options,
+                                               one = elem.type === "select-one";
+
+                                       // Nothing was selected
+                                       if ( index < 0 ) {
+                                               return null;
+                                       }
+
+                                       // Loop through all the selected options
+                                       for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+                                               var option = options[ i ];
+
+                                               // Don't return options that are disabled or in a disabled optgroup
+                                               if ( option.selected && (jQuery.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;
+                               }
+
+                               // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+                               if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
+                                       return elem.getAttribute("value") === null ? "on" : elem.value;
+                               }
+                               
+
+                               // Everything else, we just grab the value
+                               return (elem.value || "").replace(rreturn, "");
+
+                       }
+
+                       return undefined;
+               }
+
+               var isFunction = jQuery.isFunction(value);
+
+               return this.each(function(i) {
+                       var self = jQuery(this), val = value;
+
+                       if ( this.nodeType !== 1 ) {
+                               return;
+                       }
+
+                       if ( isFunction ) {
+                               val = value.call(this, i, self.val());
+                       }
+
+                       // 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 + "";
+                               });
+                       }
+
+                       if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
+                               this.checked = jQuery.inArray( self.val(), val ) >= 0;
+
+                       } else if ( jQuery.nodeName( this, "select" ) ) {
+                               var values = jQuery.makeArray(val);
+
+                               jQuery( "option", this ).each(function() {
+                                       this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+                               });
+
+                               if ( !values.length ) {
+                                       this.selectedIndex = -1;
+                               }
+
+                       } else {
+                               this.value = val;
+                       }
+               });
+       }
+});
+
+jQuery.extend({
+       attrFn: {
+               val: true,
+               css: true,
+               html: true,
+               text: true,
+               data: true,
+               width: true,
+               height: true,
+               offset: true
+       },
+               
+       attr: function( elem, name, value, pass ) {
+               // don't set attributes on text and comment nodes
+               if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+                       return undefined;
+               }
+
+               if ( pass && name in jQuery.attrFn ) {
+                       return jQuery(elem)[name](value);
+               }
+
+               var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
+                       // Whether we are setting (or getting)
+                       set = value !== undefined;
+
+               // Try to normalize/fix the name
+               name = notxml && jQuery.props[ name ] || name;
+
+               // Only do all the following if this is a node (faster for style)
+               if ( elem.nodeType === 1 ) {
+                       // These attributes require special treatment
+                       var special = rspecialurl.test( name );
+
+                       // Safari mis-reports the default selected property of an option
+                       // Accessing the parent's selectedIndex property fixes it
+                       if ( name === "selected" && !jQuery.support.optSelected ) {
+                               var parent = elem.parentNode;
+                               if ( parent ) {
+                                       parent.selectedIndex;
+       
+                                       // Make sure that it also works with optgroups, see #5701
+                                       if ( parent.parentNode ) {
+                                               parent.parentNode.selectedIndex;
+                                       }
+                               }
+                       }
+
+                       // If applicable, access the attribute via the DOM 0 way
+                       // 'in' checks fail in Blackberry 4.7 #6931
+                       if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) {
+                               if ( set ) {
+                                       // We can't allow the type property to be changed (since it causes problems in IE)
+                                       if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
+                                               jQuery.error( "type property can't be changed" );
+                                       }
+
+                                       if ( value === null ) {
+                                               if ( elem.nodeType === 1 ) {
+                                                       elem.removeAttribute( name );
+                                               }
+
+                                       } else {
+                                               elem[ name ] = value;
+                                       }
+                               }
+
+                               // browsers index elements by id/name on forms, give priority to attributes.
+                               if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
+                                       return elem.getAttributeNode( name ).nodeValue;
+                               }
+
+                               // 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/
+                               if ( name === "tabIndex" ) {
+                                       var attributeNode = elem.getAttributeNode( "tabIndex" );
+
+                                       return attributeNode && attributeNode.specified ?
+                                               attributeNode.value :
+                                               rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+                                                       0 :
+                                                       undefined;
+                               }
+
+                               return elem[ name ];
+                       }
+
+                       if ( !jQuery.support.style && notxml && name === "style" ) {
+                               if ( set ) {
+                                       elem.style.cssText = "" + value;
+                               }
+
+                               return elem.style.cssText;
+                       }
+
+                       if ( set ) {
+                               // convert the value to a string (all browsers do this but IE) see #1070
+                               elem.setAttribute( name, "" + value );
+                       }
+
+                       // Ensure that missing attributes return undefined
+                       // Blackberry 4.7 returns "" from getAttribute #6938
+                       if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) {
+                               return undefined;
+                       }
+
+                       var attr = !jQuery.support.hrefNormalized && notxml && special ?
+                                       // Some attributes require a special call on IE
+                                       elem.getAttribute( name, 2 ) :
+                                       elem.getAttribute( name );
+
+                       // Non-existent attributes return null, we normalize to undefined
+                       return attr === null ? undefined : attr;
+               }
+       }
+});
+
+
+
+
+var rnamespaces = /\.(.*)$/,
+       rformElems = /^(?:textarea|input|select)$/i,
+       rperiod = /\./g,
+       rspace = / /g,
+       rescape = /[^\w\s.|`]/g,
+       fcleanup = function( nm ) {
+               return nm.replace(rescape, "\\$&");
+       },
+       focusCounts = { focusin: 0, focusout: 0 };
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+       // Bind an event to an element
+       // Original by Dean Edwards
+       add: function( elem, types, handler, data ) {
+               if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+                       return;
+               }
+
+               // For whatever reason, IE has trouble passing the window object
+               // around, causing it to be cloned in the process
+               if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
+                       elem = window;
+               }
+
+               if ( handler === false ) {
+                       handler = returnFalse;
+               }
+
+               var handleObjIn, handleObj;
+
+               if ( handler.handler ) {
+                       handleObjIn = handler;
+                       handler = handleObjIn.handler;
+               }
+
+               // Make sure that the function being executed has a unique ID
+               if ( !handler.guid ) {
+                       handler.guid = jQuery.guid++;
+               }
+
+               // Init the element's event structure
+               var elemData = jQuery.data( elem );
+
+               // If no elemData is found then we must be trying to bind to one of the
+               // banned noData elements
+               if ( !elemData ) {
+                       return;
+               }
+
+               // Use a key less likely to result in collisions for plain JS objects.
+               // Fixes bug #7150.
+               var eventKey = elem.nodeType ? "events" : "__events__",
+                       events = elemData[ eventKey ],
+                       eventHandle = elemData.handle;
+                       
+               if ( typeof events === "function" ) {
+                       // On plain objects events is a fn that holds the the data
+                       // which prevents this data from being JSON serialized
+                       // the function does not need to be called, it just contains the data
+                       eventHandle = events.handle;
+                       events = events.events;
+
+               } else if ( !events ) {
+                       if ( !elem.nodeType ) {
+                               // On plain objects, create a fn that acts as the holder
+                               // of the values to avoid JSON serialization of event data
+                               elemData[ eventKey ] = elemData = function(){};
+                       }
+
+                       elemData.events = events = {};
+               }
+
+               if ( !eventHandle ) {
+                       elemData.handle = eventHandle = function() {
+                               // Handle the second event of a trigger and when
+                               // an event is called after a page has unloaded
+                               return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+                                       jQuery.event.handle.apply( eventHandle.elem, arguments ) :
+                                       undefined;
+                       };
+               }
+
+               // Add elem as a property of the handle function
+               // This is to prevent a memory leak with non-native events in IE.
+               eventHandle.elem = elem;
+
+               // Handle multiple events separated by a space
+               // jQuery(...).bind("mouseover mouseout", fn);
+               types = types.split(" ");
+
+               var type, i = 0, namespaces;
+
+               while ( (type = types[ i++ ]) ) {
+                       handleObj = handleObjIn ?
+                               jQuery.extend({}, handleObjIn) :
+                               { handler: handler, data: data };
+
+                       // Namespaced event handlers
+                       if ( type.indexOf(".") > -1 ) {
+                               namespaces = type.split(".");
+                               type = namespaces.shift();
+                               handleObj.namespace = namespaces.slice(0).sort().join(".");
+
+                       } else {
+                               namespaces = [];
+                               handleObj.namespace = "";
+                       }
+
+                       handleObj.type = type;
+                       if ( !handleObj.guid ) {
+                               handleObj.guid = handler.guid;
+                       }
+
+                       // Get the current list of functions bound to this event
+                       var handlers = events[ type ],
+                               special = jQuery.event.special[ type ] || {};
+
+                       // Init the event handler queue
+                       if ( !handlers ) {
+                               handlers = events[ type ] = [];
+
+                               // Check for a special event handler
+                               // Only use addEventListener/attachEvent if the special
+                               // events handler returns false
+                               if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+                                       // Bind the global event handler to the element
+                                       if ( elem.addEventListener ) {
+                                               elem.addEventListener( type, eventHandle, false );
+
+                                       } else if ( elem.attachEvent ) {
+                                               elem.attachEvent( "on" + type, eventHandle );
+                                       }
+                               }
+                       }
+                       
+                       if ( special.add ) { 
+                               special.add.call( elem, handleObj ); 
+
+                               if ( !handleObj.handler.guid ) {
+                                       handleObj.handler.guid = handler.guid;
+                               }
+                       }
+
+                       // Add the function to the element's handler list
+                       handlers.push( handleObj );
+
+                       // Keep track of which events have been used, for global triggering
+                       jQuery.event.global[ type ] = true;
+               }
+
+               // Nullify elem to prevent memory leaks in IE
+               elem = null;
+       },
+
+       global: {},
+
+       // Detach an event or set of events from an element
+       remove: function( elem, types, handler, pos ) {
+               // don't do events on text and comment nodes
+               if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+                       return;
+               }
+
+               if ( handler === false ) {
+                       handler = returnFalse;
+               }
+
+               var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+                       eventKey = elem.nodeType ? "events" : "__events__",
+                       elemData = jQuery.data( elem ),
+                       events = elemData && elemData[ eventKey ];
+
+               if ( !elemData || !events ) {
+                       return;
+               }
+               
+               if ( typeof events === "function" ) {
+                       elemData = events;
+                       events = events.events;
+               }
+
+               // types is actually an event object here
+               if ( types && types.type ) {
+                       handler = types.handler;
+                       types = types.type;
+               }
+
+               // Unbind all events for the element
+               if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
+                       types = types || "";
+
+                       for ( type in events ) {
+                               jQuery.event.remove( elem, type + types );
+                       }
+
+                       return;
+               }
+
+               // Handle multiple events separated by a space
+               // jQuery(...).unbind("mouseover mouseout", fn);
+               types = types.split(" ");
+
+               while ( (type = types[ i++ ]) ) {
+                       origType = type;
+                       handleObj = null;
+                       all = type.indexOf(".") < 0;
+                       namespaces = [];
+
+                       if ( !all ) {
+                               // Namespaced event handlers
+                               namespaces = type.split(".");
+                               type = namespaces.shift();
+
+                               namespace = new RegExp("(^|\\.)" + 
+                                       jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
+                       }
+
+                       eventType = events[ type ];
+
+                       if ( !eventType ) {
+                               continue;
+                       }
+
+                       if ( !handler ) {
+                               for ( j = 0; j < eventType.length; j++ ) {
+                                       handleObj = eventType[ j ];
+
+                                       if ( all || namespace.test( handleObj.namespace ) ) {
+                                               jQuery.event.remove( elem, origType, handleObj.handler, j );
+                                               eventType.splice( j--, 1 );
+                                       }
+                               }
+
+                               continue;
+                       }
+
+                       special = jQuery.event.special[ type ] || {};
+
+                       for ( j = pos || 0; j < eventType.length; j++ ) {
+                               handleObj = eventType[ j ];
+
+                               if ( handler.guid === handleObj.guid ) {
+                                       // remove the given handler for the given type
+                                       if ( all || namespace.test( handleObj.namespace ) ) {
+                                               if ( pos == null ) {
+                                                       eventType.splice( j--, 1 );
+                                               }
+
+                                               if ( special.remove ) {
+                                                       special.remove.call( elem, handleObj );
+                                               }
+                                       }
+
+                                       if ( pos != null ) {
+                                               break;
+                                       }
+                               }
+                       }
+
+                       // remove generic event handler if no more handlers exist
+                       if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+                               if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+                                       jQuery.removeEvent( elem, type, elemData.handle );
+                               }
+
+                               ret = null;
+                               delete events[ type ];
+                       }
+               }
+
+               // Remove the expando if it's no longer used
+               if ( jQuery.isEmptyObject( events ) ) {
+                       var handle = elemData.handle;
+                       if ( handle ) {
+                               handle.elem = null;
+                       }
+
+                       delete elemData.events;
+                       delete elemData.handle;
+
+                       if ( typeof elemData === "function" ) {
+                               jQuery.removeData( elem, eventKey );
+
+                       } else if ( jQuery.isEmptyObject( elemData ) ) {
+                               jQuery.removeData( elem );
+                       }
+               }
+       },
+
+       // bubbling is internal
+       trigger: function( event, data, elem /*, bubbling */ ) {
+               // Event object or event type
+               var type = event.type || event,
+                       bubbling = arguments[3];
+
+               if ( !bubbling ) {
+                       event = typeof event === "object" ?
+                               // jQuery.Event object
+                               event[ jQuery.expando ] ? event :
+                               // Object literal
+                               jQuery.extend( jQuery.Event(type), event ) :
+                               // Just the event type (string)
+                               jQuery.Event(type);
+
+                       if ( type.indexOf("!") >= 0 ) {
+                               event.type = type = type.slice(0, -1);
+                               event.exclusive = true;
+                       }
+
+                       // Handle a global trigger
+                       if ( !elem ) {
+                               // Don't bubble custom events when global (to avoid too much overhead)
+                               event.stopPropagation();
+
+                               // Only trigger if we've ever bound an event for it
+                               if ( jQuery.event.global[ type ] ) {
+                                       jQuery.each( jQuery.cache, function() {
+                                               if ( this.events && this.events[type] ) {
+                                                       jQuery.event.trigger( event, data, this.handle.elem );
+                                               }
+                                       });
+                               }
+                       }
+
+                       // Handle triggering a single element
+
+                       // don't do events on text and comment nodes
+                       if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+                               return undefined;
+                       }
+
+                       // Clean up in case it is reused
+                       event.result = undefined;
+                       event.target = elem;
+
+                       // Clone the incoming data, if any
+                       data = jQuery.makeArray( data );
+                       data.unshift( event );
+               }
+
+               event.currentTarget = elem;
+
+               // Trigger the event, it is assumed that "handle" is a function
+               var handle = elem.nodeType ?
+                       jQuery.data( elem, "handle" ) :
+                       (jQuery.data( elem, "__events__" ) || {}).handle;
+
+               if ( handle ) {
+                       handle.apply( elem, data );
+               }
+
+               var parent = elem.parentNode || elem.ownerDocument;
+
+               // Trigger an inline bound script
+               try {
+                       if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
+                               if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
+                                       event.result = false;
+                                       event.preventDefault();
+                               }
+                       }
+
+               // prevent IE from throwing an error for some elements with some event types, see #3533
+               } catch (inlineError) {}
+
+               if ( !event.isPropagationStopped() && parent ) {
+                       jQuery.event.trigger( event, data, parent, true );
+
+               } else if ( !event.isDefaultPrevented() ) {
+                       var target = event.target, old, targetType = type.replace(rnamespaces, ""),
+                               isClick = jQuery.nodeName(target, "a") && targetType === "click",
+                               special = jQuery.event.special[ targetType ] || {};
+
+                       if ( (!special._default || special._default.call( elem, event ) === false) && 
+                               !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
+
+                               try {
+                                       if ( target[ targetType ] ) {
+                                               // Make sure that we don't accidentally re-trigger the onFOO events
+                                               old = target[ "on" + targetType ];
+
+                                               if ( old ) {
+                                                       target[ "on" + targetType ] = null;
+                                               }
+
+                                               jQuery.event.triggered = true;
+                                               target[ targetType ]();
+                                       }
+
+                               // prevent IE from throwing an error for some elements with some event types, see #3533
+                               } catch (triggerError) {}
+
+                               if ( old ) {
+                                       target[ "on" + targetType ] = old;
+                               }
+
+                               jQuery.event.triggered = false;
+                       }
+               }
+       },
+
+       handle: function( event ) {
+               var all, handlers, namespaces, namespace_sort = [], namespace_re, events, args = jQuery.makeArray( arguments );
+
+               event = args[0] = jQuery.event.fix( event || window.event );
+               event.currentTarget = this;
+
+               // Namespaced event handlers
+               all = event.type.indexOf(".") < 0 && !event.exclusive;
+
+               if ( !all ) {
+                       namespaces = event.type.split(".");
+                       event.type = namespaces.shift();
+                       namespace_sort = namespaces.slice(0).sort();
+                       namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
+               }
+
+               event.namespace = event.namespace || namespace_sort.join(".");
+
+               events = jQuery.data(this, this.nodeType ? "events" : "__events__");
+
+               if ( typeof events === "function" ) {
+                       events = events.events;
+               }
+
+               handlers = (events || {})[ event.type ];
+
+               if ( events && handlers ) {
+                       // Clone the handlers to prevent manipulation
+                       handlers = handlers.slice(0);
+
+                       for ( var j = 0, l = handlers.length; j < l; j++ ) {
+                               var handleObj = handlers[ j ];
+
+                               // Filter the functions by class
+                               if ( all || namespace_re.test( handleObj.namespace ) ) {
+                                       // Pass in a reference to the handler function itself
+                                       // So that we can later remove it
+                                       event.handler = handleObj.handler;
+                                       event.data = handleObj.data;
+                                       event.handleObj = handleObj;
+       
+                                       var ret = handleObj.handler.apply( this, args );
+
+                                       if ( ret !== undefined ) {
+                                               event.result = ret;
+                                               if ( ret === false ) {
+                                                       event.preventDefault();
+                                                       event.stopPropagation();
+                                               }
+                                       }
+
+                                       if ( event.isImmediatePropagationStopped() ) {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               return event.result;
+       },
+
+       props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+       fix: function( event ) {
+               if ( event[ jQuery.expando ] ) {
+                       return event;
+               }
+
+               // store a copy of the original event object
+               // and "clone" to set read-only properties
+               var originalEvent = event;
+               event = jQuery.Event( originalEvent );
+
+               for ( var i = this.props.length, prop; i; ) {
+                       prop = this.props[ --i ];
+                       event[ prop ] = originalEvent[ prop ];
+               }
+
+               // Fix target property, if necessary
+               if ( !event.target ) {
+                       event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+               }
+
+               // check if target is a textnode (safari)
+               if ( event.target.nodeType === 3 ) {
+                       event.target = event.target.parentNode;
+               }
+
+               // Add relatedTarget, if necessary
+               if ( !event.relatedTarget && event.fromElement ) {
+                       event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+               }
+
+               // Calculate pageX/Y if missing and clientX/Y available
+               if ( event.pageX == null && event.clientX != null ) {
+                       var doc = document.documentElement, body = document.body;
+                       event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+                       event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
+               }
+
+               // Add which for key events
+               if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
+                       event.which = event.charCode != null ? event.charCode : event.keyCode;
+               }
+
+               // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+               if ( !event.metaKey && event.ctrlKey ) {
+                       event.metaKey = event.ctrlKey;
+               }
+
+               // Add which for click: 1 === left; 2 === middle; 3 === right
+               // Note: button is not normalized, so don't use it
+               if ( !event.which && event.button !== undefined ) {
+                       event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+               }
+
+               return event;
+       },
+
+       // Deprecated, use jQuery.guid instead
+       guid: 1E8,
+
+       // Deprecated, use jQuery.proxy instead
+       proxy: jQuery.proxy,
+
+       special: {
+               ready: {
+                       // Make sure the ready event is setup
+                       setup: jQuery.bindReady,
+                       teardown: jQuery.noop
+               },
+
+               live: {
+                       add: function( handleObj ) {
+                               jQuery.event.add( this,
+                                       liveConvert( handleObj.origType, handleObj.selector ),
+                                       jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); 
+                       },
+
+                       remove: function( handleObj ) {
+                               jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
+                       }
+               },
+
+               beforeunload: {
+                       setup: function( data, namespaces, eventHandle ) {
+                               // We only want to do this special case on windows
+                               if ( jQuery.isWindow( this ) ) {
+                                       this.onbeforeunload = eventHandle;
+                               }
+                       },
+
+                       teardown: function( namespaces, eventHandle ) {
+                               if ( this.onbeforeunload === eventHandle ) {
+                                       this.onbeforeunload = null;
+                               }
+                       }
+               }
+       }
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+       function( elem, type, handle ) {
+               if ( elem.removeEventListener ) {
+                       elem.removeEventListener( type, handle, false );
+               }
+       } : 
+       function( elem, type, handle ) {
+               if ( elem.detachEvent ) {
+                       elem.detachEvent( "on" + type, handle );
+               }
+       };
+
+jQuery.Event = function( src ) {
+       // Allow instantiation without the 'new' keyword
+       if ( !this.preventDefault ) {
+               return new jQuery.Event( src );
+       }
+
+       // Event object
+       if ( src && src.type ) {
+               this.originalEvent = src;
+               this.type = src.type;
+       // Event type
+       } else {
+               this.type = src;
+       }
+
+       // timeStamp is buggy for some events on Firefox(#3843)
+       // So we won't rely on the native value
+       this.timeStamp = jQuery.now();
+
+       // Mark it as fixed
+       this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+       return false;
+}
+function returnTrue() {
+       return 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 = {
+       preventDefault: function() {
+               this.isDefaultPrevented = returnTrue;
+
+               var e = this.originalEvent;
+               if ( !e ) {
+                       return;
+               }
+               
+               // if preventDefault exists run it on the original event
+               if ( e.preventDefault ) {
+                       e.preventDefault();
+
+               // otherwise set the returnValue property of the original event to false (IE)
+               } else {
+                       e.returnValue = false;
+               }
+       },
+       stopPropagation: function() {
+               this.isPropagationStopped = returnTrue;
+
+               var e = this.originalEvent;
+               if ( !e ) {
+                       return;
+               }
+               // if stopPropagation exists run it on the original event
+               if ( e.stopPropagation ) {
+                       e.stopPropagation();
+               }
+               // otherwise set the cancelBubble property of the original event to true (IE)
+               e.cancelBubble = true;
+       },
+       stopImmediatePropagation: function() {
+               this.isImmediatePropagationStopped = returnTrue;
+               this.stopPropagation();
+       },
+       isDefaultPrevented: returnFalse,
+       isPropagationStopped: returnFalse,
+       isImmediatePropagationStopped: returnFalse
+};
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function( event ) {
+       // Check if mouse(over|out) are still within the same parent element
+       var parent = event.relatedTarget;
+
+       // Firefox sometimes assigns relatedTarget a XUL element
+       // which we cannot access the parentNode property of
+       try {
+               // Traverse up the tree
+               while ( parent && parent !== this ) {
+                       parent = parent.parentNode;
+               }
+
+               if ( parent !== this ) {
+                       // set the correct event type
+                       event.type = event.data;
+
+                       // handle event if we actually just moused on to a non sub-element
+                       jQuery.event.handle.apply( this, arguments );
+               }
+
+       // assuming we've left the element since we most likely mousedover a xul element
+       } catch(e) { }
+},
+
+// In case of event delegation, we only need to rename the event.type,
+// liveHandler will take care of the rest.
+delegate = function( event ) {
+       event.type = event.data;
+       jQuery.event.handle.apply( this, arguments );
+};
+
+// Create mouseenter and mouseleave events
+jQuery.each({
+       mouseenter: "mouseover",
+       mouseleave: "mouseout"
+}, function( orig, fix ) {
+       jQuery.event.special[ orig ] = {
+               setup: function( data ) {
+                       jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
+               },
+               teardown: function( data ) {
+                       jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
+               }
+       };
+});
+
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+       jQuery.event.special.submit = {
+               setup: function( data, namespaces ) {
+                       if ( this.nodeName.toLowerCase() !== "form" ) {
+                               jQuery.event.add(this, "click.specialSubmit", function( e ) {
+                                       var elem = e.target, type = elem.type;
+
+                                       if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+                                               e.liveFired = undefined;
+                                               return trigger( "submit", this, arguments );
+                                       }
+                               });
+        
+                               jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+                                       var elem = e.target, type = elem.type;
+
+                                       if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+                                               e.liveFired = undefined;
+                                               return trigger( "submit", this, arguments );
+                                       }
+                               });
+
+                       } else {
+                               return false;
+                       }
+               },
+
+               teardown: function( namespaces ) {
+                       jQuery.event.remove( this, ".specialSubmit" );
+               }
+       };
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+       var changeFilters,
+
+       getVal = function( elem ) {
+               var type = elem.type, val = elem.value;
+
+               if ( type === "radio" || type === "checkbox" ) {
+                       val = elem.checked;
+
+               } else if ( type === "select-multiple" ) {
+                       val = elem.selectedIndex > -1 ?
+                               jQuery.map( elem.options, function( elem ) {
+                                       return elem.selected;
+                               }).join("-") :
+                               "";
+
+               } else if ( elem.nodeName.toLowerCase() === "select" ) {
+                       val = elem.selectedIndex;
+               }
+
+               return val;
+       },
+
+       testChange = function testChange( e ) {
+               var elem = e.target, data, val;
+
+               if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
+                       return;
+               }
+
+               data = jQuery.data( elem, "_change_data" );
+               val = getVal(elem);
+
+               // the current data will be also retrieved by beforeactivate
+               if ( e.type !== "focusout" || elem.type !== "radio" ) {
+                       jQuery.data( elem, "_change_data", val );
+               }
+               
+               if ( data === undefined || val === data ) {
+                       return;
+               }
+
+               if ( data != null || val ) {
+                       e.type = "change";
+                       e.liveFired = undefined;
+                       return jQuery.event.trigger( e, arguments[1], elem );
+               }
+       };
+
+       jQuery.event.special.change = {
+               filters: {
+                       focusout: testChange, 
+
+                       beforedeactivate: testChange,
+
+                       click: function( e ) {
+                               var elem = e.target, type = elem.type;
+
+                               if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
+                                       return testChange.call( this, e );
+                               }
+                       },
+
+                       // Change has to be called before submit
+                       // Keydown will be called before keypress, which is used in submit-event delegation
+                       keydown: function( e ) {
+                               var elem = e.target, type = elem.type;
+
+                               if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
+                                       (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+                                       type === "select-multiple" ) {
+                                       return testChange.call( this, e );
+                               }
+                       },
+
+                       // Beforeactivate happens also before the previous element is blurred
+                       // with this event you can't trigger a change event, but you can store
+                       // information
+                       beforeactivate: function( e ) {
+                               var elem = e.target;
+                               jQuery.data( elem, "_change_data", getVal(elem) );
+                       }
+               },
+
+               setup: function( data, namespaces ) {
+                       if ( this.type === "file" ) {
+                               return false;
+                       }
+
+                       for ( var type in changeFilters ) {
+                               jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
+                       }
+
+                       return rformElems.test( this.nodeName );
+               },
+
+               teardown: function( namespaces ) {
+                       jQuery.event.remove( this, ".specialChange" );
+
+                       return rformElems.test( this.nodeName );
+               }
+       };
+
+       changeFilters = jQuery.event.special.change.filters;
+
+       // Handle when the input is .focus()'d
+       changeFilters.focus = changeFilters.beforeactivate;
+}
+
+function trigger( type, elem, args ) {
+       args[0].type = type;
+       return jQuery.event.handle.apply( elem, args );
+}
+
+// Create "bubbling" focus and blur events
+if ( document.addEventListener ) {
+       jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+               jQuery.event.special[ fix ] = {
+                       setup: function() {
+                               if ( focusCounts[fix]++ === 0 ) {
+                                       document.addEventListener( orig, handler, true );
+                               }
+                       }, 
+                       teardown: function() { 
+                               if ( --focusCounts[fix] === 0 ) {
+                                       document.removeEventListener( orig, handler, true );
+                               }
+                       }
+               };
+
+               function handler( e ) { 
+                       e = jQuery.event.fix( e );
+                       e.type = fix;
+                       return jQuery.event.trigger( e, null, e.target );
+               }
+       });
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+       jQuery.fn[ name ] = function( type, data, fn ) {
+               // Handle object literals
+               if ( typeof type === "object" ) {
+                       for ( var key in type ) {
+                               this[ name ](key, data, type[key], fn);
+                       }
+                       return this;
+               }
+               
+               if ( jQuery.isFunction( data ) || data === false ) {
+                       fn = data;
+                       data = undefined;
+               }
+
+               var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
+                       jQuery( this ).unbind( event, handler );
+                       return fn.apply( this, arguments );
+               }) : fn;
+
+               if ( type === "unload" && name !== "one" ) {
+                       this.one( type, data, fn );
+
+               } else {
+                       for ( var i = 0, l = this.length; i < l; i++ ) {
+                               jQuery.event.add( this[i], type, handler, data );
+                       }
+               }
+
+               return this;
+       };
+});
+
+jQuery.fn.extend({
+       unbind: function( type, fn ) {
+               // Handle object literals
+               if ( typeof type === "object" && !type.preventDefault ) {
+                       for ( var key in type ) {
+                               this.unbind(key, type[key]);
+                       }
+
+               } else {
+                       for ( var i = 0, l = this.length; i < l; i++ ) {
+                               jQuery.event.remove( this[i], type, fn );
+                       }
+               }
+
+               return this;
+       },
+       
+       delegate: function( selector, types, data, fn ) {
+               return this.live( types, data, fn, selector );
+       },
+       
+       undelegate: function( selector, types, fn ) {
+               if ( arguments.length === 0 ) {
+                               return this.unbind( "live" );
+               
+               } else {
+                       return this.die( types, null, fn, selector );
+               }
+       },
+       
+       trigger: function( type, data ) {
+               return this.each(function() {
+                       jQuery.event.trigger( type, data, this );
+               });
+       },
+
+       triggerHandler: function( type, data ) {
+               if ( this[0] ) {
+                       var event = jQuery.Event( type );
+                       event.preventDefault();
+                       event.stopPropagation();
+                       jQuery.event.trigger( event, data, this[0] );
+                       return event.result;
+               }
+       },
+
+       toggle: function( fn ) {
+               // Save reference to arguments for access in closure
+               var args = arguments, i = 1;
+
+               // link all the functions, so any of them can unbind this click handler
+               while ( i < args.length ) {
+                       jQuery.proxy( fn, args[ i++ ] );
+               }
+
+               return this.click( jQuery.proxy( fn, function( event ) {
+                       // Figure out which function to execute
+                       var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+                       jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+                       // Make sure that clicks stop
+                       event.preventDefault();
+
+                       // and execute the function
+                       return args[ lastToggle ].apply( this, arguments ) || false;
+               }));
+       },
+
+       hover: function( fnOver, fnOut ) {
+               return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+       }
+});
+
+var liveMap = {
+       focus: "focusin",
+       blur: "focusout",
+       mouseenter: "mouseover",
+       mouseleave: "mouseout"
+};
+
+jQuery.each(["live", "die"], function( i, name ) {
+       jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
+               var type, i = 0, match, namespaces, preType,
+                       selector = origSelector || this.selector,
+                       context = origSelector ? this : jQuery( this.context );
+               
+               if ( typeof types === "object" && !types.preventDefault ) {
+                       for ( var key in types ) {
+                               context[ name ]( key, data, types[key], selector );
+                       }
+                       
+                       return this;
+               }
+
+               if ( jQuery.isFunction( data ) ) {
+                       fn = data;
+                       data = undefined;
+               }
+
+               types = (types || "").split(" ");
+
+               while ( (type = types[ i++ ]) != null ) {
+                       match = rnamespaces.exec( type );
+                       namespaces = "";
+
+                       if ( match )  {
+                               namespaces = match[0];
+                               type = type.replace( rnamespaces, "" );
+                       }
+
+                       if ( type === "hover" ) {
+                               types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
+                               continue;
+                       }
+
+                       preType = type;
+
+                       if ( type === "focus" || type === "blur" ) {
+                               types.push( liveMap[ type ] + namespaces );
+                               type = type + namespaces;
+
+                       } else {
+                               type = (liveMap[ type ] || type) + namespaces;
+                       }
+
+                       if ( name === "live" ) {
+                               // bind live handler
+                               for ( var j = 0, l = context.length; j < l; j++ ) {
+                                       jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
+                                               { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
+                               }
+
+                       } else {
+                               // unbind live handler
+                               context.unbind( "live." + liveConvert( type, selector ), fn );
+                       }
+               }
+               
+               return this;
+       };
+});
+
+function liveHandler( event ) {
+       var stop, maxLevel, elems = [], selectors = [],
+               related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
+               events = jQuery.data( this, this.nodeType ? "events" : "__events__" );
+
+       if ( typeof events === "function" ) {
+               events = events.events;
+       }
+
+       // Make sure we avoid non-left-click bubbling in Firefox (#3861)
+       if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) {
+               return;
+       }
+
+       if ( event.namespace ) {
+               namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
+       }
+
+       event.liveFired = this;
+
+       var live = events.live.slice(0);
+
+       for ( j = 0; j < live.length; j++ ) {
+               handleObj = live[j];
+
+               if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
+                       selectors.push( handleObj.selector );
+
+               } else {
+                       live.splice( j--, 1 );
+               }
+       }
+
+       match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+       for ( i = 0, l = match.length; i < l; i++ ) {
+               close = match[i];
+
+               for ( j = 0; j < live.length; j++ ) {
+                       handleObj = live[j];
+
+                       if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) {
+                               elem = close.elem;
+                               related = null;
+
+                               // Those two events require additional checking
+                               if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+                                       event.type = handleObj.preType;
+                                       related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
+                               }
+
+                               if ( !related || related !== elem ) {
+                                       elems.push({ elem: elem, handleObj: handleObj, level: close.level });
+                               }
+                       }
+               }
+       }
+
+       for ( i = 0, l = elems.length; i < l; i++ ) {
+               match = elems[i];
+
+               if ( maxLevel && match.level > maxLevel ) {
+                       break;
+               }
+
+               event.currentTarget = match.elem;
+               event.data = match.handleObj.data;
+               event.handleObj = match.handleObj;
+
+               ret = match.handleObj.origHandler.apply( match.elem, arguments );
+
+               if ( ret === false || event.isPropagationStopped() ) {
+                       maxLevel = match.level;
+
+                       if ( ret === false ) {
+                               stop = false;
+                       }
+               }
+       }
+
+       return stop;
+}
+
+function liveConvert( type, selector ) {
+       return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&");
+}
+
+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").split(" "), function( i, name ) {
+
+       // Handle event binding
+       jQuery.fn[ name ] = function( data, fn ) {
+               if ( fn == null ) {
+                       fn = data;
+                       data = null;
+               }
+
+               return arguments.length > 0 ?
+                       this.bind( name, data, fn ) :
+                       this.trigger( name );
+       };
+
+       if ( jQuery.attrFn ) {
+               jQuery.attrFn[ name ] = true;
+       }
+});
+
+// Prevent memory leaks in IE
+// Window isn't included so as not to unbind existing unload events
+// More info:
+//  - http://isaacschlueter.com/2006/10/msie-memory-leaks/
+if ( window.attachEvent && !window.addEventListener ) {
+       jQuery(window).bind("unload", function() {
+               for ( var id in jQuery.cache ) {
+                       if ( jQuery.cache[ id ].handle ) {
+                               // Try/Catch is to handle iframes being unloaded, see #4280
+                               try {
+                                       jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+                               } catch(e) {}
+                       }
+               }
+       });
+}
+
+
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ *  Copyright 2009, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+       done = 0,
+       toString = Object.prototype.toString,
+       hasDuplicate = false,
+       baseHasDuplicate = true;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function(){
+       baseHasDuplicate = false;
+       return 0;
+});
+
+var Sizzle = function(selector, context, results, seed) {
+       results = results || [];
+       context = context || document;
+
+       var origContext = context;
+
+       if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+               return [];
+       }
+       
+       if ( !selector || typeof selector !== "string" ) {
+               return results;
+       }
+
+       var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
+               soFar = selector, ret, cur, pop, i;
+       
+       // Reset the position of the chunker regexp (start from head)
+       do {
+               chunker.exec("");
+               m = chunker.exec(soFar);
+
+               if ( m ) {
+                       soFar = m[3];
+               
+                       parts.push( m[1] );
+               
+                       if ( m[2] ) {
+                               extra = m[3];
+                               break;
+                       }
+               }
+       } while ( m );
+
+       if ( parts.length > 1 && origPOS.exec( selector ) ) {
+               if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+                       set = posProcess( parts[0] + parts[1], context );
+               } else {
+                       set = Expr.relative[ parts[0] ] ?
+                               [ context ] :
+                               Sizzle( parts.shift(), context );
+
+                       while ( parts.length ) {
+                               selector = parts.shift();
+
+                               if ( Expr.relative[ selector ] ) {
+                                       selector += parts.shift();
+                               }
+                               
+                               set = posProcess( selector, set );
+                       }
+               }
+       } else {
+               // Take a shortcut and set the context if the root selector is an ID
+               // (but not if it'll be faster if the inner selector is an ID)
+               if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+                               Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+                       ret = Sizzle.find( parts.shift(), context, contextXML );
+                       context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+               }
+
+               if ( context ) {
+                       ret = seed ?
+                               { expr: parts.pop(), set: makeArray(seed) } :
+                               Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+                       set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+                       if ( parts.length > 0 ) {
+                               checkSet = makeArray(set);
+                       } else {
+                               prune = false;
+                       }
+
+                       while ( parts.length ) {
+                               cur = parts.pop();
+                               pop = cur;
+
+                               if ( !Expr.relative[ cur ] ) {
+                                       cur = "";
+                               } else {
+                                       pop = parts.pop();
+                               }
+
+                               if ( pop == null ) {
+                                       pop = context;
+                               }
+
+                               Expr.relative[ cur ]( checkSet, pop, contextXML );
+                       }
+               } else {
+                       checkSet = parts = [];
+               }
+       }
+
+       if ( !checkSet ) {
+               checkSet = set;
+       }
+
+       if ( !checkSet ) {
+               Sizzle.error( cur || selector );
+       }
+
+       if ( toString.call(checkSet) === "[object Array]" ) {
+               if ( !prune ) {
+                       results.push.apply( results, checkSet );
+               } else if ( context && context.nodeType === 1 ) {
+                       for ( i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+                                       results.push( set[i] );
+                               }
+                       }
+               } else {
+                       for ( i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+                                       results.push( set[i] );
+                               }
+                       }
+               }
+       } else {
+               makeArray( checkSet, results );
+       }
+
+       if ( extra ) {
+               Sizzle( extra, origContext, results, seed );
+               Sizzle.uniqueSort( results );
+       }
+
+       return results;
+};
+
+Sizzle.uniqueSort = function(results){
+       if ( sortOrder ) {
+               hasDuplicate = baseHasDuplicate;
+               results.sort(sortOrder);
+
+               if ( hasDuplicate ) {
+                       for ( var i = 1; i < results.length; i++ ) {
+                               if ( results[i] === results[i-1] ) {
+                                       results.splice(i--, 1);
+                               }
+                       }
+               }
+       }
+
+       return results;
+};
+
+Sizzle.matches = function(expr, set){
+       return Sizzle(expr, null, null, set);
+};
+
+Sizzle.matchesSelector = function(node, expr){
+       return Sizzle(expr, null, null, [node]).length > 0;
+};
+
+Sizzle.find = function(expr, context, isXML){
+       var set;
+
+       if ( !expr ) {
+               return [];
+       }
+
+       for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+               var type = Expr.order[i], match;
+               
+               if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+                       var left = match[1];
+                       match.splice(1,1);
+
+                       if ( left.substr( left.length - 1 ) !== "\\" ) {
+                               match[1] = (match[1] || "").replace(/\\/g, "");
+                               set = Expr.find[ type ]( match, context, isXML );
+                               if ( set != null ) {
+                                       expr = expr.replace( Expr.match[ type ], "" );
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       if ( !set ) {
+               set = context.getElementsByTagName("*");
+       }
+
+       return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+       var old = expr, result = [], curLoop = set, match, anyFound,
+               isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
+
+       while ( expr && set.length ) {
+               for ( var type in Expr.filter ) {
+                       if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+                               var filter = Expr.filter[ type ], found, item, left = match[1];
+                               anyFound = false;
+
+                               match.splice(1,1);
+
+                               if ( left.substr( left.length - 1 ) === "\\" ) {
+                                       continue;
+                               }
+
+                               if ( curLoop === result ) {
+                                       result = [];
+                               }
+
+                               if ( Expr.preFilter[ type ] ) {
+                                       match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+                                       if ( !match ) {
+                                               anyFound = found = true;
+                                       } else if ( match === true ) {
+                                               continue;
+                                       }
+                               }
+
+                               if ( match ) {
+                                       for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+                                               if ( item ) {
+                                                       found = filter( item, match, i, curLoop );
+                                                       var pass = not ^ !!found;
+
+                                                       if ( inplace && found != null ) {
+                                                               if ( pass ) {
+                                                                       anyFound = true;
+                                                               } else {
+                                                                       curLoop[i] = false;
+                                                               }
+                                                       } else if ( pass ) {
+                                                               result.push( item );
+                                                               anyFound = true;
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               if ( found !== undefined ) {
+                                       if ( !inplace ) {
+                                               curLoop = result;
+                                       }
+
+                                       expr = expr.replace( Expr.match[ type ], "" );
+
+                                       if ( !anyFound ) {
+                                               return [];
+                                       }
+
+                                       break;
+                               }
+                       }
+               }
+
+               // Improper expression
+               if ( expr === old ) {
+                       if ( anyFound == null ) {
+                               Sizzle.error( expr );
+                       } else {
+                               break;
+                       }
+               }
+
+               old = expr;
+       }
+
+       return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+       throw "Syntax error, unrecognized expression: " + msg;
+};
+
+var Expr = Sizzle.selectors = {
+       order: [ "ID", "NAME", "TAG" ],
+       match: {
+               ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+               CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+               NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+               ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+               TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+               CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
+               POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+               PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+       },
+       leftMatch: {},
+       attrMap: {
+               "class": "className",
+               "for": "htmlFor"
+       },
+       attrHandle: {
+               href: function(elem){
+                       return elem.getAttribute("href");
+               }
+       },
+       relative: {
+               "+": function(checkSet, part){
+                       var isPartStr = typeof part === "string",
+                               isTag = isPartStr && !/\W/.test(part),
+                               isPartStrNotTag = isPartStr && !isTag;
+
+                       if ( isTag ) {
+                               part = part.toLowerCase();
+                       }
+
+                       for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+                               if ( (elem = checkSet[i]) ) {
+                                       while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+                                       checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+                                               elem || false :
+                                               elem === part;
+                               }
+                       }
+
+                       if ( isPartStrNotTag ) {
+                               Sizzle.filter( part, checkSet, true );
+                       }
+               },
+               ">": function(checkSet, part){
+                       var isPartStr = typeof part === "string",
+                               elem, i = 0, l = checkSet.length;
+
+                       if ( isPartStr && !/\W/.test(part) ) {
+                               part = part.toLowerCase();
+
+                               for ( ; i < l; i++ ) {
+                                       elem = checkSet[i];
+                                       if ( elem ) {
+                                               var parent = elem.parentNode;
+                                               checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+                                       }
+                               }
+                       } else {
+                               for ( ; i < l; i++ ) {
+                                       elem = checkSet[i];
+                                       if ( elem ) {
+                                               checkSet[i] = isPartStr ?
+                                                       elem.parentNode :
+                                                       elem.parentNode === part;
+                                       }
+                               }
+
+                               if ( isPartStr ) {
+                                       Sizzle.filter( part, checkSet, true );
+                               }
+                       }
+               },
+               "": function(checkSet, part, isXML){
+                       var doneName = done++, checkFn = dirCheck, nodeCheck;
+
+                       if ( typeof part === "string" && !/\W/.test(part) ) {
+                               part = part.toLowerCase();
+                               nodeCheck = part;
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+               },
+               "~": function(checkSet, part, isXML){
+                       var doneName = done++, checkFn = dirCheck, nodeCheck;
+
+                       if ( typeof part === "string" && !/\W/.test(part) ) {
+                               part = part.toLowerCase();
+                               nodeCheck = part;
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+               }
+       },
+       find: {
+               ID: function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+                               // Check parentNode to catch when Blackberry 4.6 returns
+                               // nodes that are no longer in the document #6963
+                               return m && m.parentNode ? [m] : [];
+                       }
+               },
+               NAME: function(match, context){
+                       if ( typeof context.getElementsByName !== "undefined" ) {
+                               var ret = [], results = context.getElementsByName(match[1]);
+
+                               for ( var i = 0, l = results.length; i < l; i++ ) {
+                                       if ( results[i].getAttribute("name") === match[1] ) {
+                                               ret.push( results[i] );
+                                       }
+                               }
+
+                               return ret.length === 0 ? null : ret;
+                       }
+               },
+               TAG: function(match, context){
+                       return context.getElementsByTagName(match[1]);
+               }
+       },
+       preFilter: {
+               CLASS: function(match, curLoop, inplace, result, not, isXML){
+                       match = " " + match[1].replace(/\\/g, "") + " ";
+
+                       if ( isXML ) {
+                               return match;
+                       }
+
+                       for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+                               if ( elem ) {
+                                       if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
+                                               if ( !inplace ) {
+                                                       result.push( elem );
+                                               }
+                                       } else if ( inplace ) {
+                                               curLoop[i] = false;
+                                       }
+                               }
+                       }
+
+                       return false;
+               },
+               ID: function(match){
+                       return match[1].replace(/\\/g, "");
+               },
+               TAG: function(match, curLoop){
+                       return match[1].toLowerCase();
+               },
+               CHILD: function(match){
+                       if ( match[1] === "nth" ) {
+                               // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+                               var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+                                       match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+                                       !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+                               // calculate the numbers (first)n+(last) including if they are negative
+                               match[2] = (test[1] + (test[2] || 1)) - 0;
+                               match[3] = test[3] - 0;
+                       }
+
+                       // TODO: Move to normal caching system
+                       match[0] = done++;
+
+                       return match;
+               },
+               ATTR: function(match, curLoop, inplace, result, not, isXML){
+                       var name = match[1].replace(/\\/g, "");
+                       
+                       if ( !isXML && Expr.attrMap[name] ) {
+                               match[1] = Expr.attrMap[name];
+                       }
+
+                       if ( match[2] === "~=" ) {
+                               match[4] = " " + match[4] + " ";
+                       }
+
+                       return match;
+               },
+               PSEUDO: function(match, curLoop, inplace, result, not){
+                       if ( match[1] === "not" ) {
+                               // If we're dealing with a complex expression, or a simple one
+                               if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+                                       match[3] = Sizzle(match[3], null, null, curLoop);
+                               } else {
+                                       var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+                                       if ( !inplace ) {
+                                               result.push.apply( result, ret );
+                                       }
+                                       return false;
+                               }
+                       } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+                               return true;
+                       }
+                       
+                       return match;
+               },
+               POS: function(match){
+                       match.unshift( true );
+                       return match;
+               }
+       },
+       filters: {
+               enabled: function(elem){
+                       return elem.disabled === false && elem.type !== "hidden";
+               },
+               disabled: function(elem){
+                       return elem.disabled === true;
+               },
+               checked: function(elem){
+                       return elem.checked === true;
+               },
+               selected: function(elem){
+                       // Accessing this property makes selected-by-default
+                       // options in Safari work properly
+                       elem.parentNode.selectedIndex;
+                       return elem.selected === true;
+               },
+               parent: function(elem){
+                       return !!elem.firstChild;
+               },
+               empty: function(elem){
+                       return !elem.firstChild;
+               },
+               has: function(elem, i, match){
+                       return !!Sizzle( match[3], elem ).length;
+               },
+               header: function(elem){
+                       return (/h\d/i).test( elem.nodeName );
+               },
+               text: function(elem){
+                       return "text" === elem.type;
+               },
+               radio: function(elem){
+                       return "radio" === elem.type;
+               },
+               checkbox: function(elem){
+                       return "checkbox" === elem.type;
+               },
+               file: function(elem){
+                       return "file" === elem.type;
+               },
+               password: function(elem){
+                       return "password" === elem.type;
+               },
+               submit: function(elem){
+                       return "submit" === elem.type;
+               },
+               image: function(elem){
+                       return "image" === elem.type;
+               },
+               reset: function(elem){
+                       return "reset" === elem.type;
+               },
+               button: function(elem){
+                       return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
+               },
+               input: function(elem){
+                       return (/input|select|textarea|button/i).test(elem.nodeName);
+               }
+       },
+       setFilters: {
+               first: function(elem, i){
+                       return i === 0;
+               },
+               last: function(elem, i, match, array){
+                       return i === array.length - 1;
+               },
+               even: function(elem, i){
+                       return i % 2 === 0;
+               },
+               odd: function(elem, i){
+                       return i % 2 === 1;
+               },
+               lt: function(elem, i, match){
+                       return i < match[3] - 0;
+               },
+               gt: function(elem, i, match){
+                       return i > match[3] - 0;
+               },
+               nth: function(elem, i, match){
+                       return match[3] - 0 === i;
+               },
+               eq: function(elem, i, match){
+                       return match[3] - 0 === i;
+               }
+       },
+       filter: {
+               PSEUDO: function(elem, match, i, array){
+                       var name = match[1], filter = Expr.filters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+                       } else if ( name === "contains" ) {
+                               return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
+                       } else if ( name === "not" ) {
+                               var not = match[3];
+
+                               for ( var j = 0, l = not.length; j < l; j++ ) {
+                                       if ( not[j] === elem ) {
+                                               return false;
+                                       }
+                               }
+
+                               return true;
+                       } else {
+                               Sizzle.error( "Syntax error, unrecognized expression: " + name );
+                       }
+               },
+               CHILD: function(elem, match){
+                       var type = match[1], node = elem;
+                       switch (type) {
+                               case 'only':
+                               case 'first':
+                                       while ( (node = node.previousSibling) )  {
+                                               if ( node.nodeType === 1 ) { 
+                                                       return false; 
+                                               }
+                                       }
+                                       if ( type === "first" ) { 
+                                               return true; 
+                                       }
+                                       node = elem;
+                               case 'last':
+                                       while ( (node = node.nextSibling) )      {
+                                               if ( node.nodeType === 1 ) { 
+                                                       return false; 
+                                               }
+                                       }
+                                       return true;
+                               case 'nth':
+                                       var first = match[2], last = match[3];
+
+                                       if ( first === 1 && last === 0 ) {
+                                               return true;
+                                       }
+                                       
+                                       var doneName = match[0],
+                                               parent = elem.parentNode;
+       
+                                       if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+                                               var count = 0;
+                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {
+                                                       if ( node.nodeType === 1 ) {
+                                                               node.nodeIndex = ++count;
+                                                       }
+                                               } 
+                                               parent.sizcache = doneName;
+                                       }
+                                       
+                                       var diff = elem.nodeIndex - last;
+                                       if ( first === 0 ) {
+                                               return diff === 0;
+                                       } else {
+                                               return ( diff % first === 0 && diff / first >= 0 );
+                                       }
+                       }
+               },
+               ID: function(elem, match){
+                       return elem.nodeType === 1 && elem.getAttribute("id") === match;
+               },
+               TAG: function(elem, match){
+                       return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
+               },
+               CLASS: function(elem, match){
+                       return (" " + (elem.className || elem.getAttribute("class")) + " ")
+                               .indexOf( match ) > -1;
+               },
+               ATTR: function(elem, match){
+                       var name = match[1],
+                               result = Expr.attrHandle[ name ] ?
+                                       Expr.attrHandle[ name ]( elem ) :
+                                       elem[ name ] != null ?
+                                               elem[ name ] :
+                                               elem.getAttribute( name ),
+                               value = result + "",
+                               type = match[2],
+                               check = match[4];
+
+                       return result == null ?
+                               type === "!=" :
+                               type === "=" ?
+                               value === check :
+                               type === "*=" ?
+                               value.indexOf(check) >= 0 :
+                               type === "~=" ?
+                               (" " + value + " ").indexOf(check) >= 0 :
+                               !check ?
+                               value && result !== false :
+                               type === "!=" ?
+                               value !== check :
+                               type === "^=" ?
+                               value.indexOf(check) === 0 :
+                               type === "$=" ?
+                               value.substr(value.length - check.length) === check :
+                               type === "|=" ?
+                               value === check || value.substr(0, check.length + 1) === check + "-" :
+                               false;
+               },
+               POS: function(elem, match, i, array){
+                       var name = match[2], filter = Expr.setFilters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+                       }
+               }
+       }
+};
+
+var origPOS = Expr.match.POS,
+       fescape = function(all, num){
+               return "\\" + (num - 0 + 1);
+       };
+
+for ( var type in Expr.match ) {
+       Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+       Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function(array, results) {
+       array = Array.prototype.slice.call( array, 0 );
+
+       if ( results ) {
+               results.push.apply( results, array );
+               return results;
+       }
+       
+       return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+       Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch(e){
+       makeArray = function(array, results) {
+               var ret = results || [], i = 0;
+
+               if ( toString.call(array) === "[object Array]" ) {
+                       Array.prototype.push.apply( ret, array );
+               } else {
+                       if ( typeof array.length === "number" ) {
+                               for ( var l = array.length; i < l; i++ ) {
+                                       ret.push( array[i] );
+                               }
+                       } else {
+                               for ( ; array[i]; i++ ) {
+                                       ret.push( array[i] );
+                               }
+                       }
+               }
+
+               return ret;
+       };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+       sortOrder = function( a, b ) {
+               if ( a === b ) {
+                       hasDuplicate = true;
+                       return 0;
+               }
+
+               if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+                       return a.compareDocumentPosition ? -1 : 1;
+               }
+
+               return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+       };
+} else {
+       sortOrder = function( a, b ) {
+               var ap = [], bp = [], aup = a.parentNode, bup = b.parentNode,
+                       cur = aup, al, bl;
+
+               // The nodes are identical, we can exit early
+               if ( a === b ) {
+                       hasDuplicate = true;
+                       return 0;
+
+               // If the nodes are siblings (or identical) we can do a quick check
+               } else if ( aup === bup ) {
+                       return siblingCheck( a, b );
+
+               // If no parents were found then the nodes are disconnected
+               } else if ( !aup ) {
+                       return -1;
+
+               } else if ( !bup ) {
+                       return 1;
+               }
+
+               // Otherwise they're somewhere else in the tree so we need
+               // to build up a full list of the parentNodes for comparison
+               while ( cur ) {
+                       ap.unshift( cur );
+                       cur = cur.parentNode;
+               }
+
+               cur = bup;
+
+               while ( cur ) {
+                       bp.unshift( cur );
+                       cur = cur.parentNode;
+               }
+
+               al = ap.length;
+               bl = bp.length;
+
+               // Start walking down the tree looking for a discrepancy
+               for ( var i = 0; i < al && i < bl; i++ ) {
+                       if ( ap[i] !== bp[i] ) {
+                               return siblingCheck( ap[i], bp[i] );
+                       }
+               }
+
+               // We ended someplace up the tree so do a sibling check
+               return i === al ?
+                       siblingCheck( a, bp[i], -1 ) :
+                       siblingCheck( ap[i], b, 1 );
+       };
+
+       siblingCheck = function( a, b, ret ) {
+               if ( a === b ) {
+                       return ret;
+               }
+
+               var cur = a.nextSibling;
+
+               while ( cur ) {
+                       if ( cur === b ) {
+                               return -1;
+                       }
+
+                       cur = cur.nextSibling;
+               }
+
+               return 1;
+       };
+}
+
+// Utility function for retreiving the text value of an array of DOM nodes
+Sizzle.getText = function( elems ) {
+       var ret = "", elem;
+
+       for ( var i = 0; elems[i]; i++ ) {
+               elem = elems[i];
+
+               // Get the text from text nodes and CDATA nodes
+               if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+                       ret += elem.nodeValue;
+
+               // Traverse everything else, except comment nodes
+               } else if ( elem.nodeType !== 8 ) {
+                       ret += Sizzle.getText( elem.childNodes );
+               }
+       }
+
+       return ret;
+};
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+       // We're going to inject a fake input element with a specified name
+       var form = document.createElement("div"),
+               id = "script" + (new Date()).getTime();
+       form.innerHTML = "<a name='" + id + "'/>";
+
+       // Inject it into the root element, check its status, and remove it quickly
+       var root = document.documentElement;
+       root.insertBefore( form, root.firstChild );
+
+       // The workaround has to do additional checks after a getElementById
+       // Which slows things down for other browsers (hence the branching)
+       if ( document.getElementById( id ) ) {
+               Expr.find.ID = function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+                               return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+                       }
+               };
+
+               Expr.filter.ID = function(elem, match){
+                       var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+                       return elem.nodeType === 1 && node && node.nodeValue === match;
+               };
+       }
+
+       root.removeChild( form );
+       root = form = null; // release memory in IE
+})();
+
+(function(){
+       // Check to see if the browser returns only elements
+       // when doing getElementsByTagName("*")
+
+       // Create a fake element
+       var div = document.createElement("div");
+       div.appendChild( document.createComment("") );
+
+       // Make sure no comments are found
+       if ( div.getElementsByTagName("*").length > 0 ) {
+               Expr.find.TAG = function(match, context){
+                       var results = context.getElementsByTagName(match[1]);
+
+                       // Filter out possible comments
+                       if ( match[1] === "*" ) {
+                               var tmp = [];
+
+                               for ( var i = 0; results[i]; i++ ) {
+                                       if ( results[i].nodeType === 1 ) {
+                                               tmp.push( results[i] );
+                                       }
+                               }
+
+                               results = tmp;
+                       }
+
+                       return results;
+               };
+       }
+
+       // Check to see if an attribute returns normalized href attributes
+       div.innerHTML = "<a href='#'></a>";
+       if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+                       div.firstChild.getAttribute("href") !== "#" ) {
+               Expr.attrHandle.href = function(elem){
+                       return elem.getAttribute("href", 2);
+               };
+       }
+
+       div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) {
+       (function(){
+               var oldSizzle = Sizzle, div = document.createElement("div");
+               div.innerHTML = "<p class='TEST'></p>";
+
+               // Safari can't handle uppercase or unicode characters when
+               // in quirks mode.
+               if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+                       return;
+               }
+       
+               Sizzle = function(query, context, extra, seed){
+                       context = context || document;
+
+                       // Only use querySelectorAll on non-XML documents
+                       // (ID selectors don't work in non-HTML documents)
+                       if ( !seed && !Sizzle.isXML(context) ) {
+                               if ( context.nodeType === 9 ) {
+                                       try {
+                                               return makeArray( context.querySelectorAll(query), extra );
+                                       } catch(qsaError) {}
+
+                               // qSA works strangely on Element-rooted queries
+                               // We can work around this by specifying an extra ID on the root
+                               // and working up from there (Thanks to Andrew Dupont for the technique)
+                               // IE 8 doesn't work on object elements
+                               } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+                                       var old = context.id, id = context.id = "__sizzle__";
+
+                                       try {
+                                               return makeArray( context.querySelectorAll( "#" + id + " " + query ), extra );
+
+                                       } catch(pseudoError) {
+                                       } finally {
+                                               if ( old ) {
+                                                       context.id = old;
+
+                                               } else {
+                                                       context.removeAttribute( "id" );
+                                               }
+                                       }
+                               }
+                       }
+               
+                       return oldSizzle(query, context, extra, seed);
+               };
+
+               for ( var prop in oldSizzle ) {
+                       Sizzle[ prop ] = oldSizzle[ prop ];
+               }
+
+               div = null; // release memory in IE
+       })();
+}
+
+(function(){
+       var html = document.documentElement,
+               matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector,
+               pseudoWorks = false;
+
+       try {
+               // This should fail with an exception
+               // Gecko does not error, returns false instead
+               matches.call( document.documentElement, ":sizzle" );
+       
+       } catch( pseudoError ) {
+               pseudoWorks = true;
+       }
+
+       if ( matches ) {
+               Sizzle.matchesSelector = function( node, expr ) {
+                               try { 
+                                       if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) ) {
+                                               return matches.call( node, expr );
+                                       }
+                               } catch(e) {}
+
+                               return Sizzle(expr, null, null, [node]).length > 0;
+               };
+       }
+})();
+
+(function(){
+       var div = document.createElement("div");
+
+       div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+       // Opera can't find a second classname (in 9.6)
+       // Also, make sure that getElementsByClassName actually exists
+       if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+               return;
+       }
+
+       // Safari caches class attributes, doesn't catch changes (in 3.2)
+       div.lastChild.className = "e";
+
+       if ( div.getElementsByClassName("e").length === 1 ) {
+               return;
+       }
+       
+       Expr.order.splice(1, 0, "CLASS");
+       Expr.find.CLASS = function(match, context, isXML) {
+               if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+                       return context.getElementsByClassName(match[1]);
+               }
+       };
+
+       div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+               if ( elem ) {
+                       elem = elem[dir];
+                       var match = false;
+
+                       while ( elem ) {
+                               if ( elem.sizcache === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 && !isXML ){
+                                       elem.sizcache = doneName;
+                                       elem.sizset = i;
+                               }
+
+                               if ( elem.nodeName.toLowerCase() === cur ) {
+                                       match = elem;
+                                       break;
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+               if ( elem ) {
+                       elem = elem[dir];
+                       var match = false;
+
+                       while ( elem ) {
+                               if ( elem.sizcache === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 ) {
+                                       if ( !isXML ) {
+                                               elem.sizcache = doneName;
+                                               elem.sizset = i;
+                                       }
+                                       if ( typeof cur !== "string" ) {
+                                               if ( elem === cur ) {
+                                                       match = true;
+                                                       break;
+                                               }
+
+                                       } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+                                               match = elem;
+                                               break;
+                                       }
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+Sizzle.contains = document.documentElement.contains ? function(a, b){
+       return a !== b && (a.contains ? a.contains(b) : true);
+} : function(a, b){
+       return !!(a.compareDocumentPosition(b) & 16);
+};
+
+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 : 0).documentElement;
+       return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function(selector, context){
+       var tmpSet = [], later = "", match,
+               root = context.nodeType ? [context] : context;
+
+       // Position selectors must be done after the filter
+       // And so must :not(positional) so we move all PSEUDOs to the end
+       while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+               later += match[0];
+               selector = selector.replace( Expr.match.PSEUDO, "" );
+       }
+
+       selector = Expr.relative[selector] ? selector + "*" : selector;
+
+       for ( var i = 0, l = root.length; i < l; i++ ) {
+               Sizzle( selector, root[i], tmpSet );
+       }
+
+       return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+       rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+       // Note: This RegExp should be improved, or likely pulled from Sizzle
+       rmultiselector = /,/,
+       isSimple = /^.[^:#\[\.,]*$/,
+       slice = Array.prototype.slice,
+       POS = jQuery.expr.match.POS;
+
+jQuery.fn.extend({
+       find: function( selector ) {
+               var ret = this.pushStack( "", "find", selector ), length = 0;
+
+               for ( var i = 0, l = this.length; i < l; i++ ) {
+                       length = ret.length;
+                       jQuery.find( selector, this[i], ret );
+
+                       if ( i > 0 ) {
+                               // Make sure that the results are unique
+                               for ( var n = length; n < ret.length; n++ ) {
+                                       for ( var r = 0; r < length; r++ ) {
+                                               if ( ret[r] === ret[n] ) {
+                                                       ret.splice(n--, 1);
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               return ret;
+       },
+
+       has: function( target ) {
+               var targets = jQuery( target );
+               return this.filter(function() {
+                       for ( var i = 0, l = targets.length; i < l; i++ ) {
+                               if ( jQuery.contains( this, targets[i] ) ) {
+                                       return true;
+                               }
+                       }
+               });
+       },
+
+       not: function( selector ) {
+               return this.pushStack( winnow(this, selector, false), "not", selector);
+       },
+
+       filter: function( selector ) {
+               return this.pushStack( winnow(this, selector, true), "filter", selector );
+       },
+       
+       is: function( selector ) {
+               return !!selector && jQuery.filter( selector, this ).length > 0;
+       },
+
+       closest: function( selectors, context ) {
+               var ret = [], i, l, cur = this[0];
+
+               if ( jQuery.isArray( selectors ) ) {
+                       var match, matches = {}, selector, level = 1;
+
+                       if ( cur && selectors.length ) {
+                               for ( i = 0, l = selectors.length; i < l; i++ ) {
+                                       selector = selectors[i];
+
+                                       if ( !matches[selector] ) {
+                                               matches[selector] = jQuery.expr.match.POS.test( selector ) ? 
+                                                       jQuery( selector, context || this.context ) :
+                                                       selector;
+                                       }
+                               }
+
+                               while ( cur && cur.ownerDocument && cur !== context ) {
+                                       for ( selector in matches ) {
+                                               match = matches[selector];
+
+                                               if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
+                                                       ret.push({ selector: selector, elem: cur, level: level });
+                                               }
+                                       }
+
+                                       cur = cur.parentNode;
+                                       level++;
+                               }
+                       }
+
+                       return ret;
+               }
+
+               var pos = POS.test( selectors ) ? 
+                       jQuery( selectors, context || this.context ) : null;
+
+               for ( i = 0, l = this.length; i < l; i++ ) {
+                       cur = this[i];
+
+                       while ( cur ) {
+                               if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+                                       ret.push( cur );
+                                       break;
+
+                               } else {
+                                       cur = cur.parentNode;
+                                       if ( !cur || !cur.ownerDocument || cur === context ) {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               ret = ret.length > 1 ? jQuery.unique(ret) : ret;
+               
+               return this.pushStack( ret, "closest", selectors );
+       },
+       
+       // Determine the position of an element within
+       // the matched set of elements
+       index: function( elem ) {
+               if ( !elem || typeof elem === "string" ) {
+                       return jQuery.inArray( this[0],
+                               // If it receives a string, the selector is used
+                               // If it receives nothing, the siblings are used
+                               elem ? jQuery( elem ) : this.parent().children() );
+               }
+               // Locate the position of the desired element
+               return jQuery.inArray(
+                       // If it receives a jQuery object, the first element is used
+                       elem.jquery ? elem[0] : elem, this );
+       },
+
+       add: function( selector, context ) {
+               var set = typeof selector === "string" ?
+                               jQuery( selector, context || this.context ) :
+                               jQuery.makeArray( selector ),
+                       all = jQuery.merge( this.get(), set );
+
+               return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+                       all :
+                       jQuery.unique( all ) );
+       },
+
+       andSelf: function() {
+               return this.add( this.prevObject );
+       }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+       return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+       parent: function( elem ) {
+               var parent = elem.parentNode;
+               return parent && parent.nodeType !== 11 ? parent : null;
+       },
+       parents: function( elem ) {
+               return jQuery.dir( elem, "parentNode" );
+       },
+       parentsUntil: function( elem, i, until ) {
+               return jQuery.dir( elem, "parentNode", until );
+       },
+       next: function( elem ) {
+               return jQuery.nth( elem, 2, "nextSibling" );
+       },
+       prev: function( elem ) {
+               return jQuery.nth( elem, 2, "previousSibling" );
+       },
+       nextAll: function( elem ) {
+               return jQuery.dir( elem, "nextSibling" );
+       },
+       prevAll: function( elem ) {
+               return jQuery.dir( elem, "previousSibling" );
+       },
+       nextUntil: function( elem, i, until ) {
+               return jQuery.dir( elem, "nextSibling", until );
+       },
+       prevUntil: function( elem, i, until ) {
+               return jQuery.dir( elem, "previousSibling", until );
+       },
+       siblings: function( elem ) {
+               return jQuery.sibling( elem.parentNode.firstChild, elem );
+       },
+       children: function( elem ) {
+               return jQuery.sibling( elem.firstChild );
+       },
+       contents: function( elem ) {
+               return jQuery.nodeName( elem, "iframe" ) ?
+                       elem.contentDocument || elem.contentWindow.document :
+                       jQuery.makeArray( elem.childNodes );
+       }
+}, function( name, fn ) {
+       jQuery.fn[ name ] = function( until, selector ) {
+               var ret = jQuery.map( this, fn, until );
+               
+               if ( !runtil.test( name ) ) {
+                       selector = until;
+               }
+
+               if ( selector && typeof selector === "string" ) {
+                       ret = jQuery.filter( selector, ret );
+               }
+
+               ret = this.length > 1 ? jQuery.unique( ret ) : ret;
+
+               if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+                       ret = ret.reverse();
+               }
+
+               return this.pushStack( ret, name, slice.call(arguments).join(",") );
+       };
+});
+
+jQuery.extend({
+       filter: function( expr, elems, not ) {
+               if ( not ) {
+                       expr = ":not(" + expr + ")";
+               }
+
+               return elems.length === 1 ?
+                       jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+                       jQuery.find.matches(expr, elems);
+       },
+       
+       dir: function( elem, dir, until ) {
+               var matched = [], cur = elem[dir];
+               while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+                       if ( cur.nodeType === 1 ) {
+                               matched.push( cur );
+                       }
+                       cur = cur[dir];
+               }
+               return matched;
+       },
+
+       nth: function( cur, result, dir, elem ) {
+               result = result || 1;
+               var num = 0;
+
+               for ( ; cur; cur = cur[dir] ) {
+                       if ( cur.nodeType === 1 && ++num === result ) {
+                               break;
+                       }
+               }
+
+               return cur;
+       },
+
+       sibling: function( n, elem ) {
+               var r = [];
+
+               for ( ; n; n = n.nextSibling ) {
+                       if ( n.nodeType === 1 && n !== elem ) {
+                               r.push( n );
+                       }
+               }
+
+               return r;
+       }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+       if ( jQuery.isFunction( qualifier ) ) {
+               return jQuery.grep(elements, function( elem, i ) {
+                       var retVal = !!qualifier.call( elem, i, elem );
+                       return retVal === keep;
+               });
+
+       } else if ( qualifier.nodeType ) {
+               return jQuery.grep(elements, function( elem, i ) {
+                       return (elem === qualifier) === keep;
+               });
+
+       } else if ( typeof qualifier === "string" ) {
+               var filtered = jQuery.grep(elements, function( elem ) {
+                       return elem.nodeType === 1;
+               });
+
+               if ( isSimple.test( qualifier ) ) {
+                       return jQuery.filter(qualifier, filtered, !keep);
+               } else {
+                       qualifier = jQuery.filter( qualifier, filtered );
+               }
+       }
+
+       return jQuery.grep(elements, function( elem, i ) {
+               return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
+       });
+}
+
+
+
+
+var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+       rleadingWhitespace = /^\s+/,
+       rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+       rtagName = /<([\w:]+)/,
+       rtbody = /<tbody/i,
+       rhtml = /<|&#?\w+;/,
+       rnocache = /<(?:script|object|embed|option|style)/i,
+       rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,  // checked="checked" or checked (html5)
+       raction = /\=([^="'>\s]+\/)>/g,
+       wrapMap = {
+               option: [ 1, "<select multiple='multiple'>", "</select>" ],
+               legend: [ 1, "<fieldset>", "</fieldset>" ],
+               thead: [ 1, "<table>", "</table>" ],
+               tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+               td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+               col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+               area: [ 1, "<map>", "</map>" ],
+               _default: [ 0, "", "" ]
+       };
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+       wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+       text: function( text ) {
+               if ( jQuery.isFunction(text) ) {
+                       return this.each(function(i) {
+                               var self = jQuery(this);
+                               self.text( text.call(this, i, self.text()) );
+                       });
+               }
+
+               if ( typeof text !== "object" && text !== undefined ) {
+                       return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+               }
+
+               return jQuery.text( this );
+       },
+
+       wrapAll: function( html ) {
+               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
+                       var 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.firstChild && elem.firstChild.nodeType === 1 ) {
+                                       elem = elem.firstChild;
+                               }
+
+                               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 ) {
+               return this.each(function() {
+                       jQuery( this ).wrapAll( html );
+               });
+       },
+
+       unwrap: function() {
+               return this.parent().each(function() {
+                       if ( !jQuery.nodeName( this, "body" ) ) {
+                               jQuery( this ).replaceWith( this.childNodes );
+                       }
+               }).end();
+       },
+
+       append: function() {
+               return this.domManip(arguments, true, function( elem ) {
+                       if ( this.nodeType === 1 ) {
+                               this.appendChild( elem );
+                       }
+               });
+       },
+
+       prepend: function() {
+               return this.domManip(arguments, true, function( elem ) {
+                       if ( this.nodeType === 1 ) {
+                               this.insertBefore( elem, this.firstChild );
+                       }
+               });
+       },
+
+       before: function() {
+               if ( this[0] && this[0].parentNode ) {
+                       return this.domManip(arguments, false, function( elem ) {
+                               this.parentNode.insertBefore( elem, this );
+                       });
+               } else if ( arguments.length ) {
+                       var set = jQuery(arguments[0]);
+                       set.push.apply( set, this.toArray() );
+                       return this.pushStack( set, "before", arguments );
+               }
+       },
+
+       after: function() {
+               if ( this[0] && this[0].parentNode ) {
+                       return this.domManip(arguments, false, function( elem ) {
+                               this.parentNode.insertBefore( elem, this.nextSibling );
+                       });
+               } else if ( arguments.length ) {
+                       var set = this.pushStack( this, "after", arguments );
+                       set.push.apply( set, jQuery(arguments[0]).toArray() );
+                       return set;
+               }
+       },
+       
+       // keepData is for internal use only--do not document
+       remove: function( selector, keepData ) {
+               for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+                       if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+                               if ( !keepData && elem.nodeType === 1 ) {
+                                       jQuery.cleanData( elem.getElementsByTagName("*") );
+                                       jQuery.cleanData( [ elem ] );
+                               }
+
+                               if ( elem.parentNode ) {
+                                        elem.parentNode.removeChild( elem );
+                               }
+                       }
+               }
+               
+               return this;
+       },
+
+       empty: function() {
+               for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+                       // Remove element nodes and prevent memory leaks
+                       if ( elem.nodeType === 1 ) {
+                               jQuery.cleanData( elem.getElementsByTagName("*") );
+                       }
+
+                       // Remove any remaining nodes
+                       while ( elem.firstChild ) {
+                               elem.removeChild( elem.firstChild );
+                       }
+               }
+               
+               return this;
+       },
+
+       clone: function( events ) {
+               // Do the clone
+               var ret = this.map(function() {
+                       if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
+                               // IE copies events bound via attachEvent when
+                               // using cloneNode. Calling detachEvent on the
+                               // clone will also remove the events from the orignal
+                               // In order to get around this, we use innerHTML.
+                               // Unfortunately, this means some modifications to
+                               // attributes in IE that are actually only stored
+                               // as properties will not be copied (such as the
+                               // the name attribute on an input).
+                               var html = this.outerHTML, ownerDocument = this.ownerDocument;
+                               if ( !html ) {
+                                       var div = ownerDocument.createElement("div");
+                                       div.appendChild( this.cloneNode(true) );
+                                       html = div.innerHTML;
+                               }
+
+                               return jQuery.clean([html.replace(rinlinejQuery, "")
+                                       // Handle the case in IE 8 where action=/test/> self-closes a tag
+                                       .replace(raction, '="$1">')
+                                       .replace(rleadingWhitespace, "")], ownerDocument)[0];
+                       } else {
+                               return this.cloneNode(true);
+                       }
+               });
+
+               // Copy the events from the original to the clone
+               if ( events === true ) {
+                       cloneCopyEvent( this, ret );
+                       cloneCopyEvent( this.find("*"), ret.find("*") );
+               }
+
+               // Return the cloned set
+               return ret;
+       },
+
+       html: function( value ) {
+               if ( value === undefined ) {
+                       return this[0] && this[0].nodeType === 1 ?
+                               this[0].innerHTML.replace(rinlinejQuery, "") :
+                               null;
+
+               // See if we can take a shortcut and just use innerHTML
+               } else if ( typeof value === "string" && !rnocache.test( value ) &&
+                       (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+                       !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+                       value = value.replace(rxhtmlTag, "<$1></$2>");
+
+                       try {
+                               for ( var i = 0, l = this.length; i < l; i++ ) {
+                                       // Remove element nodes and prevent memory leaks
+                                       if ( this[i].nodeType === 1 ) {
+                                               jQuery.cleanData( this[i].getElementsByTagName("*") );
+                                               this[i].innerHTML = value;
+                                       }
+                               }
+
+                       // If using innerHTML throws an exception, use the fallback method
+                       } catch(e) {
+                               this.empty().append( value );
+                       }
+
+               } else if ( jQuery.isFunction( value ) ) {
+                       this.each(function(i){
+                               var self = jQuery(this);
+                               self.html( value.call(this, i, self.html()) );
+                       });
+
+               } else {
+                       this.empty().append( value );
+               }
+
+               return this;
+       },
+
+       replaceWith: function( value ) {
+               if ( this[0] && this[0].parentNode ) {
+                       // Make sure that the elements are removed from the DOM before they are inserted
+                       // this can help fix replacing a parent with child elements
+                       if ( jQuery.isFunction( value ) ) {
+                               return this.each(function(i) {
+                                       var self = jQuery(this), old = self.html();
+                                       self.replaceWith( value.call( this, i, old ) );
+                               });
+                       }
+
+                       if ( typeof value !== "string" ) {
+                               value = jQuery(value).detach();
+                       }
+
+                       return this.each(function() {
+                               var next = this.nextSibling, parent = this.parentNode;
+
+                               jQuery(this).remove();
+
+                               if ( next ) {
+                                       jQuery(next).before( value );
+                               } else {
+                                       jQuery(parent).append( value );
+                               }
+                       });
+               } else {
+                       return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
+               }
+       },
+
+       detach: function( selector ) {
+               return this.remove( selector, true );
+       },
+
+       domManip: function( args, table, callback ) {
+               var results, first, value = args[0], scripts = [], fragment, parent;
+
+               // We can't cloneNode fragments that contain checked, in WebKit
+               if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+                       return this.each(function() {
+                               jQuery(this).domManip( args, table, callback, true );
+                       });
+               }
+
+               if ( jQuery.isFunction(value) ) {
+                       return this.each(function(i) {
+                               var self = jQuery(this);
+                               args[0] = value.call(this, i, table ? self.html() : undefined);
+                               self.domManip( args, table, callback );
+                       });
+               }
+
+               if ( this[0] ) {
+                       parent = value && value.parentNode;
+
+                       // If we're in a fragment, just use that instead of building a new one
+                       if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+                               results = { fragment: parent };
+
+                       } else {
+                               results = jQuery.buildFragment( args, this, scripts );
+                       }
+                       
+                       fragment = results.fragment;
+                       
+                       if ( fragment.childNodes.length === 1 ) {
+                               first = fragment = fragment.firstChild;
+                       } else {
+                               first = fragment.firstChild;
+                       }
+
+                       if ( first ) {
+                               table = table && jQuery.nodeName( first, "tr" );
+
+                               for ( var i = 0, l = this.length; i < l; i++ ) {
+                                       callback.call(
+                                               table ?
+                                                       root(this[i], first) :
+                                                       this[i],
+                                               i > 0 || results.cacheable || this.length > 1  ?
+                                                       fragment.cloneNode(true) :
+                                                       fragment
+                                       );
+                               }
+                       }
+
+                       if ( scripts.length ) {
+                               jQuery.each( scripts, evalScript );
+                       }
+               }
+
+               return this;
+       }
+});
+
+function root( elem, cur ) {
+       return jQuery.nodeName(elem, "table") ?
+               (elem.getElementsByTagName("tbody")[0] ||
+               elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+               elem;
+}
+
+function cloneCopyEvent(orig, ret) {
+       var i = 0;
+
+       ret.each(function() {
+               if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) {
+                       return;
+               }
+
+               var oldData = jQuery.data( orig[i++] ), curData = jQuery.data( this, oldData ), events = oldData && oldData.events;
+
+               if ( events ) {
+                       delete curData.handle;
+                       curData.events = {};
+
+                       for ( var type in events ) {
+                               for ( var handler in events[ type ] ) {
+                                       jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
+                               }
+                       }
+               }
+       });
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+       var fragment, cacheable, cacheresults,
+               doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
+
+       // Only cache "small" (1/2 KB) strings that are associated with the main document
+       // Cloning options loses the selected state, so don't cache them
+       // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+       // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+       if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
+               !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+               cacheable = true;
+               cacheresults = jQuery.fragments[ args[0] ];
+               if ( cacheresults ) {
+                       if ( cacheresults !== 1 ) {
+                               fragment = cacheresults;
+                       }
+               }
+       }
+
+       if ( !fragment ) {
+               fragment = doc.createDocumentFragment();
+               jQuery.clean( args, doc, fragment, scripts );
+       }
+
+       if ( cacheable ) {
+               jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
+       }
+
+       return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+       appendTo: "append",
+       prependTo: "prepend",
+       insertBefore: "before",
+       insertAfter: "after",
+       replaceAll: "replaceWith"
+}, function( name, original ) {
+       jQuery.fn[ name ] = function( selector ) {
+               var ret = [], insert = jQuery( selector ),
+                       parent = this.length === 1 && this[0].parentNode;
+               
+               if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+                       insert[ original ]( this[0] );
+                       return this;
+                       
+               } else {
+                       for ( var i = 0, l = insert.length; i < l; i++ ) {
+                               var elems = (i > 0 ? this.clone(true) : this).get();
+                               jQuery( insert[i] )[ original ]( elems );
+                               ret = ret.concat( elems );
+                       }
+               
+                       return this.pushStack( ret, name, insert.selector );
+               }
+       };
+});
+
+jQuery.extend({
+       clean: function( elems, context, fragment, scripts ) {
+               context = context || document;
+
+               // !context.createElement fails in IE with an error but returns typeof 'object'
+               if ( typeof context.createElement === "undefined" ) {
+                       context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+               }
+
+               var ret = [];
+
+               for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+                       if ( typeof elem === "number" ) {
+                               elem += "";
+                       }
+
+                       if ( !elem ) {
+                               continue;
+                       }
+
+                       // Convert html string into DOM nodes
+                       if ( typeof elem === "string" && !rhtml.test( elem ) ) {
+                               elem = context.createTextNode( elem );
+
+                       } else if ( typeof elem === "string" ) {
+                               // Fix "XHTML"-style tags in all browsers
+                               elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+                               // Trim whitespace, otherwise indexOf won't work as expected
+                               var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+                                       wrap = wrapMap[ tag ] || wrapMap._default,
+                                       depth = wrap[0],
+                                       div = context.createElement("div");
+
+                               // Go to html and back, then peel off extra wrappers
+                               div.innerHTML = wrap[1] + elem + wrap[2];
+
+                               // Move to the right depth
+                               while ( depth-- ) {
+                                       div = div.lastChild;
+                               }
+
+                               // Remove IE's autoinserted <tbody> from table fragments
+                               if ( !jQuery.support.tbody ) {
+
+                                       // String was a <table>, *may* have spurious <tbody>
+                                       var hasBody = rtbody.test(elem),
+                                               tbody = tag === "table" && !hasBody ?
+                                                       div.firstChild && div.firstChild.childNodes :
+
+                                                       // String was a bare <thead> or <tfoot>
+                                                       wrap[1] === "<table>" && !hasBody ?
+                                                               div.childNodes :
+                                                               [];
+
+                                       for ( var j = tbody.length - 1; j >= 0 ; --j ) {
+                                               if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+                                                       tbody[ j ].parentNode.removeChild( tbody[ j ] );
+                                               }
+                                       }
+
+                               }
+
+                               // IE completely kills leading whitespace when innerHTML is used
+                               if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+                                       div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+                               }
+
+                               elem = div.childNodes;
+                       }
+
+                       if ( elem.nodeType ) {
+                               ret.push( elem );
+                       } else {
+                               ret = jQuery.merge( ret, elem );
+                       }
+               }
+
+               if ( fragment ) {
+                       for ( i = 0; ret[i]; i++ ) {
+                               if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+                                       scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+                               
+                               } else {
+                                       if ( ret[i].nodeType === 1 ) {
+                                               ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+                                       }
+                                       fragment.appendChild( ret[i] );
+                               }
+                       }
+               }
+
+               return ret;
+       },
+       
+       cleanData: function( elems ) {
+               var data, id, cache = jQuery.cache,
+                       special = jQuery.event.special,
+                       deleteExpando = jQuery.support.deleteExpando;
+               
+               for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+                       if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+                               continue;
+                       }
+
+                       id = elem[ jQuery.expando ];
+                       
+                       if ( id ) {
+                               data = cache[ id ];
+                               
+                               if ( data && data.events ) {
+                                       for ( var type in data.events ) {
+                                               if ( special[ type ] ) {
+                                                       jQuery.event.remove( elem, type );
+
+                                               } else {
+                                                       jQuery.removeEvent( elem, type, data.handle );
+                                               }
+                                       }
+                               }
+                               
+                               if ( deleteExpando ) {
+                                       delete elem[ jQuery.expando ];
+
+                               } else if ( elem.removeAttribute ) {
+                                       elem.removeAttribute( jQuery.expando );
+                               }
+                               
+                               delete cache[ id ];
+                       }
+               }
+       }
+});
+
+function evalScript( i, elem ) {
+       if ( elem.src ) {
+               jQuery.ajax({
+                       url: elem.src,
+                       async: false,
+                       dataType: "script"
+               });
+       } else {
+               jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+       }
+
+       if ( elem.parentNode ) {
+               elem.parentNode.removeChild( elem );
+       }
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+       ropacity = /opacity=([^)]*)/,
+       rdashAlpha = /-([a-z])/ig,
+       rupper = /([A-Z])/g,
+       rnumpx = /^-?\d+(?:px)?$/i,
+       rnum = /^-?\d/,
+
+       cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+       cssWidth = [ "Left", "Right" ],
+       cssHeight = [ "Top", "Bottom" ],
+       curCSS,
+
+       // cache check for defaultView.getComputedStyle
+       getComputedStyle = document.defaultView && document.defaultView.getComputedStyle,
+
+       fcamelCase = function( all, letter ) {
+               return letter.toUpperCase();
+       };
+
+jQuery.fn.css = function( name, value ) {
+       // Setting 'undefined' is a no-op
+       if ( arguments.length === 2 && value === undefined ) {
+               return this;
+       }
+
+       return jQuery.access( this, name, value, true, function( elem, name, value ) {
+               return value !== undefined ?
+                       jQuery.style( elem, name, value ) :
+                       jQuery.css( elem, name );
+       });
+};
+
+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", "opacity" );
+                                       return ret === "" ? "1" : ret;
+
+                               } else {
+                                       return elem.style.opacity;
+                               }
+                       }
+               }
+       },
+
+       // Exclude the following css properties to add px
+       cssNumber: {
+               "zIndex": true,
+               "fontWeight": true,
+               "opacity": true,
+               "zoom": true,
+               "lineHeight": true
+       },
+
+       // Add in properties whose names you wish to fix before
+       // setting or getting the value
+       cssProps: {
+               // normalize float css property
+               "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+       },
+
+       // 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, origName = jQuery.camelCase( name ),
+                       style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+               name = jQuery.cssProps[ origName ] || origName;
+
+               // Check if we're setting a value
+               if ( value !== undefined ) {
+                       // Make sure that NaN and null values aren't set. See: #7116
+                       if ( typeof value === "number" && isNaN( value ) || value == null ) {
+                               return;
+                       }
+
+                       // If a number was passed in, add 'px' to the (except for certain CSS properties)
+                       if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) {
+                               value += "px";
+                       }
+
+                       // If a hook was provided, use that value, otherwise just set the specified value
+                       if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+                               // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+                               // Fixes bug #5509
+                               try {
+                                       style[ name ] = value;
+                               } catch(e) {}
+                       }
+
+               } 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 ) {
+               // Make sure that we're working with the right name
+               var ret, origName = jQuery.camelCase( name ),
+                       hooks = jQuery.cssHooks[ origName ];
+
+               name = jQuery.cssProps[ origName ] || origName;
+
+               // If a hook was provided get the computed value from there
+               if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+                       return ret;
+
+               // Otherwise, if a way to get the computed value exists, use that
+               } else if ( curCSS ) {
+                       return curCSS( elem, name, origName );
+               }
+       },
+
+       // A method for quickly swapping in/out CSS properties to get correct calculations
+       swap: function( elem, options, callback ) {
+               var old = {};
+
+               // Remember the old values, and insert the new ones
+               for ( var name in options ) {
+                       old[ name ] = elem.style[ name ];
+                       elem.style[ name ] = options[ name ];
+               }
+
+               callback.call( elem );
+
+               // Revert the old values
+               for ( name in options ) {
+                       elem.style[ name ] = old[ name ];
+               }
+       },
+
+       camelCase: function( string ) {
+               return string.replace( rdashAlpha, fcamelCase );
+       }
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+       jQuery.cssHooks[ name ] = {
+               get: function( elem, computed, extra ) {
+                       var val;
+
+                       if ( computed ) {
+                               if ( elem.offsetWidth !== 0 ) {
+                                       val = getWH( elem, name, extra );
+
+                               } else {
+                                       jQuery.swap( elem, cssShow, function() {
+                                               val = getWH( elem, name, extra );
+                                       });
+                               }
+
+                               return val + "px";
+                       }
+               },
+
+               set: function( elem, value ) {
+                       if ( rnumpx.test( value ) ) {
+                               // ignore negative width and height values #1599
+                               value = parseFloat(value);
+
+                               if ( value >= 0 ) {
+                                       return value + "px";
+                               }
+
+                       } else {
+                               return value;
+                       }
+               }
+       };
+});
+
+if ( !jQuery.support.opacity ) {
+       jQuery.cssHooks.opacity = {
+               get: function( elem, computed ) {
+                       // IE uses filters for opacity
+                       return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ?
+                               (parseFloat(RegExp.$1) / 100) + "" :
+                               computed ? "1" : "";
+               },
+
+               set: function( elem, value ) {
+                       var style = elem.style;
+
+                       // IE has trouble with opacity if it does not have layout
+                       // Force it by setting the zoom level
+                       style.zoom = 1;
+
+                       // Set the alpha filter to set the opacity
+                       var opacity = jQuery.isNaN(value) ?
+                               "" :
+                               "alpha(opacity=" + value * 100 + ")",
+                               filter = style.filter || "";
+
+                       style.filter = ralpha.test(filter) ?
+                               filter.replace(ralpha, opacity) :
+                               style.filter + ' ' + opacity;
+               }
+       };
+}
+
+if ( getComputedStyle ) {
+       curCSS = function( elem, newName, name ) {
+               var ret, defaultView, computedStyle;
+
+               name = name.replace( rupper, "-$1" ).toLowerCase();
+
+               if ( !(defaultView = elem.ownerDocument.defaultView) ) {
+                       return undefined;
+               }
+
+               if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+                       ret = computedStyle.getPropertyValue( name );
+                       if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+                               ret = jQuery.style( elem, name );
+                       }
+               }
+
+               return ret;
+       };
+
+} else if ( document.documentElement.currentStyle ) {
+       curCSS = function( elem, name ) {
+               var left, rsLeft, ret = elem.currentStyle && elem.currentStyle[ name ], style = elem.style;
+
+               // From the awesome hack by Dean Edwards
+               // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+               // If we're not dealing with a regular pixel number
+               // but a number that has a weird ending, we need to convert it to pixels
+               if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+                       // Remember the original values
+                       left = style.left;
+                       rsLeft = elem.runtimeStyle.left;
+
+                       // Put in the new values to get a computed value out
+                       elem.runtimeStyle.left = elem.currentStyle.left;
+                       style.left = name === "fontSize" ? "1em" : (ret || 0);
+                       ret = style.pixelLeft + "px";
+
+                       // Revert the changed values
+                       style.left = left;
+                       elem.runtimeStyle.left = rsLeft;
+               }
+
+               return ret;
+       };
+}
+
+function getWH( elem, name, extra ) {
+       var which = name === "width" ? cssWidth : cssHeight,
+               val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
+
+       if ( extra === "border" ) {
+               return val;
+       }
+
+       jQuery.each( which, function() {
+               if ( !extra ) {
+                       val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0;
+               }
+
+               if ( extra === "margin" ) {
+                       val += parseFloat(jQuery.css( elem, "margin" + this )) || 0;
+
+               } else {
+                       val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0;
+               }
+       });
+
+       return val;
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+       jQuery.expr.filters.hidden = function( elem ) {
+               var width = elem.offsetWidth, height = elem.offsetHeight;
+
+               return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
+       };
+
+       jQuery.expr.filters.visible = function( elem ) {
+               return !jQuery.expr.filters.hidden( elem );
+       };
+}
+
+
+
+
+var jsc = jQuery.now(),
+       rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+       rselectTextarea = /^(?:select|textarea)/i,
+       rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+       rnoContent = /^(?:GET|HEAD|DELETE)$/,
+       rbracket = /\[\]$/,
+       jsre = /\=\?(&|$)/,
+       rquery = /\?/,
+       rts = /([?&])_=[^&]*/,
+       rurl = /^(\w+:)?\/\/([^\/?#]+)/,
+       r20 = /%20/g,
+       rhash = /#.*$/,
+
+       // Keep a copy of the old load method
+       _load = jQuery.fn.load;
+
+jQuery.fn.extend({
+       load: function( url, params, callback ) {
+               if ( typeof url !== "string" && _load ) {
+                       return _load.apply( this, arguments );
+
+               // Don't do a request if no elements are being requested
+               } else if ( !this.length ) {
+                       return this;
+               }
+
+               var off = url.indexOf(" ");
+               if ( off >= 0 ) {
+                       var selector = url.slice(off, url.length);
+                       url = url.slice(0, off);
+               }
+
+               // Default to a GET request
+               var type = "GET";
+
+               // If the second parameter was provided
+               if ( params ) {
+                       // If it's a function
+                       if ( jQuery.isFunction( params ) ) {
+                               // We assume that it's the callback
+                               callback = params;
+                               params = null;
+
+                       // Otherwise, build a param string
+                       } else if ( typeof params === "object" ) {
+                               params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+                               type = "POST";
+                       }
+               }
+
+               var self = this;
+
+               // Request the remote document
+               jQuery.ajax({
+                       url: url,
+                       type: type,
+                       dataType: "html",
+                       data: params,
+                       complete: function( res, status ) {
+                               // If successful, inject the HTML into all the matched elements
+                               if ( status === "success" || status === "notmodified" ) {
+                                       // See if a selector was specified
+                                       self.html( selector ?
+                                               // Create a dummy div to hold the results
+                                               jQuery("<div>")
+                                                       // inject the contents of the document in, removing the scripts
+                                                       // to avoid any 'Permission Denied' errors in IE
+                                                       .append(res.responseText.replace(rscript, ""))
+
+                                                       // Locate the specified elements
+                                                       .find(selector) :
+
+                                               // If not, just inject the full result
+                                               res.responseText );
+                               }
+
+                               if ( callback ) {
+                                       self.each( callback, [res.responseText, status, res] );
+                               }
+                       }
+               });
+
+               return this;
+       },
+
+       serialize: function() {
+               return jQuery.param(this.serializeArray());
+       },
+
+       serializeArray: function() {
+               return this.map(function() {
+                       return this.elements ? jQuery.makeArray(this.elements) : this;
+               })
+               .filter(function() {
+                       return this.name && !this.disabled &&
+                               (this.checked || rselectTextarea.test(this.nodeName) ||
+                                       rinput.test(this.type));
+               })
+               .map(function( i, elem ) {
+                       var val = jQuery(this).val();
+
+                       return val == null ?
+                               null :
+                               jQuery.isArray(val) ?
+                                       jQuery.map( val, function( val, i ) {
+                                               return { name: elem.name, value: val };
+                                       }) :
+                                       { name: elem.name, value: val };
+               }).get();
+       }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) {
+       jQuery.fn[o] = function( f ) {
+               return this.bind(o, f);
+       };
+});
+
+jQuery.extend({
+       get: function( url, data, callback, type ) {
+               // shift arguments if data argument was omited
+               if ( jQuery.isFunction( data ) ) {
+                       type = type || callback;
+                       callback = data;
+                       data = null;
+               }
+
+               return jQuery.ajax({
+                       type: "GET",
+                       url: url,
+                       data: data,
+                       success: callback,
+                       dataType: type
+               });
+       },
+
+       getScript: function( url, callback ) {
+               return jQuery.get(url, null, callback, "script");
+       },
+
+       getJSON: function( url, data, callback ) {
+               return jQuery.get(url, data, callback, "json");
+       },
+
+       post: function( url, data, callback, type ) {
+               // shift arguments if data argument was omited
+               if ( jQuery.isFunction( data ) ) {
+                       type = type || callback;
+                       callback = data;
+                       data = {};
+               }
+
+               return jQuery.ajax({
+                       type: "POST",
+                       url: url,
+                       data: data,
+                       success: callback,
+                       dataType: type
+               });
+       },
+
+       ajaxSetup: function( settings ) {
+               jQuery.extend( jQuery.ajaxSettings, settings );
+       },
+
+       ajaxSettings: {
+               url: location.href,
+               global: true,
+               type: "GET",
+               contentType: "application/x-www-form-urlencoded",
+               processData: true,
+               async: true,
+               /*
+               timeout: 0,
+               data: null,
+               username: null,
+               password: null,
+               traditional: false,
+               */
+               // This function can be overriden by calling jQuery.ajaxSetup
+               xhr: function() {
+                       return new window.XMLHttpRequest();
+               },
+               accepts: {
+                       xml: "application/xml, text/xml",
+                       html: "text/html",
+                       script: "text/javascript, application/javascript",
+                       json: "application/json, text/javascript",
+                       text: "text/plain",
+                       _default: "*/*"
+               }
+       },
+
+       ajax: function( origSettings ) {
+               var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
+                       jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type);
+
+               s.url = s.url.replace( rhash, "" );
+
+               // Use original (not extended) context object if it was provided
+               s.context = origSettings && origSettings.context != null ? origSettings.context : s;
+
+               // convert data if not already a string
+               if ( s.data && s.processData && typeof s.data !== "string" ) {
+                       s.data = jQuery.param( s.data, s.traditional );
+               }
+
+               // Handle JSONP Parameter Callbacks
+               if ( s.dataType === "jsonp" ) {
+                       if ( type === "GET" ) {
+                               if ( !jsre.test( s.url ) ) {
+                                       s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+                               }
+                       } else if ( !s.data || !jsre.test(s.data) ) {
+                               s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+                       }
+                       s.dataType = "json";
+               }
+
+               // Build temporary JSONP function
+               if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
+                       jsonp = s.jsonpCallback || ("jsonp" + jsc++);
+
+                       // Replace the =? sequence both in the query string and the data
+                       if ( s.data ) {
+                               s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+                       }
+
+                       s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+                       // We need to make sure
+                       // that a JSONP style response is executed properly
+                       s.dataType = "script";
+
+                       // Handle JSONP-style loading
+                       var customJsonp = window[ jsonp ];
+
+                       window[ jsonp ] = function( tmp ) {
+                               data = tmp;
+                               jQuery.handleSuccess( s, xhr, status, data );
+                               jQuery.handleComplete( s, xhr, status, data );
+
+                               if ( jQuery.isFunction( customJsonp ) ) {
+                                       customJsonp( tmp );
+
+                               } else {
+                                       // Garbage collect
+                                       window[ jsonp ] = undefined;
+
+                                       try {
+                                               delete window[ jsonp ];
+                                       } catch( jsonpError ) {}
+                               }
+                               
+                               if ( head ) {
+                                       head.removeChild( script );
+                               }
+                       };
+               }
+
+               if ( s.dataType === "script" && s.cache === null ) {
+                       s.cache = false;
+               }
+
+               if ( s.cache === false && type === "GET" ) {
+                       var ts = jQuery.now();
+
+                       // try replacing _= if it is there
+                       var ret = s.url.replace(rts, "$1_=" + ts);
+
+                       // if nothing was replaced, add timestamp to the end
+                       s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
+               }
+
+               // If data is available, append data to url for get requests
+               if ( s.data && type === "GET" ) {
+                       s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
+               }
+
+               // Watch for a new set of requests
+               if ( s.global && jQuery.active++ === 0 ) {
+                       jQuery.event.trigger( "ajaxStart" );
+               }
+
+               // Matches an absolute URL, and saves the domain
+               var parts = rurl.exec( s.url ),
+                       remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
+
+               // If we're requesting a remote document
+               // and trying to load JSON or Script with a GET
+               if ( s.dataType === "script" && type === "GET" && remote ) {
+                       var head = document.getElementsByTagName("head")[0] || document.documentElement;
+                       var script = document.createElement("script");
+                       if ( s.scriptCharset ) {
+                               script.charset = s.scriptCharset;
+                       }
+                       script.src = s.url;
+
+                       // Handle Script loading
+                       if ( !jsonp ) {
+                               var done = false;
+
+                               // Attach handlers for all browsers
+                               script.onload = script.onreadystatechange = function() {
+                                       if ( !done && (!this.readyState ||
+                                                       this.readyState === "loaded" || this.readyState === "complete") ) {
+                                               done = true;
+                                               jQuery.handleSuccess( s, xhr, status, data );
+                                               jQuery.handleComplete( s, xhr, status, data );
+
+                                               // Handle memory leak in IE
+                                               script.onload = script.onreadystatechange = null;
+                                               if ( head && script.parentNode ) {
+                                                       head.removeChild( script );
+                                               }
+                                       }
+                               };
+                       }
+
+                       // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+                       // This arises when a base node is used (#2709 and #4378).
+                       head.insertBefore( script, head.firstChild );
+
+                       // We handle everything using the script element injection
+                       return undefined;
+               }
+
+               var requestDone = false;
+
+               // Create the request object
+               var xhr = s.xhr();
+
+               if ( !xhr ) {
+                       return;
+               }
+
+               // Open the socket
+               // Passing null username, generates a login popup on Opera (#2865)
+               if ( s.username ) {
+                       xhr.open(type, s.url, s.async, s.username, s.password);
+               } else {
+                       xhr.open(type, s.url, s.async);
+               }
+
+               // Need an extra try/catch for cross domain requests in Firefox 3
+               try {
+                       // Set content-type if data specified and content-body is valid for this type
+                       if ( (s.data != null && !noContent) || (origSettings && origSettings.contentType) ) {
+                               xhr.setRequestHeader("Content-Type", s.contentType);
+                       }
+
+                       // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+                       if ( s.ifModified ) {
+                               if ( jQuery.lastModified[s.url] ) {
+                                       xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]);
+                               }
+
+                               if ( jQuery.etag[s.url] ) {
+                                       xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]);
+                               }
+                       }
+
+                       // Set header so the called script knows that it's an XMLHttpRequest
+                       // Only send the header if it's not a remote XHR
+                       if ( !remote ) {
+                               xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+                       }
+
+                       // Set the Accepts header for the server, depending on the dataType
+                       xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+                               s.accepts[ s.dataType ] + ", */*; q=0.01" :
+                               s.accepts._default );
+               } catch( headerError ) {}
+
+               // Allow custom headers/mimetypes and early abort
+               if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) {
+                       // Handle the global AJAX counter
+                       if ( s.global && jQuery.active-- === 1 ) {
+                               jQuery.event.trigger( "ajaxStop" );
+                       }
+
+                       // close opended socket
+                       xhr.abort();
+                       return false;
+               }
+
+               if ( s.global ) {
+                       jQuery.triggerGlobal( s, "ajaxSend", [xhr, s] );
+               }
+
+               // Wait for a response to come back
+               var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) {
+                       // The request was aborted
+                       if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) {
+                               // Opera doesn't call onreadystatechange before this point
+                               // so we simulate the call
+                               if ( !requestDone ) {
+                                       jQuery.handleComplete( s, xhr, status, data );
+                               }
+
+                               requestDone = true;
+                               if ( xhr ) {
+                                       xhr.onreadystatechange = jQuery.noop;
+                               }
+
+                       // The transfer is complete and the data is available, or the request timed out
+                       } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) {
+                               requestDone = true;
+                               xhr.onreadystatechange = jQuery.noop;
+
+                               status = isTimeout === "timeout" ?
+                                       "timeout" :
+                                       !jQuery.httpSuccess( xhr ) ?
+                                               "error" :
+                                               s.ifModified && jQuery.httpNotModified( xhr, s.url ) ?
+                                                       "notmodified" :
+                                                       "success";
+
+                               var errMsg;
+
+                               if ( status === "success" ) {
+                                       // Watch for, and catch, XML document parse errors
+                                       try {
+                                               // process the data (runs the xml through httpData regardless of callback)
+                                               data = jQuery.httpData( xhr, s.dataType, s );
+                                       } catch( parserError ) {
+                                               status = "parsererror";
+                                               errMsg = parserError;
+                                       }
+                               }
+
+                               // Make sure that the request was successful or notmodified
+                               if ( status === "success" || status === "notmodified" ) {
+                                       // JSONP handles its own success callback
+                                       if ( !jsonp ) {
+                                               jQuery.handleSuccess( s, xhr, status, data );
+                                       }
+                               } else {
+                                       jQuery.handleError( s, xhr, status, errMsg );
+                               }
+
+                               // Fire the complete handlers
+                               if ( !jsonp ) {
+                                       jQuery.handleComplete( s, xhr, status, data );
+                               }
+
+                               if ( isTimeout === "timeout" ) {
+                                       xhr.abort();
+                               }
+
+                               // Stop memory leaks
+                               if ( s.async ) {
+                                       xhr = null;
+                               }
+                       }
+               };
+
+               // Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK)
+               // Opera doesn't fire onreadystatechange at all on abort
+               try {
+                       var oldAbort = xhr.abort;
+                       xhr.abort = function() {
+                               // xhr.abort in IE7 is not a native JS function
+                               // and does not have a call property
+                               if ( xhr && oldAbort.call ) {
+                                       oldAbort.call( xhr );
+                               }
+
+                               onreadystatechange( "abort" );
+                       };
+               } catch( abortError ) {}
+
+               // Timeout checker
+               if ( s.async && s.timeout > 0 ) {
+                       setTimeout(function() {
+                               // Check to see if the request is still happening
+                               if ( xhr && !requestDone ) {
+                                       onreadystatechange( "timeout" );
+                               }
+                       }, s.timeout);
+               }
+
+               // Send the data
+               try {
+                       xhr.send( noContent || s.data == null ? null : s.data );
+
+               } catch( sendError ) {
+                       jQuery.handleError( s, xhr, null, sendError );
+
+                       // Fire the complete handlers
+                       jQuery.handleComplete( s, xhr, status, data );
+               }
+
+               // firefox 1.5 doesn't fire statechange for sync requests
+               if ( !s.async ) {
+                       onreadystatechange();
+               }
+
+               // return XMLHttpRequest to allow aborting the request etc.
+               return xhr;
+       },
+
+       // Serialize an array of form elements or a set of
+       // key/values into a query string
+       param: function( a, traditional ) {
+               var s = [], add = function( key, value ) {
+                       // If value is a function, invoke it and return its value
+                       value = jQuery.isFunction(value) ? value() : value;
+                       s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
+               };
+               
+               // Set traditional to true for jQuery <= 1.3.2 behavior.
+               if ( traditional === undefined ) {
+                       traditional = jQuery.ajaxSettings.traditional;
+               }
+               
+               // If an array was passed in, assume that it is an array of form elements.
+               if ( jQuery.isArray(a) || a.jquery ) {
+                       // 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 ( var prefix in a ) {
+                               buildParams( prefix, a[prefix], traditional, add );
+                       }
+               }
+
+               // Return the resulting serialization
+               return s.join("&").replace(r20, "+");
+       }
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+       if ( jQuery.isArray(obj) && obj.length ) {
+               // 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 {
+                               // If array item is non-scalar (array or object), encode its
+                               // numeric index to resolve deserialization ambiguity issues.
+                               // Note that rack (as of 1.0.0) can't currently deserialize
+                               // nested arrays properly, and attempting to do so may cause
+                               // a server error. Possible fixes are to modify rack's
+                               // deserialization algorithm or to provide an option or flag
+                               // to force array serialization to be shallow.
+                               buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+                       }
+               });
+                       
+       } else if ( !traditional && obj != null && typeof obj === "object" ) {
+               if ( jQuery.isEmptyObject( obj ) ) {
+                       add( prefix, "" );
+
+               // Serialize object item.
+               } else {
+                       jQuery.each( obj, function( k, v ) {
+                               buildParams( prefix + "[" + k + "]", v, traditional, add );
+                       });
+               }
+                                       
+       } else {
+               // Serialize scalar item.
+               add( prefix, obj );
+       }
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+       // Counter for holding the number of active queries
+       active: 0,
+
+       // Last-Modified header cache for next request
+       lastModified: {},
+       etag: {},
+
+       handleError: function( s, xhr, status, e ) {
+               // If a local callback was specified, fire it
+               if ( s.error ) {
+                       s.error.call( s.context, xhr, status, e );
+               }
+
+               // Fire the global callback
+               if ( s.global ) {
+                       jQuery.triggerGlobal( s, "ajaxError", [xhr, s, e] );
+               }
+       },
+
+       handleSuccess: function( s, xhr, status, data ) {
+               // If a local callback was specified, fire it and pass it the data
+               if ( s.success ) {
+                       s.success.call( s.context, data, status, xhr );
+               }
+
+               // Fire the global callback
+               if ( s.global ) {
+                       jQuery.triggerGlobal( s, "ajaxSuccess", [xhr, s] );
+               }
+       },
+
+       handleComplete: function( s, xhr, status ) {
+               // Process result
+               if ( s.complete ) {
+                       s.complete.call( s.context, xhr, status );
+               }
+
+               // The request was completed
+               if ( s.global ) {
+                       jQuery.triggerGlobal( s, "ajaxComplete", [xhr, s] );
+               }
+
+               // Handle the global AJAX counter
+               if ( s.global && jQuery.active-- === 1 ) {
+                       jQuery.event.trigger( "ajaxStop" );
+               }
+       },
+               
+       triggerGlobal: function( s, type, args ) {
+               (s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args);
+       },
+
+       // Determines if an XMLHttpRequest was successful or not
+       httpSuccess: function( xhr ) {
+               try {
+                       // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+                       return !xhr.status && location.protocol === "file:" ||
+                               xhr.status >= 200 && xhr.status < 300 ||
+                               xhr.status === 304 || xhr.status === 1223;
+               } catch(e) {}
+
+               return false;
+       },
+
+       // Determines if an XMLHttpRequest returns NotModified
+       httpNotModified: function( xhr, url ) {
+               var lastModified = xhr.getResponseHeader("Last-Modified"),
+                       etag = xhr.getResponseHeader("Etag");
+
+               if ( lastModified ) {
+                       jQuery.lastModified[url] = lastModified;
+               }
+
+               if ( etag ) {
+                       jQuery.etag[url] = etag;
+               }
+
+               return xhr.status === 304;
+       },
+
+       httpData: function( xhr, type, s ) {
+               var ct = xhr.getResponseHeader("content-type") || "",
+                       xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
+                       data = xml ? xhr.responseXML : xhr.responseText;
+
+               if ( xml && data.documentElement.nodeName === "parsererror" ) {
+                       jQuery.error( "parsererror" );
+               }
+
+               // Allow a pre-filtering function to sanitize the response
+               // s is checked to keep backwards compatibility
+               if ( s && s.dataFilter ) {
+                       data = s.dataFilter( data, type );
+               }
+
+               // The filter can actually parse the response
+               if ( typeof data === "string" ) {
+                       // Get the JavaScript object, if JSON is used.
+                       if ( type === "json" || !type && ct.indexOf("json") >= 0 ) {
+                               data = jQuery.parseJSON( data );
+
+                       // If the type is "script", eval it in global context
+                       } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) {
+                               jQuery.globalEval( data );
+                       }
+               }
+
+               return data;
+       }
+
+});
+
+/*
+ * Create the request object; Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+if ( window.ActiveXObject ) {
+       jQuery.ajaxSettings.xhr = function() {
+               if ( window.location.protocol !== "file:" ) {
+                       try {
+                               return new window.XMLHttpRequest();
+                       } catch(xhrError) {}
+               }
+
+               try {
+                       return new window.ActiveXObject("Microsoft.XMLHTTP");
+               } catch(activeError) {}
+       };
+}
+
+// Does this browser support XHR requests?
+jQuery.support.ajax = !!jQuery.ajaxSettings.xhr();
+
+
+
+
+var elemdisplay = {},
+       rfxtypes = /^(?:toggle|show|hide)$/,
+       rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/,
+       timerId,
+       fxAttrs = [
+               // height animations
+               [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+               // width animations
+               [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+               // opacity animations
+               [ "opacity" ]
+       ];
+
+jQuery.fn.extend({
+       show: function( speed, easing, callback ) {
+               if ( speed || speed === 0 ) {
+                       return this.animate( genFx("show", 3), speed, easing, callback);
+               } else {
+                       for ( var i = 0, j = this.length; i < j; i++ ) {
+                               // Reset the inline display of this element to learn if it is
+                               // being hidden by cascaded rules or not
+                               if ( !jQuery.data(this[i], "olddisplay") && this[i].style.display === "none" ) {
+                                       this[i].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 ( this[i].style.display === "" && jQuery.css( this[i], "display" ) === "none" ) {
+                                       jQuery.data(this[i], "olddisplay", defaultDisplay(this[i].nodeName));
+                               }
+                       }
+
+                       // Set the display of most of the elements in a second loop
+                       // to avoid the constant reflow
+                       for ( i = 0; i < j; i++ ) {
+                               this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
+                       }
+
+                       return this;
+               }
+       },
+
+       hide: function( speed, easing, callback ) {
+               if ( speed || speed === 0 ) {
+                       return this.animate( genFx("hide", 3), speed, easing, callback);
+
+               } else {
+                       for ( var i = 0, j = this.length; i < j; i++ ) {
+                               var display = jQuery.css( this[i], "display" );
+
+                               if ( display !== "none" ) {
+                                       jQuery.data( this[i], "olddisplay", display );
+                               }
+                       }
+
+                       // Set the display of the elements in a second loop
+                       // to avoid the constant reflow
+                       for ( i = 0; i < j; i++ ) {
+                               this[i].style.display = "none";
+                       }
+
+                       return this;
+               }
+       },
+
+       // Save the old toggle function
+       _toggle: jQuery.fn.toggle,
+
+       toggle: function( fn, fn2, callback ) {
+               var bool = typeof fn === "boolean";
+
+               if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+                       this._toggle.apply( this, arguments );
+
+               } else if ( fn == null || bool ) {
+                       this.each(function() {
+                               var state = bool ? fn : jQuery(this).is(":hidden");
+                               jQuery(this)[ state ? "show" : "hide" ]();
+                       });
+
+               } else {
+                       this.animate(genFx("toggle", 3), fn, fn2, callback);
+               }
+
+               return this;
+       },
+
+       fadeTo: function( speed, to, easing, callback ) {
+               return this.filter(":hidden").css("opacity", 0).show().end()
+                                       .animate({opacity: to}, speed, easing, callback);
+       },
+
+       animate: function( prop, speed, easing, callback ) {
+               var optall = jQuery.speed(speed, easing, callback);
+
+               if ( jQuery.isEmptyObject( prop ) ) {
+                       return this.each( optall.complete );
+               }
+
+               return this[ optall.queue === false ? "each" : "queue" ](function() {
+                       // XXX ‘this’ does not always have a nodeName when running the
+                       // test suite
+
+                       var opt = jQuery.extend({}, optall), p,
+                               isElement = this.nodeType === 1,
+                               hidden = isElement && jQuery(this).is(":hidden"),
+                               self = this;
+
+                       for ( p in prop ) {
+                               var name = jQuery.camelCase( p );
+
+                               if ( p !== name ) {
+                                       prop[ name ] = prop[ p ];
+                                       delete prop[ p ];
+                                       p = name;
+                               }
+
+                               if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
+                                       return opt.complete.call(this);
+                               }
+
+                               if ( isElement && ( p === "height" || p === "width" ) ) {
+                                       // Make sure that nothing sneaks out
+                                       // Record all 3 overflow attributes because IE does not
+                                       // change the overflow attribute when overflowX and
+                                       // overflowY are set to the same value
+                                       opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+                                       // Set display property to inline-block for height/width
+                                       // animations on inline elements that are having width/height
+                                       // animated
+                                       if ( jQuery.css( this, "display" ) === "inline" &&
+                                                       jQuery.css( this, "float" ) === "none" ) {
+                                               if ( !jQuery.support.inlineBlockNeedsLayout ) {
+                                                       this.style.display = "inline-block";
+
+                                               } else {
+                                                       var display = defaultDisplay(this.nodeName);
+
+                                                       // inline-level elements accept inline-block;
+                                                       // block-level elements need to be inline with layout
+                                                       if ( display === "inline" ) {
+                                                               this.style.display = "inline-block";
+
+                                                       } else {
+                                                               this.style.display = "inline";
+                                                               this.style.zoom = 1;
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               if ( jQuery.isArray( prop[p] ) ) {
+                                       // Create (if needed) and add to specialEasing
+                                       (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
+                                       prop[p] = prop[p][0];
+                               }
+                       }
+
+                       if ( opt.overflow != null ) {
+                               this.style.overflow = "hidden";
+                       }
+
+                       opt.curAnim = jQuery.extend({}, prop);
+
+                       jQuery.each( prop, function( name, val ) {
+                               var e = new jQuery.fx( self, opt, name );
+
+                               if ( rfxtypes.test(val) ) {
+                                       e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+
+                               } else {
+                                       var parts = rfxnum.exec(val),
+                                               start = e.cur(true) || 0;
+
+                                       if ( parts ) {
+                                               var end = parseFloat( parts[2] ),
+                                                       unit = parts[3] || "px";
+
+                                               // We need to compute starting value
+                                               if ( unit !== "px" ) {
+                                                       jQuery.style( self, name, (end || 1) + unit);
+                                                       start = ((end || 1) / e.cur(true)) * start;
+                                                       jQuery.style( self, name, start + unit);
+                                               }
+
+                                               // If a +=/-= token was provided, we're doing a relative animation
+                                               if ( parts[1] ) {
+                                                       end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
+                                               }
+
+                                               e.custom( start, end, unit );
+
+                                       } else {
+                                               e.custom( start, val, "" );
+                                       }
+                               }
+                       });
+
+                       // For JS strict compliance
+                       return true;
+               });
+       },
+
+       stop: function( clearQueue, gotoEnd ) {
+               var timers = jQuery.timers;
+
+               if ( clearQueue ) {
+                       this.queue([]);
+               }
+
+               this.each(function() {
+                       // go in reverse order so anything added to the queue during the loop is ignored
+                       for ( var i = timers.length - 1; i >= 0; i-- ) {
+                               if ( timers[i].elem === this ) {
+                                       if (gotoEnd) {
+                                               // force the next step to be the last
+                                               timers[i](true);
+                                       }
+
+                                       timers.splice(i, 1);
+                               }
+                       }
+               });
+
+               // start the next in the queue if the last step wasn't forced
+               if ( !gotoEnd ) {
+                       this.dequeue();
+               }
+
+               return this;
+       }
+
+});
+
+function genFx( type, num ) {
+       var obj = {};
+
+       jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
+               obj[ this ] = type;
+       });
+
+       return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+       slideDown: genFx("show", 1),
+       slideUp: genFx("hide", 1),
+       slideToggle: genFx("toggle", 1),
+       fadeIn: { opacity: "show" },
+       fadeOut: { opacity: "hide" }
+}, function( name, props ) {
+       jQuery.fn[ name ] = function( speed, easing, callback ) {
+               return this.animate( props, speed, easing, callback );
+       };
+});
+
+jQuery.extend({
+       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;
+
+               // Queueing
+               opt.old = opt.complete;
+               opt.complete = function() {
+                       if ( opt.queue !== false ) {
+                               jQuery(this).dequeue();
+                       }
+                       if ( jQuery.isFunction( opt.old ) ) {
+                               opt.old.call( this );
+                       }
+               };
+
+               return opt;
+       },
+
+       easing: {
+               linear: function( p, n, firstNum, diff ) {
+                       return firstNum + diff * p;
+               },
+               swing: function( p, n, firstNum, diff ) {
+                       return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+               }
+       },
+
+       timers: [],
+
+       fx: function( elem, options, prop ) {
+               this.options = options;
+               this.elem = elem;
+               this.prop = prop;
+
+               if ( !options.orig ) {
+                       options.orig = {};
+               }
+       }
+
+});
+
+jQuery.fx.prototype = {
+       // Simple function for setting a style value
+       update: function() {
+               if ( this.options.step ) {
+                       this.options.step.call( this.elem, this.now, this );
+               }
+
+               (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+       },
+
+       // Get the current size
+       cur: function() {
+               if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
+                       return this.elem[ this.prop ];
+               }
+
+               var r = parseFloat( jQuery.css( this.elem, this.prop ) );
+               return r && r > -10000 ? r : 0;
+       },
+
+       // Start an animation from one number to another
+       custom: function( from, to, unit ) {
+               this.startTime = jQuery.now();
+               this.start = from;
+               this.end = to;
+               this.unit = unit || this.unit || "px";
+               this.now = this.start;
+               this.pos = this.state = 0;
+
+               var self = this, fx = jQuery.fx;
+               function t( gotoEnd ) {
+                       return self.step(gotoEnd);
+               }
+
+               t.elem = this.elem;
+
+               if ( t() && jQuery.timers.push(t) && !timerId ) {
+                       timerId = setInterval(fx.tick, fx.interval);
+               }
+       },
+
+       // Simple 'show' function
+       show: function() {
+               // Remember where we started, so that we can go back to it later
+               this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+               this.options.show = true;
+
+               // Begin the animation
+               // Make sure that we start at a small width/height to avoid any
+               // flash of content
+               this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
+
+               // Start by showing the element
+               jQuery( this.elem ).show();
+       },
+
+       // Simple 'hide' function
+       hide: function() {
+               // Remember where we started, so that we can go back to it later
+               this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+               this.options.hide = true;
+
+               // Begin the animation
+               this.custom(this.cur(), 0);
+       },
+
+       // Each step of an animation
+       step: function( gotoEnd ) {
+               var t = jQuery.now(), done = true;
+
+               if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+                       this.now = this.end;
+                       this.pos = this.state = 1;
+                       this.update();
+
+                       this.options.curAnim[ this.prop ] = true;
+
+                       for ( var i in this.options.curAnim ) {
+                               if ( this.options.curAnim[i] !== true ) {
+                                       done = false;
+                               }
+                       }
+
+                       if ( done ) {
+                               // Reset the overflow
+                               if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+                                       var elem = this.elem, options = this.options;
+                                       jQuery.each( [ "", "X", "Y" ], function (index, value) {
+                                               elem.style[ "overflow" + value ] = options.overflow[index];
+                                       } );
+                               }
+
+                               // Hide the element if the "hide" operation was done
+                               if ( this.options.hide ) {
+                                       jQuery(this.elem).hide();
+                               }
+
+                               // Reset the properties, if the item has been hidden or shown
+                               if ( this.options.hide || this.options.show ) {
+                                       for ( var p in this.options.curAnim ) {
+                                               jQuery.style( this.elem, p, this.options.orig[p] );
+                                       }
+                               }
+
+                               // Execute the complete function
+                               this.options.complete.call( this.elem );
+                       }
+
+                       return false;
+
+               } else {
+                       var n = t - this.startTime;
+                       this.state = n / this.options.duration;
+
+                       // Perform the easing function, defaults to swing
+                       var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
+                       var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
+                       this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
+                       this.now = this.start + ((this.end - this.start) * this.pos);
+
+                       // Perform the next step of the animation
+                       this.update();
+               }
+
+               return true;
+       }
+};
+
+jQuery.extend( jQuery.fx, {
+       tick: function() {
+               var timers = jQuery.timers;
+
+               for ( var i = 0; i < timers.length; i++ ) {
+                       if ( !timers[i]() ) {
+                               timers.splice(i--, 1);
+                       }
+               }
+
+               if ( !timers.length ) {
+                       jQuery.fx.stop();
+               }
+       },
+
+       interval: 13,
+
+       stop: function() {
+               clearInterval( timerId );
+               timerId = null;
+       },
+
+       speeds: {
+               slow: 600,
+               fast: 200,
+               // Default speed
+               _default: 400
+       },
+
+       step: {
+               opacity: function( fx ) {
+                       jQuery.style( fx.elem, "opacity", fx.now );
+               },
+
+               _default: function( fx ) {
+                       if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+                               fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
+                       } else {
+                               fx.elem[ fx.prop ] = fx.now;
+                       }
+               }
+       }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+       jQuery.expr.filters.animated = function( elem ) {
+               return jQuery.grep(jQuery.timers, function( fn ) {
+                       return elem === fn.elem;
+               }).length;
+       };
+}
+
+function defaultDisplay( nodeName ) {
+       if ( !elemdisplay[ nodeName ] ) {
+               var elem = jQuery("<" + nodeName + ">").appendTo("body"),
+                       display = elem.css("display");
+
+               elem.remove();
+
+               if ( display === "none" || display === "" ) {
+                       display = "block";
+               }
+
+               elemdisplay[ nodeName ] = display;
+       }
+
+       return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+       rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+       jQuery.fn.offset = function( options ) {
+               var elem = this[0], box;
+
+               if ( options ) { 
+                       return this.each(function( i ) {
+                               jQuery.offset.setOffset( this, options, i );
+                       });
+               }
+
+               if ( !elem || !elem.ownerDocument ) {
+                       return null;
+               }
+
+               if ( elem === elem.ownerDocument.body ) {
+                       return jQuery.offset.bodyOffset( elem );
+               }
+
+               try {
+                       box = elem.getBoundingClientRect();
+               } catch(e) {}
+
+               var doc = elem.ownerDocument,
+                       docElem = doc.documentElement;
+
+               // Make sure we're not dealing with a disconnected DOM node
+               if ( !box || !jQuery.contains( docElem, elem ) ) {
+                       return box || { top: 0, left: 0 };
+               }
+
+               var body = doc.body,
+                       win = getWindow(doc),
+                       clientTop  = docElem.clientTop  || body.clientTop  || 0,
+                       clientLeft = docElem.clientLeft || body.clientLeft || 0,
+                       scrollTop  = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop ),
+                       scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft),
+                       top  = box.top  + scrollTop  - clientTop,
+                       left = box.left + scrollLeft - clientLeft;
+
+               return { top: top, left: left };
+       };
+
+} else {
+       jQuery.fn.offset = function( options ) {
+               var elem = this[0];
+
+               if ( options ) { 
+                       return this.each(function( i ) {
+                               jQuery.offset.setOffset( this, options, i );
+                       });
+               }
+
+               if ( !elem || !elem.ownerDocument ) {
+                       return null;
+               }
+
+               if ( elem === elem.ownerDocument.body ) {
+                       return jQuery.offset.bodyOffset( elem );
+               }
+
+               jQuery.offset.initialize();
+
+               var offsetParent = elem.offsetParent, prevOffsetParent = elem,
+                       doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
+                       body = doc.body, defaultView = doc.defaultView,
+                       prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+                       top = elem.offsetTop, left = elem.offsetLeft;
+
+               while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+                       if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+                               break;
+                       }
+
+                       computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+                       top  -= elem.scrollTop;
+                       left -= elem.scrollLeft;
+
+                       if ( elem === offsetParent ) {
+                               top  += elem.offsetTop;
+                               left += elem.offsetLeft;
+
+                               if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+                                       top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+                                       left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+                               }
+
+                               prevOffsetParent = offsetParent;
+                               offsetParent = elem.offsetParent;
+                       }
+
+                       if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+                               top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+                               left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+                       }
+
+                       prevComputedStyle = computedStyle;
+               }
+
+               if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+                       top  += body.offsetTop;
+                       left += body.offsetLeft;
+               }
+
+               if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+                       top  += Math.max( docElem.scrollTop, body.scrollTop );
+                       left += Math.max( docElem.scrollLeft, body.scrollLeft );
+               }
+
+               return { top: top, left: left };
+       };
+}
+
+jQuery.offset = {
+       initialize: function() {
+               var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
+                       html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+
+               jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
+
+               container.innerHTML = html;
+               body.insertBefore( container, body.firstChild );
+               innerDiv = container.firstChild;
+               checkDiv = innerDiv.firstChild;
+               td = innerDiv.nextSibling.firstChild.firstChild;
+
+               this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+               this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+               checkDiv.style.position = "fixed";
+               checkDiv.style.top = "20px";
+
+               // safari subtracts parent border width here which is 5px
+               this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
+               checkDiv.style.position = checkDiv.style.top = "";
+
+               innerDiv.style.overflow = "hidden";
+               innerDiv.style.position = "relative";
+
+               this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+               this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
+
+               body.removeChild( container );
+               body = container = innerDiv = checkDiv = table = td = null;
+               jQuery.offset.initialize = jQuery.noop;
+       },
+
+       bodyOffset: function( body ) {
+               var top = body.offsetTop, left = body.offsetLeft;
+
+               jQuery.offset.initialize();
+
+               if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
+                       top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+                       left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+               }
+
+               return { top: top, left: left };
+       },
+       
+       setOffset: function( elem, options, i ) {
+               var position = jQuery.css( elem, "position" );
+
+               // set position first, in-case top/left are set even on static elem
+               if ( position === "static" ) {
+                       elem.style.position = "relative";
+               }
+
+               var curElem = jQuery( elem ),
+                       curOffset = curElem.offset(),
+                       curCSSTop = jQuery.css( elem, "top" ),
+                       curCSSLeft = jQuery.css( elem, "left" ),
+                       calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1),
+                       props = {}, curPosition = {}, curTop, curLeft;
+
+               // need to be able to calculate position if either top or left is auto and position is absolute
+               if ( calculatePosition ) {
+                       curPosition = curElem.position();
+               }
+
+               curTop  = calculatePosition ? curPosition.top  : parseInt( curCSSTop,  10 ) || 0;
+               curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0;
+
+               if ( jQuery.isFunction( options ) ) {
+                       options = options.call( elem, i, 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({
+       position: function() {
+               if ( !this[0] ) {
+                       return null;
+               }
+
+               var elem = this[0],
+
+               // Get *real* offsetParent
+               offsetParent = this.offsetParent(),
+
+               // Get correct offsets
+               offset       = this.offset(),
+               parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+               // Subtract element margins
+               // note: when an element has margin: auto the offsetLeft and marginLeft
+               // are the same in Safari causing offset.left to incorrectly be 0
+               offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+               offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+               // Add offsetParent borders
+               parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+               parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+               // Subtract the two offsets
+               return {
+                       top:  offset.top  - parentOffset.top,
+                       left: offset.left - parentOffset.left
+               };
+       },
+
+       offsetParent: function() {
+               return this.map(function() {
+                       var offsetParent = this.offsetParent || document.body;
+                       while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+                               offsetParent = offsetParent.offsetParent;
+                       }
+                       return offsetParent;
+               });
+       }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+       var method = "scroll" + name;
+
+       jQuery.fn[ method ] = function(val) {
+               var elem = this[0], win;
+               
+               if ( !elem ) {
+                       return null;
+               }
+
+               if ( val !== undefined ) {
+                       // Set the scroll offset
+                       return this.each(function() {
+                               win = getWindow( this );
+
+                               if ( win ) {
+                                       win.scrollTo(
+                                               !i ? val : jQuery(win).scrollLeft(),
+                                                i ? val : jQuery(win).scrollTop()
+                                       );
+
+                               } else {
+                                       this[ method ] = val;
+                               }
+                       });
+               } else {
+                       win = getWindow( elem );
+
+                       // Return the scroll offset
+                       return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+                               jQuery.support.boxModel && win.document.documentElement[ method ] ||
+                                       win.document.body[ method ] :
+                               elem[ method ];
+               }
+       };
+});
+
+function getWindow( elem ) {
+       return jQuery.isWindow( elem ) ?
+               elem :
+               elem.nodeType === 9 ?
+                       elem.defaultView || elem.parentWindow :
+                       false;
+}
+
+
+
+
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+       var type = name.toLowerCase();
+
+       // innerHeight and innerWidth
+       jQuery.fn["inner" + name] = function() {
+               return this[0] ?
+                       parseFloat( jQuery.css( this[0], type, "padding" ) ) :
+                       null;
+       };
+
+       // outerHeight and outerWidth
+       jQuery.fn["outer" + name] = function( margin ) {
+               return this[0] ?
+                       parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) :
+                       null;
+       };
+
+       jQuery.fn[ type ] = function( size ) {
+               // Get window width or height
+               var elem = this[0];
+               if ( !elem ) {
+                       return size == null ? null : this;
+               }
+               
+               if ( jQuery.isFunction( size ) ) {
+                       return this.each(function( i ) {
+                               var self = jQuery( this );
+                               self[ type ]( size.call( this, i, self[ type ]() ) );
+                       });
+               }
+
+               return jQuery.isWindow( elem ) ?
+                       // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+                       elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] ||
+                       elem.document.body[ "client" + name ] :
+
+                       // Get document width or height
+                       (elem.nodeType === 9) ? // is it a document
+                               // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+                               Math.max(
+                                       elem.documentElement["client" + name],
+                                       elem.body["scroll" + name], elem.documentElement["scroll" + name],
+                                       elem.body["offset" + name], elem.documentElement["offset" + name]
+                               ) :
+
+                               // Get or set width or height on the element
+                               size === undefined ?
+                                       // Get width or height on the element
+                                       parseFloat( jQuery.css( elem, type ) ) :
+
+                                       // Set the width or height on the element (default to pixels if value is unitless)
+                                       this.css( type, typeof size === "string" ? size : size + "px" );
+       };
+
+});
+
+
+})(window);
diff --git a/lib/jquery-1.4.3.min.js b/lib/jquery-1.4.3.min.js
new file mode 100644 (file)
index 0000000..c941a5f
--- /dev/null
@@ -0,0 +1,166 @@
+/*!
+ * jQuery JavaScript Library v1.4.3
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Oct 14 23:10:06 2010 -0400
+ */
+(function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;n<H.length;n++){k=H[n];k.origType.replace(X,
+"")===a.type?f.push(k.selector):H.splice(n--,1)}f=c(a.target).closest(f,a.currentTarget);s=0;for(v=f.length;s<v;s++){B=f[s];for(n=0;n<H.length;n++){k=H[n];if(B.selector===k.selector&&(!D||D.test(k.namespace))){l=B.elem;h=null;if(k.preType==="mouseenter"||k.preType==="mouseleave"){a.type=k.preType;h=c(a.relatedTarget).closest(k.selector)[0]}if(!h||h!==l)e.push({elem:l,handleObj:k,level:B.level})}}}s=0;for(v=e.length;s<v;s++){f=e[s];if(d&&f.level>d)break;a.currentTarget=f.elem;a.data=f.handleObj.data;
+a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b,
+e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}
+function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)?
+e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a,
+1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false,
+q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i=
+[u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i);
+else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ":
+"")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r,
+y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r<y;r++)if((F=arguments[r])!=null)for(I in F){K=i[I];J=F[I];if(i!==J)if(z&&J&&(b.isPlainObject(J)||(fa=b.isArray(J)))){if(fa){fa=false;clone=K&&b.isArray(K)?K:[]}else clone=
+K&&b.isPlainObject(K)?K:{};i[I]=b.extend(z,clone,J)}else if(J!==A)i[I]=J}return i};b.extend({noConflict:function(i){E.$=e;if(i)E.jQuery=d;return b},isReady:false,readyWait:1,ready:function(i){i===true&&b.readyWait--;if(!b.readyWait||i!==true&&!b.isReady){if(!u.body)return setTimeout(b.ready,1);b.isReady=true;if(!(i!==true&&--b.readyWait>0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready,
+1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i==
+null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i);
+if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()===
+r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;F<I;){if(r.apply(i[F++],y)===false)break}else if(K)for(z in i){if(r.call(i[z],z,i[z])===false)break}else for(y=i[0];F<I&&r.call(y,F,y)!==false;y=i[++F]);return i},trim:R?function(i){return i==null?"":R.call(i)}:function(i){return i==null?"":i.toString().replace(l,"").replace(n,"")},makeArray:function(i,r){var y=r||[];if(i!=null){var z=b.type(i);i.length==
+null||z==="string"||z==="function"||z==="regexp"||b.isWindow(i)?P.call(y,i):b.merge(y,i)}return y},inArray:function(i,r){if(r.indexOf)return r.indexOf(i);for(var y=0,z=r.length;y<z;y++)if(r[y]===i)return y;return-1},merge:function(i,r){var y=i.length,z=0;if(typeof r.length==="number")for(var F=r.length;z<F;z++)i[y++]=r[z];else for(;r[z]!==A;)i[y++]=r[z++];i.length=y;return i},grep:function(i,r,y){var z=[],F;y=!!y;for(var I=0,K=i.length;I<K;I++){F=!!r(i[I],I);y!==F&&z.push(i[I])}return z},map:function(i,
+r,y){for(var z=[],F,I=0,K=i.length;I<K;I++){F=r(i[I],I,y);if(F!=null)z[z.length]=F}return z.concat.apply([],z)},guid:1,proxy:function(i,r,y){if(arguments.length===2)if(typeof r==="string"){y=i;i=y[r];r=A}else if(r&&!b.isFunction(r)){y=r;r=A}if(!r&&i)r=function(){return i.apply(y||this,arguments)};if(i)r.guid=i.guid=i.guid||r.guid||b.guid++;return r},access:function(i,r,y,z,F,I){var K=i.length;if(typeof r==="object"){for(var J in r)b.access(i,J,r[J],z,F,y);return i}if(y!==A){z=!I&&z&&b.isFunction(y);
+for(J=0;J<K;J++)F(i[J],r,z?y.call(i[J],J,F(i[J],r)):y,I);return i}return K?F(i[0],r):A},now:function(){return(new Date).getTime()},uaMatch:function(i){i=i.toLowerCase();i=M.exec(i)||g.exec(i)||j.exec(i)||i.indexOf("compatible")<0&&o.exec(i)||[];return{browser:i[1]||"",version:i[2]||"0"}},browser:{}});b.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(i,r){L["[object "+r+"]"]=r.toLowerCase()});m=b.uaMatch(m);if(m.browser){b.browser[m.browser]=true;b.browser.version=
+m.version}if(b.browser.webkit)b.browser.safari=true;if(Q)b.inArray=function(i,r){return Q.call(r,i)};if(!/\s/.test("\u00a0")){l=/^[\s\xA0]+/;n=/[\s\xA0]+$/}f=b(u);if(u.addEventListener)t=function(){u.removeEventListener("DOMContentLoaded",t,false);b.ready()};else if(u.attachEvent)t=function(){if(u.readyState==="complete"){u.detachEvent("onreadystatechange",t);b.ready()}};return E.jQuery=E.$=b}();(function(){c.support={};var a=u.documentElement,b=u.createElement("script"),d=u.createElement("div"),
+e="script"+c.now();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var f=d.getElementsByTagName("*"),h=d.getElementsByTagName("a")[0],k=u.createElement("select"),l=k.appendChild(u.createElement("option"));if(!(!f||!f.length||!h)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(h.getAttribute("style")),
+hrefNormalized:h.getAttribute("href")==="/a",opacity:/^0.55$/.test(h.style.opacity),cssFloat:!!h.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:l.selected,optDisabled:false,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableHiddenOffsets:true};k.disabled=true;c.support.optDisabled=!l.disabled;b.type="text/javascript";try{b.appendChild(u.createTextNode("window."+e+"=1;"))}catch(n){}a.insertBefore(b,
+a.firstChild);if(E[e]){c.support.scriptEval=true;delete E[e]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function s(){c.support.noCloneEvent=false;d.detachEvent("onclick",s)});d.cloneNode(true).fireEvent("onclick")}d=u.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div");
+s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="<div style='width:4px;'></div>";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight===
+0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",
+cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]=
+c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b=
+c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e===
+"string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e||
+[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,
+a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,
+a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1)if(f.className){for(var h=" "+f.className+" ",k=f.className,l=0,n=b.length;l<n;l++)if(h.indexOf(" "+b[l]+" ")<0)k+=" "+b[l];f.className=c.trim(k)}else f.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(n){var s=
+c(this);s.removeClass(a.call(this,n,s.attr("class")))});if(a&&typeof a==="string"||a===A)for(var b=(a||"").split(ga),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1&&f.className)if(a){for(var h=(" "+f.className+" ").replace(qa," "),k=0,l=b.length;k<l;k++)h=h.replace(" "+b[k]+" "," ");f.className=c.trim(h)}else f.className=""}return this},toggleClass:function(a,b){var d=typeof a,e=typeof b==="boolean";if(c.isFunction(a))return this.each(function(f){var h=c(this);h.toggleClass(a.call(this,
+f,h.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var f,h=0,k=c(this),l=b,n=a.split(ga);f=n[h++];){l=e?l:!k.hasClass(f);k[l?"addClass":"removeClass"](f)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(qa," ").indexOf(a)>-1)return true;return false},
+val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h<e;h++){var k=f[h];if(k.selected&&(c.support.optDisabled?!k.disabled:k.getAttribute("disabled")===null)&&(!k.parentNode.disabled||!c.nodeName(k.parentNode,"optgroup"))){a=c(k).val();if(b)return a;d.push(a)}}return d}if(ra.test(b.type)&&
+!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Pa,"")}return A}var l=c.isFunction(a);return this.each(function(n){var s=c(this),v=a;if(this.nodeType===1){if(l)v=a.call(this,n,s.val());if(v==null)v="";else if(typeof v==="number")v+="";else if(c.isArray(v))v=c.map(v,function(D){return D==null?"":D+""});if(c.isArray(v)&&ra.test(this.type))this.checked=c.inArray(s.val(),v)>=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&&
+h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l===
+"function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[];
+if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b||
+typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h<B.length;h++){D=B[h];if(d.guid===D.guid){if(l||s.test(D.namespace)){e==null&&B.splice(h--,1);v.remove&&v.remove.call(a,D)}if(e!=null)break}}if(B.length===0||e!=null&&B.length===1){if(!v.teardown||
+v.teardown.call(a,n)===false)c.removeEvent(a,f,w.handle);delete G[f]}}else for(h=0;h<B.length;h++){D=B[h];if(l||s.test(D.namespace)){c.event.remove(a,v,D.handler,h);B.splice(h--,1)}}}if(c.isEmptyObject(G)){if(b=w.handle)b.elem=null;delete w.events;delete w.handle;if(typeof w==="function")c.removeData(a,H);else c.isEmptyObject(w)&&c.removeData(a)}}}}},trigger:function(a,b,d,e){var f=a.type||a;if(!e){a=typeof a==="object"?a[c.expando]?a:c.extend(c.Event(f),a):c.Event(f);if(f.indexOf("!")>=0){a.type=
+f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===
+false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e;
+d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f<k;f++){var l=d[f];if(b||e.test(l.namespace)){a.handler=l.handler;a.data=
+l.data;a.handleObj=l;l=l.handler.apply(this,h);if(l!==A){a.result=l;if(l===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[c.expando])return a;var b=a;a=c.Event(b);for(var d=this.props.length,e;d;){e=this.props[--d];a[e]=b[e]}if(!a.target)a.target=a.srcElement||u;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=u.documentElement;d=u.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(a.which==null&&(a.charCode!=null||a.keyCode!=null))a.which=a.charCode!=null?a.charCode:a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==A)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,Y(a.origType,a.selector),c.extend({},a,{handler:Ga,guid:a.handler.guid}))},remove:function(a){c.event.remove(this,
+Y(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,d){if(c.isWindow(this))this.onbeforeunload=d},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.removeEvent=u.removeEventListener?function(a,b,d){a.removeEventListener&&a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent&&a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=
+c.now();this[c.expando]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=ba;var a=this.originalEvent;if(a)if(a.preventDefault)a.preventDefault();else a.returnValue=false},stopPropagation:function(){this.isPropagationStopped=ba;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=ba;this.stopPropagation()},isDefaultPrevented:U,isPropagationStopped:U,isImmediatePropagationStopped:U};
+var ta=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},ua=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?ua:ta,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?ua:ta)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(){if(this.nodeName.toLowerCase()!==
+"form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length){a.liveFired=A;return ja("submit",this,arguments)}});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13){a.liveFired=A;return ja("submit",this,arguments)}})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};if(!c.support.changeBubbles){var V,
+va=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired=
+A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type===
+"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]===
+0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h<l;h++)c.event.add(this[h],d,k,e)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault)for(var d in a)this.unbind(d,
+a[d]);else{d=0;for(var e=this.length;d<e;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,e){return this.live(b,d,e,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){var d=c.Event(a);d.preventDefault();d.stopPropagation();c.event.trigger(d,b,this[0]);return d.result}},toggle:function(a){for(var b=arguments,d=
+1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(e){var f=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,f+1);e.preventDefault();return b[f].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var wa={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,e,f,h){var k,l=0,n,s,v=h||this.selector;h=h?this:c(this.context);if(typeof d===
+"object"&&!d.preventDefault){for(k in d)h[b](k,e,d[k],v);return this}if(c.isFunction(e)){f=e;e=A}for(d=(d||"").split(" ");(k=d[l++])!=null;){n=X.exec(k);s="";if(n){s=n[0];k=k.replace(X,"")}if(k==="hover")d.push("mouseenter"+s,"mouseleave"+s);else{n=k;if(k==="focus"||k==="blur"){d.push(wa[k]+s);k+=s}else k=(wa[k]||k)+s;if(b==="live"){s=0;for(var B=h.length;s<B;s++)c.event.add(h[s],"live."+Y(k,v),{data:e,selector:v,handler:f,origType:k,origHandler:f,preType:n})}else h.unbind("live."+Y(k,v),f)}}return this}});
+c.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".split(" "),function(a,b){c.fn[b]=function(d,e){if(e==null){e=d;d=null}return arguments.length>0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
+(function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p<t;p++){var x=m[p];if(x){x=x[g];for(var C=false;x;){if(x.sizcache===o){C=m[x.sizset];break}if(x.nodeType===1&&!q){x.sizcache=o;x.sizset=p}if(x.nodeName.toLowerCase()===j){C=x;break}x=x[g]}m[p]=C}}}function b(g,j,o,m,p,q){p=0;for(var t=m.length;p<t;p++){var x=m[p];if(x){x=x[g];for(var C=false;x;){if(x.sizcache===o){C=m[x.sizset];break}if(x.nodeType===1){if(!q){x.sizcache=o;x.sizset=p}if(typeof j!=="string"){if(x===j){C=true;break}}else if(l.filter(j,
+[x]).length>0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3];
+break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr,
+t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h=
+k;g.sort(w);if(h)for(var j=1;j<g.length;j++)g[j]===g[j-1]&&g.splice(j--,1)}return g};l.matches=function(g,j){return l(g,null,null,j)};l.matchesSelector=function(g,j){return l(j,null,null,[g]).length>0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p<q;p++){var t=n.order[p],x;if(x=n.leftMatch[t].exec(g)){var C=x[1];x.splice(1,1);if(C.substr(C.length-1)!=="\\"){x[1]=(x[1]||"").replace(/\\/g,"");m=n.find[t](x,j,o);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=j.getElementsByTagName("*"));
+return{set:m,expr:g}};l.filter=function(g,j,o,m){for(var p=g,q=[],t=j,x,C,P=j&&j[0]&&l.isXML(j[0]);g&&j.length;){for(var N in n.filter)if((x=n.leftMatch[N].exec(g))!=null&&x[2]){var R=n.filter[N],Q,L;L=x[1];C=false;x.splice(1,1);if(L.substr(L.length-1)!=="\\"){if(t===q)q=[];if(n.preFilter[N])if(x=n.preFilter[N](x,t,o,q,m,P)){if(x===true)continue}else C=Q=true;if(x)for(var i=0;(L=t[i])!=null;i++)if(L){Q=R(L,x,i,t);var r=m^!!Q;if(o&&Q!=null)if(r)C=true;else t[i]=false;else if(r){q.push(L);C=true}}if(Q!==
+A){o||(t=q);g=g.replace(n.match[N],"");if(!C)return[];break}}}if(g===p)if(C==null)l.error(g);else break;p=g}return t};l.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=l.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
+POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,j){var o=typeof j==="string",m=o&&!/\W/.test(j);o=o&&!m;if(m)j=j.toLowerCase();m=0;for(var p=g.length,q;m<p;m++)if(q=g[m]){for(;(q=q.previousSibling)&&q.nodeType!==1;);g[m]=o||q&&q.nodeName.toLowerCase()===
+j?q||false:q===j}o&&l.filter(j,g,true)},">":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p<q;p++){if(m=g[p]){o=m.parentNode;g[p]=o.nodeName.toLowerCase()===j?o:false}}else{for(;p<q;p++)if(m=g[p])g[p]=o?m.parentNode:m.parentNode===j;o&&l.filter(j,g,true)}},"":function(g,j,o){var m=e++,p=b,q;if(typeof j==="string"&&!/\W/.test(j)){q=j=j.toLowerCase();p=a}p("parentNode",j,m,g,q,o)},"~":function(g,j,o){var m=e++,p=b,q;if(typeof j==="string"&&!/\W/.test(j)){q=
+j=j.toLowerCase();p=a}p("previousSibling",j,m,g,q,o)}},find:{ID:function(g,j,o){if(typeof j.getElementById!=="undefined"&&!o)return(g=j.getElementById(g[1]))&&g.parentNode?[g]:[]},NAME:function(g,j){if(typeof j.getElementsByName!=="undefined"){for(var o=[],m=j.getElementsByName(g[1]),p=0,q=m.length;p<q;p++)m[p].getAttribute("name")===g[1]&&o.push(m[p]);return o.length===0?null:o}},TAG:function(g,j){return j.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,j,o,m,p,q){g=" "+g[1].replace(/\\/g,
+"")+" ";if(q)return g;q=0;for(var t;(t=j[q])!=null;q++)if(t)if(p^(t.className&&(" "+t.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o,
+m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===
+true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===
+g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return j<o[3]-0},gt:function(g,j,o){return j>o[3]-0},nth:function(g,j,o){return o[3]-
+0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o<m;o++)if(j[o]===g)return false;return true}else l.error("Syntax error, unrecognized expression: "+p)},CHILD:function(g,j){var o=j[1],m=g;switch(o){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(o===
+"first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":o=j[2];var p=j[3];if(o===1&&p===0)return true;var q=j[0],t=g.parentNode;if(t&&(t.sizcache!==q||!g.nodeIndex)){var x=0;for(m=t.firstChild;m;m=m.nextSibling)if(m.nodeType===1)m.nodeIndex=++x;t.sizcache=q}m=g.nodeIndex-p;return o===0?m===0:m%o===0&&m/o>=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===
+j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]];
+if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o,
+g);else if(typeof g.length==="number")for(var p=g.length;m<p;m++)o.push(g[m]);else for(;g[m];m++)o.push(g[m]);return o}}var w,G;if(u.documentElement.compareDocumentPosition)w=function(g,j){if(g===j){h=true;return 0}if(!g.compareDocumentPosition||!j.compareDocumentPosition)return g.compareDocumentPosition?-1:1;return g.compareDocumentPosition(j)&4?-1:1};else{w=function(g,j){var o=[],m=[],p=g.parentNode,q=j.parentNode,t=p;if(g===j){h=true;return 0}else if(p===q)return G(g,j);else if(p){if(!q)return 1}else return-1;
+for(;t;){o.unshift(t);t=t.parentNode}for(t=q;t;){m.unshift(t);t=t.parentNode}p=o.length;q=m.length;for(t=0;t<p&&t<q;t++)if(o[t]!==m[t])return G(o[t],m[t]);return t===p?G(g,m[t],-1):G(o[t],j,1)};G=function(g,j,o){if(g===j)return o;for(g=g.nextSibling;g;){if(g===j)return-1;g=g.nextSibling}return 1}}l.getText=function(g){for(var j="",o,m=0;g[m];m++){o=g[m];if(o.nodeType===3||o.nodeType===4)j+=o.nodeValue;else if(o.nodeType!==8)j+=l.getText(o.childNodes)}return j};(function(){var g=u.createElement("div"),
+j="script"+(new Date).getTime();g.innerHTML="<a name='"+j+"'/>";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g);
+o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&&
+function(){var g=l,j=u.createElement("div");j.innerHTML="<p class='TEST'></p>";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o];
+j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===
+0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g,
+j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p<t;p++)l(g,q[p],o);return l.filter(m,o)};c.find=l;c.expr=l.selectors;c.expr[":"]=c.expr.filters;c.unique=l.uniqueSort;c.text=l.getText;c.isXMLDoc=l.isXML;c.contains=l.contains})();var Wa=/Until$/,Xa=/^(?:parents|prevUntil|prevAll)/,Ya=/,/,Ja=/^.[^:#\[\.,]*$/,Za=Array.prototype.slice,$a=c.expr.match.POS;c.fn.extend({find:function(a){for(var b=this.pushStack("",
+"find",a),d=0,e=0,f=this.length;e<f;e++){d=b.length;c.find(a,this[e],b);if(e>0)for(var h=d;h<b.length;h++)for(var k=0;k<d;k++)if(b[k]===b[h]){b.splice(h--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,e=b.length;d<e;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(ka(this,a,false),"not",a)},filter:function(a){return this.pushStack(ka(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,
+b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e<f;e++){l=a[e];k[l]||(k[l]=c.expr.match.POS.test(l)?c(l,b||this.context):l)}for(;h&&h.ownerDocument&&h!==b;){for(l in k){e=k[l];if(e.jquery?e.index(h)>-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e<f;e++)for(h=this[e];h;)if(k?k.index(h)>-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h||
+!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}});
+c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",
+d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}});
+c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g,
+$=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/<tbody/i,bb=/<|&#?\w+;/,Aa=/<(?:script|object|embed|option|style)/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,cb=/\=([^="'>\s]+\/)>/g,O={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],
+area:[1,"<map>","</map>"],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,
+d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},
+unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=
+c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*"));
+c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($,
+"")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1></$2>");try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(e){this.empty().append(a)}}else c.isFunction(a)?
+this.each(function(f){var h=c(this);h.html(a.call(this,f,h.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),e=d.html();d.replaceWith(a.call(this,b,e))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,
+true)},domManip:function(a,b,d){var e,f,h=a[0],k=[],l;if(!c.support.checkClone&&arguments.length===3&&typeof h==="string"&&Ba.test(h))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(h))return this.each(function(s){var v=c(this);a[0]=h.call(this,s,b?v.html():A);v.domManip(a,b,d)});if(this[0]){e=h&&h.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:c.buildFragment(a,this,k);l=e.fragment;if(f=l.childNodes.length===1?l=l.firstChild:
+l.firstChild){b=b&&c.nodeName(f,"tr");f=0;for(var n=this.length;f<n;f++)d.call(b?c.nodeName(this[f],"table")?this[f].getElementsByTagName("tbody")[0]||this[f].appendChild(this[f].ownerDocument.createElement("tbody")):this[f]:this[f],f>0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone||
+!Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h=
+d.length;f<h;f++){var k=(f>0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1></$2>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default,
+s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]==="<table>"&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&&
+c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]?
+c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this;
+return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]||
+h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=
+e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":
+b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b],
+h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+mb=/^(?:select|textarea)/i,nb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ob=/^(?:GET|HEAD|DELETE)$/,Na=/\[\]$/,T=/\=\?(&|$)/,ia=/\?/,pb=/([?&])_=[^&]*/,qb=/^(\w+:)?\/\/([^\/?#]+)/,rb=/%20/g,sb=/#.*$/,Ea=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ea)return Ea.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d=
+b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(k,l){if(l==="success"||l==="notmodified")h.html(f?c("<div>").append(k.responseText.replace(lb,"")).find(f):k.responseText);d&&h.each(d,[k.responseText,l,k])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&
+!this.disabled&&(this.checked||mb.test(this.nodeName)||nb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})},
+getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html",
+script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),k=ob.test(h);b.url=b.url.replace(sb,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ia.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data||
+!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+kb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var l=E[d];E[d]=function(m){f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);if(c.isFunction(l))l(m);else{E[d]=A;try{delete E[d]}catch(p){}}v&&v.removeChild(B)}}if(b.dataType==="script"&&b.cache===null)b.cache=
+false;if(b.cache===false&&h==="GET"){var n=c.now(),s=b.url.replace(pb,"$1_="+n);b.url=s+(s===b.url?(ia.test(b.url)?"&":"?")+"_="+n:"")}if(b.data&&h==="GET")b.url+=(ia.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");n=(n=qb.exec(b.url))&&(n[1]&&n[1]!==location.protocol||n[2]!==location.host);if(b.dataType==="script"&&h==="GET"&&n){var v=u.getElementsByTagName("head")[0]||u.documentElement,B=u.createElement("script");if(b.scriptCharset)B.charset=b.scriptCharset;B.src=
+b.url;if(!d){var D=false;B.onload=B.onreadystatechange=function(){if(!D&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){D=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);B.onload=B.onreadystatechange=null;v&&B.parentNode&&v.removeChild(B)}}}v.insertBefore(B,v.firstChild);return A}var H=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!k||a&&a.contentType)w.setRequestHeader("Content-Type",
+b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}n||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(G){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&&
+c.triggerGlobal(b,"ajaxSend",[w,b]);var M=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){H||c.handleComplete(b,w,e,f);H=true;if(w)w.onreadystatechange=c.noop}else if(!H&&w&&(w.readyState===4||m==="timeout")){H=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d||
+c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&g.call&&g.call(w);M("abort")}}catch(j){}b.async&&b.timeout>0&&setTimeout(function(){w&&!H&&M("timeout")},b.timeout);try{w.send(k||b.data==null?null:b.data)}catch(o){c.handleError(b,w,null,o);c.handleComplete(b,w,e,f)}b.async||M();return w}},param:function(a,b){var d=[],e=function(h,k){k=c.isFunction(k)?k():k;d[d.length]=encodeURIComponent(h)+
+"="+encodeURIComponent(k)};if(b===A)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)ca(f,a[f],b,e);return d.join("&").replace(rb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",[b,a])},handleComplete:function(a,
+b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),e=a.getResponseHeader("Etag");
+if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});if(E.ActiveXObject)c.ajaxSettings.xhr=
+function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var da={},tb=/^(?:toggle|show|hide)$/,ub=/^([+\-]=)?([\d+.\-]+)(.*)$/,aa,na=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",3),a,b,d);else{a=
+0;for(b=this.length;a<b;a++){if(!c.data(this[a],"olddisplay")&&this[a].style.display==="none")this[a].style.display="";this[a].style.display===""&&c.css(this[a],"display")==="none"&&c.data(this[a],"olddisplay",oa(this[a].nodeName))}for(a=0;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b,d){if(a||a===0)return this.animate(S("hide",3),a,b,d);else{a=0;for(b=this.length;a<b;a++){d=c.css(this[a],"display");d!=="none"&&c.data(this[a],"olddisplay",d)}for(a=
+0;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b,d){var e=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||e?this.each(function(){var f=e?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(S("toggle",3),a,b,d);return this},fadeTo:function(a,b,d,e){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d,e)},animate:function(a,b,d,e){var f=c.speed(b,d,e);if(c.isEmptyObject(a))return this.each(f.complete);
+return this[f.queue===false?"each":"queue"](function(){var h=c.extend({},f),k,l=this.nodeType===1,n=l&&c(this).is(":hidden"),s=this;for(k in a){var v=c.camelCase(k);if(k!==v){a[v]=a[k];delete a[k];k=v}if(a[k]==="hide"&&n||a[k]==="show"&&!n)return h.complete.call(this);if(l&&(k==="height"||k==="width")){h.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(c.css(this,"display")==="inline"&&c.css(this,"float")==="none")if(c.support.inlineBlockNeedsLayout)if(oa(this.nodeName)===
+"inline")this.style.display="inline-block";else{this.style.display="inline";this.style.zoom=1}else this.style.display="inline-block"}if(c.isArray(a[k])){(h.specialEasing=h.specialEasing||{})[k]=a[k][1];a[k]=a[k][0]}}if(h.overflow!=null)this.style.overflow="hidden";h.curAnim=c.extend({},a);c.each(a,function(B,D){var H=new c.fx(s,h,B);if(tb.test(D))H[D==="toggle"?n?"show":"hide":D](a);else{var w=ub.exec(D),G=H.cur(true)||0;if(w){var M=parseFloat(w[2]),g=w[3]||"px";if(g!=="px"){c.style(s,B,(M||1)+g);
+G=(M||1)/H.cur(true)*G;c.style(s,B,G+g)}if(w[1])M=(w[1]==="-="?-1:1)*M+G;H.custom(G,M,g)}else H.custom(G,D,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var e=d.length-1;e>=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b,
+d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a*
+Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(h){return f.step(h)}
+this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var f=this;a=c.fx;e.elem=this.elem;if(e()&&c.timers.push(e)&&!aa)aa=setInterval(a.tick,a.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;
+this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(l,n){f.style["overflow"+n]=h.overflow[l]})}this.options.hide&&c(this.elem).hide();if(this.options.hide||
+this.options.show)for(var k in this.options.curAnim)c.style(this.elem,k,this.options.orig[k]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=
+c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},interval:13,stop:function(){clearInterval(aa);aa=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===
+b.elem}).length};var vb=/^t(?:able|d|h)$/i,Fa=/^(?:body|html)$/i;c.fn.offset="getBoundingClientRect"in u.documentElement?function(a){var b=this[0],d;if(a)return this.each(function(k){c.offset.setOffset(this,a,k)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);try{d=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,h=f.documentElement;if(!d||!c.contains(h,b))return d||{top:0,left:0};b=f.body;f=ea(f);return{top:d.top+(f.pageYOffset||c.support.boxModel&&
+h.scrollTop||b.scrollTop)-(h.clientTop||b.clientTop||0),left:d.left+(f.pageXOffset||c.support.boxModel&&h.scrollLeft||b.scrollLeft)-(h.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(s){c.offset.setOffset(this,a,s)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,e=b.ownerDocument,f,h=e.documentElement,k=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;
+for(var l=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==k&&b!==h;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;f=e?e.getComputedStyle(b,null):b.currentStyle;l-=b.scrollTop;n-=b.scrollLeft;if(b===d){l+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&vb.test(b.nodeName))){l+=parseFloat(f.borderTopWidth)||0;n+=parseFloat(f.borderLeftWidth)||0}d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&f.overflow!=="visible"){l+=
+parseFloat(f.borderTopWidth)||0;n+=parseFloat(f.borderLeftWidth)||0}f=f}if(f.position==="relative"||f.position==="static"){l+=k.offsetTop;n+=k.offsetLeft}if(c.offset.supportsFixedPosition&&f.position==="fixed"){l+=Math.max(h.scrollTop,k.scrollTop);n+=Math.max(h.scrollLeft,k.scrollLeft)}return{top:l,left:n}};c.offset={initialize:function(){var a=u.body,b=u.createElement("div"),d,e,f,h=parseFloat(c.css(a,"marginTop"))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",
+height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);d=b.firstChild;e=d.firstChild;f=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=e.offsetTop!==5;this.doesAddBorderForTableAndCells=
+f.offsetTop===5;e.style.position="fixed";e.style.top="20px";this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15;e.style.position=e.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==h;a.removeChild(b);c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.css(a,
+"marginTop"))||0;d+=parseFloat(c.css(a,"marginLeft"))||0}return{top:b,left:d}},setOffset:function(a,b,d){var e=c.css(a,"position");if(e==="static")a.style.position="relative";var f=c(a),h=f.offset(),k=c.css(a,"top"),l=c.css(a,"left"),n=e==="absolute"&&c.inArray("auto",[k,l])>-1;e={};var s={};if(n)s=f.position();k=n?s.top:parseInt(k,10)||0;l=n?s.left:parseInt(l,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+k;if(b.left!=null)e.left=b.left-h.left+l;"using"in b?b.using.call(a,
+e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Fa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||u.body;a&&!Fa.test(a.nodeName)&&
+c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==A)return this.each(function(){if(h=ea(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=ea(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();
+c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(h){var k=c(this);k[d](e.call(this,h,k[d]()))});return c.isWindow(f)?f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b]:f.nodeType===9?Math.max(f.documentElement["client"+
+b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]):e===A?parseFloat(c.css(f,d)):this.css(d,typeof e==="string"?e:e+"px")}})})(window);
diff --git a/lib/jquery.hotkeys.js b/lib/jquery.hotkeys.js
new file mode 100644 (file)
index 0000000..fbd71c7
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * http://github.com/tzuryby/hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+*/
+
+(function(jQuery){
+       
+       jQuery.hotkeys = {
+               version: "0.8",
+
+               specialKeys: {
+                       8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+                       20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+                       37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 
+                       96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
+                       104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 
+                       112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 
+                       120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
+               },
+       
+               shiftNums: {
+                       "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 
+                       "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 
+                       ".": ">",  "/": "?",  "\\": "|"
+               }
+       };
+
+       function keyHandler( handleObj ) {
+               // Only care when a possible input has been specified
+               if ( typeof handleObj.data !== "string" ) {
+                       return;
+               }
+               
+               var origHandler = handleObj.handler,
+                       keys = handleObj.data.toLowerCase().split(" ");
+       
+               handleObj.handler = function( event ) {
+                       // Don't fire in text-accepting inputs that we didn't directly bind to
+                       if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
+                                event.target.type === "text") ) {
+                               return;
+                       }
+                       
+                       // Keypress represents characters, not special keys
+                       var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
+                               character = String.fromCharCode( event.which ).toLowerCase(),
+                               key, modif = "", possible = {};
+
+                       // check combinations (alt|ctrl|shift+anything)
+                       if ( event.altKey && special !== "alt" ) {
+                               modif += "alt+";
+                       }
+
+                       if ( event.ctrlKey && special !== "ctrl" ) {
+                               modif += "ctrl+";
+                       }
+                       
+                       // TODO: Need to make sure this works consistently across platforms
+                       if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
+                               modif += "meta+";
+                       }
+
+                       if ( event.shiftKey && special !== "shift" ) {
+                               modif += "shift+";
+                       }
+
+                       if ( special ) {
+                               possible[ modif + special ] = true;
+
+                       } else {
+                               possible[ modif + character ] = true;
+                               possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
+
+                               // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+                               if ( modif === "shift+" ) {
+                                       possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
+                               }
+                       }
+
+                       for ( var i = 0, l = keys.length; i < l; i++ ) {
+                               if ( possible[ keys[i] ] ) {
+                                       return origHandler.apply( this, arguments );
+                               }
+                       }
+               };
+       }
+
+       jQuery.each([ "keydown", "keyup", "keypress" ], function() {
+               jQuery.event.special[ this ] = { add: keyHandler };
+       });
+
+})( jQuery );
\ No newline at end of file
diff --git a/lib/jquery.hotkeys.min.js b/lib/jquery.hotkeys.min.js
new file mode 100644 (file)
index 0000000..6689e7a
--- /dev/null
@@ -0,0 +1 @@
+(function(b){b.fn.__bind__=b.fn.bind;b.fn.__unbind__=b.fn.unbind;b.fn.__find__=b.fn.find;var a={version:"0.7.9",override:/keypress|keydown|keyup/g,triggersMap:{},specialKeys:{27:"esc",9:"tab",32:"space",13:"return",8:"backspace",145:"scroll",20:"capslock",144:"numlock",19:"pause",45:"insert",36:"home",46:"del",35:"end",33:"pageup",34:"pagedown",37:"left",38:"up",39:"right",40:"down",109:"-",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",191:"/"},shiftNums:{"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"_","=":"+",";":":","'":'"',",":"<",".":">","/":"?","\\":"|"},newTrigger:function(e,d,f){var c={};c[e]={};c[e][d]={cb:f,disableInInput:false};return c}};a.specialKeys=b.extend(a.specialKeys,{96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/"});b.fn.find=function(c){this.query=c;return b.fn.__find__.apply(this,arguments)};b.fn.unbind=function(h,e,g){if(b.isFunction(e)){g=e;e=null}if(e&&typeof e==="string"){var f=((this.prevObject&&this.prevObject.query)||(this[0].id&&this[0].id)||this[0]).toString();var d=h.split(" ");for(var c=0;c<d.length;c++){delete a.triggersMap[f][d[c]][e]}}return this.__unbind__(h,g)};b.fn.bind=function(j,f,k){var h=j.match(a.override);if(b.isFunction(f)||!h){return this.__bind__(j,f,k)}else{var n=null,i=b.trim(j.replace(a.override,""));if(i){n=this.__bind__(i,f,k)}if(typeof f==="string"){f={combi:f}}if(f.combi){for(var m=0;m<h.length;m++){var d=h[m];var g=f.combi.toLowerCase(),e=a.newTrigger(d,g,k),l=((this.prevObject&&this.prevObject.query)||(this[0].id&&this[0].id)||this[0]).toString();e[d][g].disableInInput=f.disableInInput;if(!a.triggersMap[l]){a.triggersMap[l]=e}else{if(!a.triggersMap[l][d]){a.triggersMap[l][d]=e[d]}}var c=a.triggersMap[l][d][g];if(!c){a.triggersMap[l][d][g]=[e[d][g]]}else{if(c.constructor!==Array){a.triggersMap[l][d][g]=[c]}else{a.triggersMap[l][d][g][c.length]=e[d][g]}}this.each(function(){var o=b(this);if(o.attr("hkId")&&o.attr("hkId")!==l){l=o.attr("hkId")+";"+l}o.attr("hkId",l)});n=this.__bind__(h.join(" "),f,a.handler)}}return n}};a.findElement=function(c){if(!b(c).attr("hkId")){if(b.browser.opera||b.browser.safari){while(!b(c).attr("hkId")&&c.parentNode){c=c.parentNode}}}return c};a.handler=function(e){var o=a.findElement(e.currentTarget),i=b(o),d=i.attr("hkId");if(d){d=d.split(";");var g=e.which,q=e.type,p=a.specialKeys[g],n=!p&&String.fromCharCode(g).toLowerCase(),h=e.shiftKey,c=e.ctrlKey,m=e.altKey||e.originalEvent.altKey,f=null;for(var r=0;r<d.length;r++){if(a.triggersMap[d[r]][q]){f=a.triggersMap[d[r]][q];break}}if(f){var j;if(!h&&!c&&!m){j=f[p]||(n&&f[n])}else{var l="";if(m){l+="alt+"}if(c){l+="ctrl+"}if(h){l+="shift+"}j=f[l+p];if(!j){if(n){j=f[l+n]||f[l+a.shiftNums[n]]||(l==="shift+"&&f[a.shiftNums[n]])}}}if(j){var s=false;for(var r=0;r<j.length;r++){if(j[r].disableInInput){var k=b(e.target);if(i.is("input")||i.is("textarea")||i.is("select")||k.is("input")||k.is("textarea")||k.is("select")){return true}}s=s||j[r].cb.apply(this,[e])}return s}}}};window.hotkeys=a;return b})(jQuery);
\ No newline at end of file
diff --git a/lib/processing-0.9.7.js b/lib/processing-0.9.7.js
new file mode 100644 (file)
index 0000000..5bd6ef9
--- /dev/null
@@ -0,0 +1,13095 @@
+/*
+
+    P R O C E S S I N G . J S - 0.9.7
+    a port of the Processing visualization language
+
+    License       : MIT
+    Developer     : John Resig: http://ejohn.org
+    Web Site      : http://processingjs.org
+    Java Version  : http://processing.org
+    Github Repo.  : http://github.com/jeresig/processing-js
+    Bug Tracking  : http://processing-js.lighthouseapp.com
+    Mozilla POW!  : http://wiki.Mozilla.org/Education/Projects/ProcessingForTheWeb
+    Maintained by : Seneca: http://zenit.senecac.on.ca/wiki/index.php/Processing.js
+                    Hyper-Metrix: http://hyper-metrix.com/#Processing
+                    BuildingSky: http://weare.buildingsky.net/pages/processing-js
+
+ */
+
+(function() {
+
+  var undef; // intentionally left undefined
+
+  var ajax = function ajax(url) {
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", url, false);
+    xhr.setRequestHeader("If-Modified-Since", "Fri, 1 Jan 1960 00:00:00 GMT");
+    xhr.send(null);
+    // failed request?
+    if (xhr.status !== 200 && xhr.status !== 0) { throw ("XMLHttpRequest failed, status code " + xhr.status); }
+    return xhr.responseText;
+  };
+
+  /* Browsers fixes start */
+  function fixReplaceByRegExp() {
+    var re = /t/g;
+    if ("t".replace(re,"") !== null && re.exec("t")) {
+      return; // it is not necessary
+    }
+    var _ie_replace = String.prototype.replace;
+    String.prototype.replace = function(searchValue, repaceValue) {
+      var result = _ie_replace.apply(this, arguments);
+      if (searchValue instanceof RegExp && searchValue.global) {
+        searchValue.lastIndex = 0;
+      }
+      return result;
+    };
+  }
+
+  function fixMatchByRegExp() {
+    var re = /t/g;
+    if ("t".match(re) !== null && re.exec("t")) {
+      return; // it is not necessary
+    }
+    var _ie_match = String.prototype.match;
+    String.prototype.match = function(searchValue) {
+      var result = _ie_match.apply(this, arguments);
+      if(searchValue instanceof RegExp && searchValue.global) {
+        searchValue.lastIndex = 0;
+      }
+      return result;
+    };
+  }
+  fixReplaceByRegExp();
+  fixMatchByRegExp();
+
+  (function fixOperaCreateImageData() {
+    try {
+      if (!("createImageData" in CanvasRenderingContext2D.prototype)) {
+        CanvasRenderingContext2D.prototype.createImageData = function (sw, sh) {
+          return new ImageData(sw, sh);
+        };
+      }
+    } catch(e) {}
+  }());
+  /* Browsers fixes end */
+
+  var PConstants = {
+    X: 0,
+    Y: 1,
+    Z: 2,
+
+    R: 3,
+    G: 4,
+    B: 5,
+    A: 6,
+
+    U: 7,
+    V: 8,
+
+    NX: 9,
+    NY: 10,
+    NZ: 11,
+
+    EDGE: 12,
+
+    // Stroke
+    SR: 13,
+    SG: 14,
+    SB: 15,
+    SA: 16,
+
+    SW: 17,
+
+    // Transformations (2D and 3D)
+    TX: 18,
+    TY: 19,
+    TZ: 20,
+
+    VX: 21,
+    VY: 22,
+    VZ: 23,
+    VW: 24,
+
+    // Material properties
+    AR: 25,
+    AG: 26,
+    AB: 27,
+
+    DR: 3,
+    DG: 4,
+    DB: 5,
+    DA: 6,
+
+    SPR: 28,
+    SPG: 29,
+    SPB: 30,
+
+    SHINE: 31,
+
+    ER: 32,
+    EG: 33,
+    EB: 34,
+
+    BEEN_LIT: 35,
+
+    VERTEX_FIELD_COUNT: 36,
+
+    // Renderers
+    P2D:    1,
+    JAVA2D: 1,
+    WEBGL:  2,
+    P3D:    2,
+    OPENGL: 2,
+    PDF:    0,
+    DXF:    0,
+
+    // Platform IDs
+    OTHER:   0,
+    WINDOWS: 1,
+    MAXOSX:  2,
+    LINUX:   3,
+
+    EPSILON: 0.0001,
+
+    MAX_FLOAT:  3.4028235e+38,
+    MIN_FLOAT: -3.4028235e+38,
+    MAX_INT:    2147483647,
+    MIN_INT:   -2147483648,
+
+    PI:         Math.PI,
+    TWO_PI:     2 * Math.PI,
+    HALF_PI:    Math.PI / 2,
+    THIRD_PI:   Math.PI / 3,
+    QUARTER_PI: Math.PI / 4,
+
+    DEG_TO_RAD: Math.PI / 180,
+    RAD_TO_DEG: 180 / Math.PI,
+
+    WHITESPACE: " \t\n\r\f\u00A0",
+
+    // Color modes
+    RGB:   1,
+    ARGB:  2,
+    HSB:   3,
+    ALPHA: 4,
+    CMYK:  5,
+
+    // Image file types
+    TIFF:  0,
+    TARGA: 1,
+    JPEG:  2,
+    GIF:   3,
+
+    // Filter/convert types
+    BLUR:      11,
+    GRAY:      12,
+    INVERT:    13,
+    OPAQUE:    14,
+    POSTERIZE: 15,
+    THRESHOLD: 16,
+    ERODE:     17,
+    DILATE:    18,
+
+    // Blend modes
+    REPLACE:    0,
+    BLEND:      1 << 0,
+    ADD:        1 << 1,
+    SUBTRACT:   1 << 2,
+    LIGHTEST:   1 << 3,
+    DARKEST:    1 << 4,
+    DIFFERENCE: 1 << 5,
+    EXCLUSION:  1 << 6,
+    MULTIPLY:   1 << 7,
+    SCREEN:     1 << 8,
+    OVERLAY:    1 << 9,
+    HARD_LIGHT: 1 << 10,
+    SOFT_LIGHT: 1 << 11,
+    DODGE:      1 << 12,
+    BURN:       1 << 13,
+
+    // Color component bit masks
+    ALPHA_MASK: 0xff000000,
+    RED_MASK:   0x00ff0000,
+    GREEN_MASK: 0x0000ff00,
+    BLUE_MASK:  0x000000ff,
+
+    // Projection matrices
+    CUSTOM:       0,
+    ORTHOGRAPHIC: 2,
+    PERSPECTIVE:  3,
+
+    // Shapes
+    POINT:          2,
+    POINTS:         2,
+    LINE:           4,
+    LINES:          4,
+    TRIANGLE:       8,
+    TRIANGLES:      9,
+    TRIANGLE_STRIP: 10,
+    TRIANGLE_FAN:   11,
+    QUAD:           16,
+    QUADS:          16,
+    QUAD_STRIP:     17,
+    POLYGON:        20,
+    PATH:           21,
+    RECT:           30,
+    ELLIPSE:        31,
+    ARC:            32,
+    SPHERE:         40,
+    BOX:            41,
+
+    GROUP:          0,
+    PRIMITIVE:      1,
+    //PATH:         21, // shared with Shape PATH
+    GEOMETRY:       3,
+
+    // Shape Vertex
+    VERTEX:        0,
+    BEZIER_VERTEX: 1,
+    CURVE_VERTEX:  2,
+    BREAK:         3,
+    CLOSESHAPE:    4,
+
+    // Shape closing modes
+    OPEN:  1,
+    CLOSE: 2,
+
+    // Shape drawing modes
+    CORNER:          0, // Draw mode convention to use (x, y) to (width, height)
+    CORNERS:         1, // Draw mode convention to use (x1, y1) to (x2, y2) coordinates
+    RADIUS:          2, // Draw mode from the center, and using the radius
+    CENTER_RADIUS:   2, // Deprecated! Use RADIUS instead
+    CENTER:          3, // Draw from the center, using second pair of values as the diameter
+    DIAMETER:        3, // Synonym for the CENTER constant. Draw from the center
+    CENTER_DIAMETER: 3, // Deprecated! Use DIAMETER instead
+
+    // Text vertical alignment modes
+    BASELINE: 0,   // Default vertical alignment for text placement
+    TOP:      101, // Align text to the top
+    BOTTOM:   102, // Align text from the bottom, using the baseline
+
+    // UV Texture coordinate modes
+    NORMAL:     1,
+    NORMALIZED: 1,
+    IMAGE:      2,
+
+    // Text placement modes
+    MODEL: 4,
+    SHAPE: 5,
+
+    // Stroke modes
+    SQUARE:  'butt',
+    ROUND:   'round',
+    PROJECT: 'square',
+    MITER:   'miter',
+    BEVEL:   'bevel',
+
+    // Lighting modes
+    AMBIENT:     0,
+    DIRECTIONAL: 1,
+    //POINT:     2, Shared with Shape constant
+    SPOT:        3,
+
+    // Key constants
+
+    // Both key and keyCode will be equal to these values
+    BACKSPACE: 8,
+    TAB:       9,
+    ENTER:     10,
+    RETURN:    13,
+    ESC:       27,
+    DELETE:    127,
+    CODED:     0xffff,
+
+    // p.key will be CODED and p.keyCode will be this value
+    SHIFT:     16,
+    CONTROL:   17,
+    ALT:       18,
+    UP:        38,
+    RIGHT:     39,
+    DOWN:      40,
+    LEFT:      37,
+
+    // Cursor types
+    ARROW:    'default',
+    CROSS:    'crosshair',
+    HAND:     'pointer',
+    MOVE:     'move',
+    TEXT:     'text',
+    WAIT:     'wait',
+    NOCURSOR: "url('data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='), auto",
+
+    // Hints
+    DISABLE_OPENGL_2X_SMOOTH:     1,
+    ENABLE_OPENGL_2X_SMOOTH:     -1,
+    ENABLE_OPENGL_4X_SMOOTH:      2,
+    ENABLE_NATIVE_FONTS:          3,
+    DISABLE_DEPTH_TEST:           4,
+    ENABLE_DEPTH_TEST:           -4,
+    ENABLE_DEPTH_SORT:            5,
+    DISABLE_DEPTH_SORT:          -5,
+    DISABLE_OPENGL_ERROR_REPORT:  6,
+    ENABLE_OPENGL_ERROR_REPORT:  -6,
+    ENABLE_ACCURATE_TEXTURES:     7,
+    DISABLE_ACCURATE_TEXTURES:   -7,
+    HINT_COUNT:                  10,
+
+    // PJS defined constants
+    SINCOS_LENGTH:      parseInt(360 / 0.5, 10),
+    PRECISIONB:         15, // fixed point precision is limited to 15 bits!!
+    PRECISIONF:         1 << 15,
+    PREC_MAXVAL:        (1 << 15) - 1,
+    PREC_ALPHA_SHIFT:   24 - 15,
+    PREC_RED_SHIFT:     16 - 15,
+    NORMAL_MODE_AUTO:   0,
+    NORMAL_MODE_SHAPE:  1,
+    NORMAL_MODE_VERTEX: 2,
+    MAX_LIGHTS:         8
+  };
+
+  // Typed Arrays: fallback to WebGL arrays or Native JS arrays if unavailable
+  function setupTypedArray(name, fallback) {
+    // check if TypedArray exists
+    if (typeof this[name] !== "function") {
+      // nope.. check if WebGLArray exists
+      if (typeof this[fallback] === "function") {
+        this[name] = this[fallback];
+      } else {
+        // nope.. set as Native JS array
+        this[name] = function(obj) {
+          if (obj instanceof Array) {
+            return obj;
+          } else if (typeof obj === "number") {
+            return new Array(obj);
+          }
+        };
+      }
+    }
+  }
+
+  setupTypedArray("Float32Array", "WebGLFloatArray");
+  setupTypedArray("Uint16Array",  "WebGLUnsignedShortArray");
+  setupTypedArray("Uint8Array",   "WebGLUnsignedByteArray");
+
+  var ArrayList = function() {
+    function createArrayList(args) {
+      var arr = [];
+      for (var i = 0; i < args[0]; i++) {
+        arr[i] = (args.length > 1 ? createArrayList(args.slice(1)) : 0 );
+      }
+
+      arr.get = function(i) {
+        return this[i];
+      };
+
+      arr.contains = function(item) {
+        return this.indexOf(item) !== -1;
+      };
+
+      arr.add = function() {
+        if (arguments.length === 1) {
+          this.push(arguments[0]); // for add(Object)
+        } else if (arguments.length === 2) {
+          if (typeof arguments[0] === 'number') {
+            if (arguments[0] >= 0 && arguments[0] <= this.length) {
+              this.splice(arguments[0], 0, arguments[1]); // for add(i, Object)
+            } else {
+              throw(arguments[0] + " is not a valid index");
+            }
+          } else {
+            throw(typeof arguments[0] + " is not a number");
+          }
+        } else {
+          throw("Please use the proper number of parameters.");
+        }
+      };
+
+      arr.set = function() {
+        if (arguments.length === 2) {
+          if (typeof arguments[0] === 'number') {
+            if (arguments[0] >= 0 && arguments[0] < this.length) {
+              this.splice(arguments[0], 1, arguments[1]);
+            } else {
+              throw(arguments[0] + " is not a valid index.");
+            }
+          } else {
+            throw(typeof arguments[0] + " is not a number");
+          }
+        } else {
+          throw("Please use the proper number of parameters.");
+        }
+      };
+
+      arr.size = function() {
+        return this.length;
+      };
+
+      arr.clear = function() {
+        this.length = 0;
+      };
+
+      arr.remove = function(i) {
+        return this.splice(i, 1)[0];
+      };
+
+      arr.isEmpty = function() {
+        return !this.length;
+      };
+
+      arr.clone = function() {
+        return this.slice(0);
+      };
+
+      arr.toArray = function() {
+        return this.slice(0);
+      };
+
+      return arr;
+    }
+
+    return createArrayList(Array.prototype.slice.call(arguments));
+  };
+
+  var HashMap = (function() {
+    function virtHashCode(obj) {
+      if (obj.constructor === String) {
+        var hash = 0;
+        for (var i = 0; i < obj.length; ++i) {
+          hash = (hash * 31 + obj.charCodeAt(i)) & 0xFFFFFFFF;
+        }
+        return hash;
+      } else if (typeof(obj) !== "object") {
+        return obj & 0xFFFFFFFF;
+      } else if ("hashCode" in obj) {
+        return obj.hashCode.call(obj);
+      } else {
+        if (obj.$id === undef) {
+          obj.$id = ((Math.floor(Math.random() * 0x10000) - 0x8000) << 16) | Math.floor(Math.random() * 0x10000);
+        }
+        return obj.$id;
+      }
+    }
+
+    function virtEquals(obj, other) {
+      if (obj === null || other === null) {
+        return (obj === null) && (other === null);
+      } else if (obj.constructor === String) {
+        return obj === other;
+      } else if (typeof(obj) !== "object") {
+        return obj === other;
+      } else if ("equals" in obj) {
+        return obj.equals.call(obj, other);
+      } else {
+        return obj === other;
+      }
+    }
+
+    function HashMap() {
+      if (arguments.length === 1 && arguments[0].constructor === HashMap) {
+        return arguments[0].clone();
+      }
+
+      var initialCapacity = arguments.length > 0 ? arguments[0] : 16;
+      var loadFactor = arguments.length > 1 ? arguments[1] : 0.75;
+      var buckets = new Array(initialCapacity);
+      var count = 0;
+      var hashMap = this;
+
+      function ensureLoad() {
+        if (count <= loadFactor * buckets.length) {
+          return;
+        }
+        var allEntries = [];
+        for (var i = 0; i < buckets.length; ++i) {
+          if (buckets[i] !== undef) {
+            allEntries = allEntries.concat(buckets[i]);
+          }
+        }
+        buckets = new Array(buckets.length * 2);
+        for (var j = 0; j < allEntries.length; ++j) {
+          var index = virtHashCode(allEntries[j].key) % buckets.length;
+          var bucket = buckets[index];
+          if (bucket === undef) {
+            buckets[index] = bucket = [];
+          }
+          bucket.push(allEntries[j]);
+        }
+      }
+
+      function Iterator(conversion, removeItem) {
+        var bucketIndex = 0;
+        var itemIndex = -1;
+        var endOfBuckets = false;
+
+        function findNext() {
+          while (!endOfBuckets) {
+            ++itemIndex;
+            if (bucketIndex >= buckets.length) {
+              endOfBuckets = true;
+            } else if (buckets[bucketIndex] === undef || itemIndex >= buckets[bucketIndex].length) {
+              itemIndex = -1;
+              ++bucketIndex;
+            } else {
+              return;
+            }
+          }
+        }
+
+        this.hasNext = function() {
+          return !endOfBuckets;
+        };
+
+        this.next = function() {
+          var result = conversion(buckets[bucketIndex][itemIndex]);
+          findNext();
+          return result;
+        };
+
+        this.remove = function() {
+          removeItem(this.next());
+          --itemIndex;
+        };
+
+        findNext();
+      }
+
+      function Set(conversion, isIn, removeItem) {
+        this.clear = function() {
+          hashMap.clear();
+        };
+
+        this.contains = function(o) {
+          return isIn(o);
+        };
+
+        this.containsAll = function(o) {
+          var it = o.iterator();
+          while (it.hasNext()) {
+            if (!this.contains(it.next())) {
+              return false;
+            }
+          }
+          return true;
+        };
+
+        this.isEmpty = function() {
+          return hashMap.isEmpty();
+        };
+
+        this.iterator = function() {
+          return new Iterator(conversion, removeItem);
+        };
+
+        this.remove = function(o) {
+          if (this.contains(o)) {
+            removeItem(o);
+            return true;
+          }
+          return false;
+        };
+
+        this.removeAll = function(c) {
+          var it = c.iterator();
+          var changed = false;
+          while (it.hasNext()) {
+            var item = it.next();
+            if (this.contains(item)) {
+              removeItem(item);
+              changed = true;
+            }
+          }
+          return true;
+        };
+
+        this.retainAll = function(c) {
+          var it = this.iterator();
+          var toRemove = [];
+          while (it.hasNext()) {
+            var entry = it.next();
+            if (!c.contains(entry)) {
+              toRemove.push(entry);
+            }
+          }
+          for (var i = 0; i < toRemove.length; ++i) {
+            removeItem(toRemove[i]);
+          }
+          return toRemove.length > 0;
+        };
+
+        this.size = function() {
+          return hashMap.size();
+        };
+
+        this.toArray = function() {
+          var result = new ArrayList(0);
+          var it = this.iterator();
+          while (it.hasNext()) {
+            result.push(it.next());
+          }
+          return result;
+        };
+      }
+
+      function Entry(pair) {
+        this._isIn = function(map) {
+          return map === hashMap && (pair.removed === undef);
+        };
+
+        this.equals = function(o) {
+          return virtEquals(pair.key, o.getKey());
+        };
+
+        this.getKey = function() {
+          return pair.key;
+        };
+
+        this.getValue = function() {
+          return pair.value;
+        };
+
+        this.hashCode = function(o) {
+          return virtHashCode(pair.key);
+        };
+
+        this.setValue = function(value) {
+          var old = pair.value;
+          pair.value = value;
+          return old;
+        };
+      }
+
+      this.clear = function() {
+        count = 0;
+        buckets = new Array(initialCapacity);
+      };
+
+      this.clone = function() {
+        var map = new HashMap();
+        map.putAll(this);
+        return map;
+      };
+
+      this.containsKey = function(key) {
+        var index = virtHashCode(key) % buckets.length;
+        var bucket = buckets[index];
+        if (bucket === undef) {
+          return false;
+        }
+        for (var i = 0; i < bucket.length; ++i) {
+          if (virtEquals(bucket[i].key, key)) {
+            return true;
+          }
+        }
+        return false;
+      };
+
+      this.containsValue = function(value) {
+        for (var i = 0; i < buckets.length; ++i) {
+          var bucket = buckets[i];
+          if (bucket === undef) {
+            continue;
+          }
+          for (var j = 0; j < bucket.length; ++j) {
+            if (virtEquals(bucket[j].value, value)) {
+              return true;
+            }
+          }
+        }
+        return false;
+      };
+
+      this.entrySet = function() {
+        return new Set(
+
+        function(pair) {
+          return new Entry(pair);
+        },
+
+        function(pair) {
+          return pair.constructor === Entry && pair._isIn(hashMap);
+        },
+
+        function(pair) {
+          return hashMap.remove(pair.getKey());
+        });
+      };
+
+      this.get = function(key) {
+        var index = virtHashCode(key) % buckets.length;
+        var bucket = buckets[index];
+        if (bucket === undef) {
+          return null;
+        }
+        for (var i = 0; i < bucket.length; ++i) {
+          if (virtEquals(bucket[i].key, key)) {
+            return bucket[i].value;
+          }
+        }
+        return null;
+      };
+
+      this.isEmpty = function() {
+        return count === 0;
+      };
+
+      this.keySet = function() {
+        return new Set(
+
+        function(pair) {
+          return pair.key;
+        },
+
+        function(key) {
+          return hashMap.containsKey(key);
+        },
+
+        function(key) {
+          return hashMap.remove(key);
+        });
+      };
+
+      this.put = function(key, value) {
+        var index = virtHashCode(key) % buckets.length;
+        var bucket = buckets[index];
+        if (bucket === undef) {
+          ++count;
+          buckets[index] = [{
+            key: key,
+            value: value
+          }];
+          ensureLoad();
+          return null;
+        }
+        for (var i = 0; i < bucket.length; ++i) {
+          if (virtEquals(bucket[i].key, key)) {
+            var previous = bucket[i].value;
+            bucket[i].value = value;
+            return previous;
+          }
+        }
+        ++count;
+        bucket.push({
+          key: key,
+          value: value
+        });
+        ensureLoad();
+        return null;
+      };
+
+      this.putAll = function(m) {
+        var it = m.entrySet().iterator();
+        while (it.hasNext()) {
+          var entry = it.next();
+          this.put(entry.getKey(), entry.getValue());
+        }
+      };
+
+      this.remove = function(key) {
+        var index = virtHashCode(key) % buckets.length;
+        var bucket = buckets[index];
+        if (bucket === undef) {
+          return null;
+        }
+        for (var i = 0; i < bucket.length; ++i) {
+          if (virtEquals(bucket[i].key, key)) {
+            --count;
+            var previous = bucket[i].value;
+            bucket[i].removed = true;
+            if (bucket.length > 1) {
+              bucket.splice(i, 1);
+            } else {
+              buckets[index] = undef;
+            }
+            return previous;
+          }
+        }
+        return null;
+      };
+
+      this.size = function() {
+        return count;
+      };
+
+      this.values = function() {
+        var result = new ArrayList(0);
+        var it = this.entrySet().iterator();
+        while (it.hasNext()) {
+          var entry = it.next();
+          result.push(entry.getValue());
+        }
+        return result;
+      };
+    }
+
+    return HashMap;
+  }());
+
+  var PVector = (function() {
+    function PVector(x, y, z) {
+      this.x = x || 0;
+      this.y = y || 0;
+      this.z = z || 0;
+    }
+
+    function createPVectorMethod(method) {
+      return function(v1, v2) {
+        var v = v1.get();
+        v[method](v2);
+        return v;
+      };
+    }
+
+    function createSimplePVectorMethod(method) {
+      return function(v1, v2) {
+        return v1[method](v2);
+      };
+    }
+
+    var simplePVMethods = "dist dot cross".split(" ");
+    var method = simplePVMethods.length;
+
+    PVector.angleBetween = function(v1, v2) {
+      return Math.acos(v1.dot(v2) / (v1.mag() * v2.mag()));
+    };
+
+    // Common vector operations for PVector
+    PVector.prototype = {
+      set: function(v, y, z) {
+        if (arguments.length === 1) {
+          this.set(v.x || v[0], v.y || v[1], v.z || v[2]);
+        } else {
+          this.x = v;
+          this.y = y;
+          this.z = z;
+        }
+      },
+      get: function() {
+        return new PVector(this.x, this.y, this.z);
+      },
+      mag: function() {
+        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+      },
+      add: function(v, y, z) {
+        if (arguments.length === 3) {
+          this.x += v;
+          this.y += y;
+          this.z += z;
+        } else if (arguments.length === 1) {
+          this.x += v.x;
+          this.y += v.y;
+          this.z += v.z;
+        }
+      },
+      sub: function(v, y, z) {
+        if (arguments.length === 3) {
+          this.x -= v;
+          this.y -= y;
+          this.z -= z;
+        } else if (arguments.length === 1) {
+          this.x -= v.x;
+          this.y -= v.y;
+          this.z -= v.z;
+        }
+      },
+      mult: function(v) {
+        if (typeof v === 'number') {
+          this.x *= v;
+          this.y *= v;
+          this.z *= v;
+        } else if (typeof v === 'object') {
+          this.x *= v.x;
+          this.y *= v.y;
+          this.z *= v.z;
+        }
+      },
+      div: function(v) {
+        if (typeof v === 'number') {
+          this.x /= v;
+          this.y /= v;
+          this.z /= v;
+        } else if (typeof v === 'object') {
+          this.x /= v.x;
+          this.y /= v.y;
+          this.z /= v.z;
+        }
+      },
+      dist: function(v) {
+        var dx = this.x - v.x,
+            dy = this.y - v.y,
+            dz = this.z - v.z;
+        return Math.sqrt(dx * dx + dy * dy + dz * dz);
+      },
+      dot: function(v, y, z) {
+        if (arguments.length === 3) {
+          return (this.x * v + this.y * y + this.z * z);
+        } else if (arguments.length === 1) {
+          return (this.x * v.x + this.y * v.y + this.z * v.z);
+        }
+      },
+      cross: function(v) {
+        return new PVector(this.y * v.z - v.y * this.z,
+                           this.z * v.x - v.z * this.x,
+                           this.x * v.y - v.x * this.y);
+      },
+      normalize: function() {
+        var m = this.mag();
+        if (m > 0) {
+          this.div(m);
+        }
+      },
+      limit: function(high) {
+        if (this.mag() > high) {
+          this.normalize();
+          this.mult(high);
+        }
+      },
+      heading2D: function() {
+        return (-Math.atan2(-this.y, this.x));
+      },
+      toString: function() {
+        return "[" + this.x + ", " + this.y + ", " + this.z + "]";
+      },
+      array: function() {
+        return [this.x, this.y, this.z];
+      }
+    };
+
+    while (method--) {
+      PVector[simplePVMethods[method]] = createSimplePVectorMethod(simplePVMethods[method]);
+    }
+
+    for (method in PVector.prototype) {
+      if (PVector.prototype.hasOwnProperty(method) && !PVector.hasOwnProperty(method)) {
+        PVector[method] = createPVectorMethod(method);
+      }
+    }
+
+    return PVector;
+  }());
+
+  var Processing = this.Processing = function Processing(curElement, aCode) {
+    var p = this;
+
+    // Include Package Classes -- do this differently in the future.
+    p.ArrayList   = ArrayList;
+    p.HashMap     = HashMap;
+    p.PVector     = PVector;
+    //p.PImage    = PImage;     // TODO
+    //p.PShape    = PShape;     // TODO
+    //p.PShapeSVG = PShapeSVG;  // TODO
+
+    // PJS specific (non-p5) methods and properties to externalize
+    p.externals = {
+      canvas:  curElement,
+      context: undef,
+      sketch:  undef,
+      onblur:  function() {},
+      onfocus: function() {}
+    };
+
+    p.name            = 'Processing.js Instance'; // Set Processing defaults / environment variables
+    p.use3DContext    = false; // default '2d' canvas context
+
+    p.focused         = true;
+    p.breakShape      = false;
+
+    // Glyph path storage for textFonts
+    p.glyphTable      = {};
+
+    // Global vars for tracking mouse position
+    p.pmouseX         = 0;
+    p.pmouseY         = 0;
+    p.mouseX          = 0;
+    p.mouseY          = 0;
+    p.mouseButton     = 0;
+    p.mouseScroll     = 0;
+
+    // Undefined event handlers to be replaced by user when needed
+    p.mouseClicked    = undef;
+    p.mouseDragged    = undef;
+    p.mouseMoved      = undef;
+    p.mousePressed    = undef;
+    p.mouseReleased   = undef;
+    p.mouseScrolled   = undef;
+    p.key             = undef;
+    p.keyCode         = undef;
+    p.keyPressed      = undef;
+    p.keyReleased     = undef;
+    p.keyTyped        = undef;
+    p.draw            = undef;
+    p.setup           = undef;
+
+    // Remapped vars
+    p.__mousePressed  = false;
+    p.__keyPressed    = false;
+    p.__frameRate     = 0;
+
+    // The current animation frame
+    p.frameCount      = 0;
+
+    // The height/width of the canvas
+    p.width           = curElement.width  - 0;
+    p.height          = curElement.height - 0;
+
+    p.defineProperty = function(obj, name, desc) {
+      if("defineProperty" in Object) {
+        Object.defineProperty(obj, name, desc);
+      } else {
+        if (desc.hasOwnProperty("get")) {
+          obj.__defineGetter__(name, desc.get);
+        }
+        if (desc.hasOwnProperty("set")) {
+          obj.__defineSetter__(name, desc.set);
+        }
+      }
+    };
+
+    // "Private" variables used to maintain state
+    var curContext,
+        curSketch,
+        online = true,
+        doFill = true,
+        fillStyle = [1.0, 1.0, 1.0, 1.0],
+        currentFillColor = 0xFFFFFFFF,
+        isFillDirty = true,
+        doStroke = true,
+        strokeStyle = [0.8, 0.8, 0.8, 1.0],
+        currentStrokeColor = 0xFFFDFDFD,
+        isStrokeDirty = true,
+        lineWidth = 1,
+        loopStarted = false,
+        doLoop = true,
+        looping = 0,
+        curRectMode = PConstants.CORNER,
+        curEllipseMode = PConstants.CENTER,
+        normalX = 0,
+        normalY = 0,
+        normalZ = 0,
+        normalMode = PConstants.NORMAL_MODE_AUTO,
+        inDraw = false,
+        curFrameRate = 60,
+        curCursor = PConstants.ARROW,
+        oldCursor = curElement.style.cursor,
+        curMsPerFrame = 1,
+        curShape = PConstants.POLYGON,
+        curShapeCount = 0,
+        curvePoints = [],
+        curTightness = 0,
+        curveDet = 20,
+        curveInited = false,
+        bezDetail = 20,
+        colorModeA = 255,
+        colorModeX = 255,
+        colorModeY = 255,
+        colorModeZ = 255,
+        pathOpen = false,
+        mouseDragging = false,
+        curColorMode = PConstants.RGB,
+        curTint = function() {},
+        curTextSize = 12,
+        curTextFont = "Arial",
+        getLoaded = false,
+        start = new Date().getTime(),
+        timeSinceLastFPS = start,
+        framesSinceLastFPS = 0,
+        textcanvas,
+        curveBasisMatrix,
+        curveToBezierMatrix,
+        curveDrawMatrix,
+        bezierDrawMatrix,
+        bezierBasisInverse,
+        bezierBasisMatrix,
+        // Shaders
+        programObject3D,
+        programObject2D,
+        programObjectUnlitShape,
+        boxBuffer,
+        boxNormBuffer,
+        boxOutlineBuffer,
+        rectBuffer,
+        rectNormBuffer,
+        sphereBuffer,
+        lineBuffer,
+        fillBuffer,
+        fillColorBuffer,
+        strokeColorBuffer,
+        pointBuffer,
+        shapeTexVBO,
+        canTex,   // texture for createGraphics
+        curTexture = {width:0,height:0},
+        curTextureMode = PConstants.IMAGE,
+        usingTexture = false,
+        textBuffer,
+        textureBuffer,
+        indexBuffer,
+        // Text alignment
+        horizontalTextAlignment = PConstants.LEFT,
+        verticalTextAlignment = PConstants.BASELINE,
+        baselineOffset = 0.2, // percent
+        tMode = PConstants.MODEL,
+        // Pixels cache
+        originalContext,
+        proxyContext = null,
+        isContextReplaced = false,
+        setPixelsCached,
+        maxPixelsCached = 1000,
+        codedKeys = [PConstants.SHIFT, PConstants.CONTROL, PConstants.ALT, PConstants.UP, PConstants.RIGHT, PConstants.DOWN, PConstants.LEFT];
+
+    // Get padding and border style widths for mouse offsets
+    var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
+
+    if (document.defaultView && document.defaultView.getComputedStyle) {
+      stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingLeft'], 10)      || 0;
+      stylePaddingTop  = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingTop'], 10)       || 0;
+      styleBorderLeft  = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderLeftWidth'], 10)  || 0;
+      styleBorderTop   = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderTopWidth'], 10)   || 0;
+    }
+
+    // User can only have MAX_LIGHTS lights
+    var lightCount = 0;
+
+    //sphere stuff
+    var sphereDetailV = 0,
+        sphereDetailU = 0,
+        sphereX = [],
+        sphereY = [],
+        sphereZ = [],
+        sinLUT = new Array(PConstants.SINCOS_LENGTH),
+        cosLUT = new Array(PConstants.SINCOS_LENGTH),
+        sphereVerts,
+        sphereNorms;
+
+    // Camera defaults and settings
+    var cam,
+        cameraInv,
+        forwardTransform,
+        reverseTransform,
+        modelView,
+        modelViewInv,
+        userMatrixStack,
+        inverseCopy,
+        projection,
+        manipulatingCamera = false,
+        frustumMode = false,
+        cameraFOV = 60 * (Math.PI / 180),
+        cameraX = curElement.width / 2,
+        cameraY = curElement.height / 2,
+        cameraZ = cameraY / Math.tan(cameraFOV / 2),
+        cameraNear = cameraZ / 10,
+        cameraFar = cameraZ * 10,
+        cameraAspect = curElement.width / curElement.height;
+
+    var vertArray = [],
+        curveVertArray = [],
+        curveVertCount = 0,
+        isCurve = false,
+        isBezier = false,
+        firstVert = true;
+
+    //PShape stuff
+    var curShapeMode = PConstants.CORNER;
+
+    var colors = {
+      aliceblue:            "#f0f8ff",
+      antiquewhite:         "#faebd7",
+      aqua:                 "#00ffff",
+      aquamarine:           "#7fffd4",
+      azure:                "#f0ffff",
+      beige:                "#f5f5dc",
+      bisque:               "#ffe4c4",
+      black:                "#000000",
+      blanchedalmond:       "#ffebcd",
+      blue:                 "#0000ff",
+      blueviolet:           "#8a2be2",
+      brown:                "#a52a2a",
+      burlywood:            "#deb887",
+      cadetblue:            "#5f9ea0",
+      chartreuse:           "#7fff00",
+      chocolate:            "#d2691e",
+      coral:                "#ff7f50",
+      cornflowerblue:       "#6495ed",
+      cornsilk:             "#fff8dc",
+      crimson:              "#dc143c",
+      cyan:                 "#00ffff",
+      darkblue:             "#00008b",
+      darkcyan:             "#008b8b",
+      darkgoldenrod:        "#b8860b",
+      darkgray:             "#a9a9a9",
+      darkgreen:            "#006400",
+      darkkhaki:            "#bdb76b",
+      darkmagenta:          "#8b008b",
+      darkolivegreen:       "#556b2f",
+      darkorange:           "#ff8c00",
+      darkorchid:           "#9932cc",
+      darkred:              "#8b0000",
+      darksalmon:           "#e9967a",
+      darkseagreen:         "#8fbc8f",
+      darkslateblue:        "#483d8b",
+      darkslategray:        "#2f4f4f",
+      darkturquoise:        "#00ced1",
+      darkviolet:           "#9400d3",
+      deeppink:             "#ff1493",
+      deepskyblue:          "#00bfff",
+      dimgray:              "#696969",
+      dodgerblue:           "#1e90ff",
+      firebrick:            "#b22222",
+      floralwhite:          "#fffaf0",
+      forestgreen:          "#228b22",
+      fuchsia:              "#ff00ff",
+      gainsboro:            "#dcdcdc",
+      ghostwhite:           "#f8f8ff",
+      gold:                 "#ffd700",
+      goldenrod:            "#daa520",
+      gray:                 "#808080",
+      green:                "#008000",
+      greenyellow:          "#adff2f",
+      honeydew:             "#f0fff0",
+      hotpink:              "#ff69b4",
+      indianred:            "#cd5c5c",
+      indigo:               "#4b0082",
+      ivory:                "#fffff0",
+      khaki:                "#f0e68c",
+      lavender:             "#e6e6fa",
+      lavenderblush:        "#fff0f5",
+      lawngreen:            "#7cfc00",
+      lemonchiffon:         "#fffacd",
+      lightblue:            "#add8e6",
+      lightcoral:           "#f08080",
+      lightcyan:            "#e0ffff",
+      lightgoldenrodyellow: "#fafad2",
+      lightgrey:            "#d3d3d3",
+      lightgreen:           "#90ee90",
+      lightpink:            "#ffb6c1",
+      lightsalmon:          "#ffa07a",
+      lightseagreen:        "#20b2aa",
+      lightskyblue:         "#87cefa",
+      lightslategray:       "#778899",
+      lightsteelblue:       "#b0c4de",
+      lightyellow:          "#ffffe0",
+      lime:                 "#00ff00",
+      limegreen:            "#32cd32",
+      linen:                "#faf0e6",
+      magenta:              "#ff00ff",
+      maroon:               "#800000",
+      mediumaquamarine:     "#66cdaa",
+      mediumblue:           "#0000cd",
+      mediumorchid:         "#ba55d3",
+      mediumpurple:         "#9370d8",
+      mediumseagreen:       "#3cb371",
+      mediumslateblue:      "#7b68ee",
+      mediumspringgreen:    "#00fa9a",
+      mediumturquoise:      "#48d1cc",
+      mediumvioletred:      "#c71585",
+      midnightblue:         "#191970",
+      mintcream:            "#f5fffa",
+      mistyrose:            "#ffe4e1",
+      moccasin:             "#ffe4b5",
+      navajowhite:          "#ffdead",
+      navy:                 "#000080",
+      oldlace:              "#fdf5e6",
+      olive:                "#808000",
+      olivedrab:            "#6b8e23",
+      orange:               "#ffa500",
+      orangered:            "#ff4500",
+      orchid:               "#da70d6",
+      palegoldenrod:        "#eee8aa",
+      palegreen:            "#98fb98",
+      paleturquoise:        "#afeeee",
+      palevioletred:        "#d87093",
+      papayawhip:           "#ffefd5",
+      peachpuff:            "#ffdab9",
+      peru:                 "#cd853f",
+      pink:                 "#ffc0cb",
+      plum:                 "#dda0dd",
+      powderblue:           "#b0e0e6",
+      purple:               "#800080",
+      red:                  "#ff0000",
+      rosybrown:            "#bc8f8f",
+      royalblue:            "#4169e1",
+      saddlebrown:          "#8b4513",
+      salmon:               "#fa8072",
+      sandybrown:           "#f4a460",
+      seagreen:             "#2e8b57",
+      seashell:             "#fff5ee",
+      sienna:               "#a0522d",
+      silver:               "#c0c0c0",
+      skyblue:              "#87ceeb",
+      slateblue:            "#6a5acd",
+      slategray:            "#708090",
+      snow:                 "#fffafa",
+      springgreen:          "#00ff7f",
+      steelblue:            "#4682b4",
+      tan:                  "#d2b48c",
+      teal:                 "#008080",
+      thistle:              "#d8bfd8",
+      tomato:               "#ff6347",
+      turquoise:            "#40e0d0",
+      violet:               "#ee82ee",
+      wheat:                "#f5deb3",
+      white:                "#ffffff",
+      whitesmoke:           "#f5f5f5",
+      yellow:               "#ffff00",
+      yellowgreen:          "#9acd32"
+    };
+
+    // Stores states for pushStyle() and popStyle().
+    var styleArray = new Array(0);
+
+    // Vertices are specified in a counter-clockwise order
+    // triangles are in this order: back, front, right, bottom, left, top
+    var boxVerts = new Float32Array([
+       0.5,  0.5, -0.5,  0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5,
+      -0.5,  0.5, -0.5,  0.5,  0.5, -0.5,  0.5,  0.5,  0.5, -0.5,  0.5,  0.5,
+      -0.5, -0.5,  0.5, -0.5, -0.5,  0.5,  0.5, -0.5,  0.5,  0.5,  0.5,  0.5,
+       0.5,  0.5, -0.5,  0.5,  0.5,  0.5,  0.5, -0.5,  0.5,  0.5, -0.5,  0.5,
+       0.5, -0.5, -0.5,  0.5,  0.5, -0.5,  0.5, -0.5, -0.5,  0.5, -0.5,  0.5,
+      -0.5, -0.5,  0.5, -0.5, -0.5,  0.5, -0.5, -0.5, -0.5,  0.5, -0.5, -0.5,
+      -0.5, -0.5, -0.5, -0.5, -0.5,  0.5, -0.5,  0.5,  0.5, -0.5,  0.5,  0.5,
+      -0.5,  0.5, -0.5, -0.5, -0.5, -0.5,  0.5,  0.5,  0.5,  0.5,  0.5, -0.5,
+      -0.5,  0.5, -0.5, -0.5,  0.5, -0.5, -0.5,  0.5,  0.5,  0.5,  0.5,  0.5]);
+
+    var boxOutlineVerts = new Float32Array([
+       0.5,  0.5,  0.5,  0.5, -0.5,  0.5,  0.5,  0.5, -0.5,  0.5, -0.5, -0.5,
+      -0.5,  0.5, -0.5, -0.5, -0.5, -0.5, -0.5,  0.5,  0.5, -0.5, -0.5,  0.5,
+       0.5,  0.5,  0.5,  0.5,  0.5, -0.5,  0.5,  0.5, -0.5, -0.5,  0.5, -0.5,
+      -0.5,  0.5, -0.5, -0.5,  0.5,  0.5, -0.5,  0.5,  0.5,  0.5,  0.5,  0.5,
+       0.5, -0.5,  0.5,  0.5, -0.5, -0.5,  0.5, -0.5, -0.5, -0.5, -0.5, -0.5,
+      -0.5, -0.5, -0.5, -0.5, -0.5,  0.5, -0.5, -0.5,  0.5,  0.5, -0.5,  0.5]);
+
+    var boxNorms = new Float32Array([
+       0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,
+       0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,
+       1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,
+       0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,
+      -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0,
+       0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0]);
+
+    // These verts are used for the fill and stroke using TRIANGLE_FAN and LINE_LOOP
+    var rectVerts = new Float32Array([0,0,0, 0,1,0, 1,1,0, 1,0,0]);
+
+    var rectNorms = new Float32Array([0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1]);
+
+    // Vertex shader for points and lines
+    var vShaderSrcUnlitShape =
+      "varying vec4 frontColor;" +
+
+      "attribute vec3 aVertex;" +
+      "attribute vec4 aColor;" +
+
+      "uniform mat4 uView;" +
+      "uniform mat4 uProjection;" +
+
+      "void main(void) {" +
+      "  frontColor = aColor;" +
+      "  gl_Position = uProjection * uView * vec4(aVertex, 1.0);" +
+      "}";
+
+    var fShaderSrcUnlitShape =
+      "#ifdef GL_ES\n" +
+      "precision highp float;\n" +
+      "#endif\n" +
+
+      "varying vec4 frontColor;" +
+
+      "void main(void){" +
+      "  gl_FragColor = frontColor;" +
+      "}";
+
+    // Vertex shader for points and lines
+    var vertexShaderSource2D =
+      "varying vec4 frontColor;" +
+
+      "attribute vec3 Vertex;" +
+      "attribute vec2 aTextureCoord;" +
+      "uniform vec4 color;" +
+
+      "uniform mat4 model;" +
+      "uniform mat4 view;" +
+      "uniform mat4 projection;" +
+      "uniform float pointSize;" +
+      "varying vec2 vTextureCoord;"+
+
+      "void main(void) {" +
+      "  gl_PointSize = pointSize;" +
+      "  frontColor = color;" +
+      "  gl_Position = projection * view * model * vec4(Vertex, 1.0);" +
+      "  vTextureCoord = aTextureCoord;" +
+      "}";
+
+    var fragmentShaderSource2D =
+      "#ifdef GL_ES\n" +
+      "precision highp float;\n" +
+      "#endif\n" +
+
+      "varying vec4 frontColor;" +
+      "varying vec2 vTextureCoord;"+
+
+      "uniform sampler2D uSampler;"+
+      "uniform int picktype;"+
+
+      "void main(void){" +
+      "  if(picktype == 0){"+
+      "    gl_FragColor = frontColor;" +
+      "  }" +
+      "  else if(picktype == 1){"+
+      "    float alpha = texture2D(uSampler, vTextureCoord).a;"+
+      "    gl_FragColor = vec4(frontColor.rgb*alpha, alpha);\n"+
+      "  }"+
+      "}";
+
+    // Vertex shader for boxes and spheres
+    var vertexShaderSource3D =
+      "varying vec4 frontColor;" +
+
+      "attribute vec3 Vertex;" +
+      "attribute vec3 Normal;" +
+      "attribute vec4 aColor;" +
+      "attribute vec2 aTexture;" +
+      "varying   vec2 vTexture;" +
+
+      "uniform vec4 color;" +
+
+      "uniform bool usingMat;" +
+      "uniform vec3 specular;" +
+      "uniform vec3 mat_emissive;" +
+      "uniform vec3 mat_ambient;" +
+      "uniform vec3 mat_specular;" +
+      "uniform float shininess;" +
+
+      "uniform mat4 model;" +
+      "uniform mat4 view;" +
+      "uniform mat4 projection;" +
+      "uniform mat4 normalTransform;" +
+
+      "uniform int lightCount;" +
+      "uniform vec3 falloff;" +
+
+      "struct Light {" +
+      "  bool dummy;" +
+      "  int type;" +
+      "  vec3 color;" +
+      "  vec3 position;" +
+      "  vec3 direction;" +
+      "  float angle;" +
+      "  vec3 halfVector;" +
+      "  float concentration;" +
+      "};" +
+      "uniform Light lights[8];" +
+
+      "void AmbientLight( inout vec3 totalAmbient, in vec3 ecPos, in Light light ) {" +
+      // Get the vector from the light to the vertex
+      // Get the distance from the current vector to the light position
+      "  float d = length( light.position - ecPos );" +
+      "  float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" + "  totalAmbient += light.color * attenuation;" +
+      "}" +
+
+      "void DirectionalLight( inout vec3 col, in vec3 ecPos, inout vec3 spec, in vec3 vertNormal, in Light light ) {" +
+      "  float powerfactor = 0.0;" +
+      "  float nDotVP = max(0.0, dot( vertNormal, light.position ));" +
+      "  float nDotVH = max(0.0, dot( vertNormal, normalize( light.position-ecPos )));" +
+
+      "  if( nDotVP != 0.0 ){" +
+      "    powerfactor = pow( nDotVH, shininess );" +
+      "  }" +
+
+      "  col += light.color * nDotVP;" +
+      "  spec += specular * powerfactor;" +
+      "}" +
+
+      "void PointLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in vec3 eye, in Light light ) {" +
+      "  float powerfactor;" +
+
+      // Get the vector from the light to the vertex
+      "   vec3 VP = light.position - ecPos;" +
+
+      // Get the distance from the current vector to the light position
+      "  float d = length( VP ); " +
+
+      // Normalize the light ray so it can be used in the dot product operation.
+      "  VP = normalize( VP );" +
+
+      "  float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" +
+
+      "  float nDotVP = max( 0.0, dot( vertNormal, VP ));" +
+      "  vec3 halfVector = normalize( VP + eye );" +
+      "  float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" +
+
+      "  if( nDotVP == 0.0) {" +
+      "    powerfactor = 0.0;" +
+      "  }" +
+      "  else{" +
+      "    powerfactor = pow( nDotHV, shininess );" +
+      "  }" +
+
+      "  spec += specular * powerfactor * attenuation;" +
+      "  col += light.color * nDotVP * attenuation;" +
+      "}" +
+
+      /*
+      */
+      "void SpotLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in vec3 eye, in Light light ) {" +
+      "  float spotAttenuation;" +
+      "  float powerfactor;" +
+
+      // calculate the vector from the current vertex to the light.
+      "  vec3 VP = light.position - ecPos; " +
+      "  vec3 ldir = normalize( light.direction );" +
+
+      // get the distance from the spotlight and the vertex
+      "  float d = length( VP );" +
+      "  VP = normalize( VP );" +
+
+      "  float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ) );" +
+
+      // dot product of the vector from vertex to light and light direction.
+      "  float spotDot = dot( VP, ldir );" +
+
+      // if the vertex falls inside the cone
+      "  if( spotDot < cos( light.angle ) ) {" +
+      "    spotAttenuation = pow( spotDot, light.concentration );" +
+      "  }" +
+      "  else{" +
+      "    spotAttenuation = 1.0;" +
+      "  }" +
+      "  attenuation *= spotAttenuation;" +
+
+      "  float nDotVP = max( 0.0, dot( vertNormal, VP ));" +
+      "  vec3 halfVector = normalize( VP + eye );" +
+      "  float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" +
+
+      "  if( nDotVP == 0.0 ) {" +
+      "    powerfactor = 0.0;" +
+      "  }" +
+      "  else {" +
+      "    powerfactor = pow( nDotHV, shininess );" +
+      "  }" +
+
+      "  spec += specular * powerfactor * attenuation;" +
+      "  col += light.color * nDotVP * attenuation;" +
+      "}" +
+
+      "void main(void) {" +
+      "  vec3 finalAmbient = vec3( 0.0, 0.0, 0.0 );" +
+      "  vec3 finalDiffuse = vec3( 0.0, 0.0, 0.0 );" +
+      "  vec3 finalSpecular = vec3( 0.0, 0.0, 0.0 );" +
+
+      "  vec4 col = color;" +
+      "  if(color[0] == -1.0){" +
+      "    col = aColor;" +
+      "  }" +
+
+      "  vec3 norm = vec3( normalTransform * vec4( Normal, 0.0 ) );" +
+
+      "  vec4 ecPos4 = view * model * vec4(Vertex,1.0);" +
+      "  vec3 ecPos = (vec3(ecPos4))/ecPos4.w;" +
+      "  vec3 eye = vec3( 0.0, 0.0, 1.0 );" +
+
+      // If there were no lights this draw call, just use the
+      // assigned fill color of the shape and the specular value
+      "  if( lightCount == 0 ) {" +
+      "    frontColor = col + vec4(mat_specular,1.0);" +
+      "  }" +
+      "  else {" +
+      "    for( int i = 0; i < lightCount; i++ ) {" +
+      "      if( lights[i].type == 0 ) {" +
+      "        AmbientLight( finalAmbient, ecPos, lights[i] );" +
+      "      }" +
+      "      else if( lights[i].type == 1 ) {" +
+      "        DirectionalLight( finalDiffuse,ecPos, finalSpecular, norm, lights[i] );" +
+      "      }" +
+      "      else if( lights[i].type == 2 ) {" +
+      "        PointLight( finalDiffuse, finalSpecular, norm, ecPos, eye, lights[i] );" +
+      "      }" +
+      "      else if( lights[i].type == 3 ) {" +
+      "        SpotLight( finalDiffuse, finalSpecular, norm, ecPos, eye, lights[i] );" +
+      "      }" +
+      "    }" +
+
+      "   if( usingMat == false ) {" +
+      "    frontColor = vec4(  " +
+      "      vec3(col) * finalAmbient +" +
+      "      vec3(col) * finalDiffuse +" +
+      "      vec3(col) * finalSpecular," +
+      "      col[3] );" +
+      "   }" +
+      "   else{" +
+      "     frontColor = vec4( " +
+      "       mat_emissive + " +
+      "       (vec3(col) * mat_ambient * finalAmbient) + " +
+      "       (vec3(col) * finalDiffuse) + " +
+      "       (mat_specular * finalSpecular), " +
+      "       col[3] );" +
+      "    }" +
+      "  }" +
+      "  vTexture.xy = aTexture.xy;" +
+      "  gl_Position = projection * view * model * vec4( Vertex, 1.0 );" +
+      "}";
+
+    var fragmentShaderSource3D =
+      "#ifdef GL_ES\n" +
+      "precision highp float;\n" +
+      "#endif\n" +
+
+      "varying vec4 frontColor;" +
+
+      "uniform sampler2D sampler;" +
+      "uniform bool usingTexture;" +
+      "varying vec2 vTexture;" +
+
+      // In Processing, when a texture is used, the fill color is ignored
+      "void main(void){" +
+      "  if(usingTexture){" +
+      "    gl_FragColor =  vec4(texture2D(sampler, vTexture.xy));" +
+      "  }"+
+      "  else{" +
+      "    gl_FragColor = frontColor;" +
+      "  }" +
+      "}";
+
+    ////////////////////////////////////////////////////////////////////////////
+    // 3D Functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    /*
+      Sets the uniform variable 'varName' to the value specified by 'value'.
+      Before calling this function, make sure the correct program object
+      has been installed as part of the current rendering state.
+
+      On some systems, if the variable exists in the shader but isn't used,
+      the compiler will optimize it out and this function will fail.
+    */
+    function uniformf(programObj, varName, varValue) {
+      var varLocation = curContext.getUniformLocation(programObj, varName);
+      // the variable won't be found if it was optimized out.
+      if (varLocation !== -1) {
+        if (varValue.length === 4) {
+          curContext.uniform4fv(varLocation, varValue);
+        } else if (varValue.length === 3) {
+          curContext.uniform3fv(varLocation, varValue);
+        } else if (varValue.length === 2) {
+          curContext.uniform2fv(varLocation, varValue);
+        } else {
+          curContext.uniform1f(varLocation, varValue);
+        }
+      }
+    }
+
+    function uniformi(programObj, varName, varValue) {
+      var varLocation = curContext.getUniformLocation(programObj, varName);
+      // the variable won't be found if it was optimized out.
+      if (varLocation !== -1) {
+        if (varValue.length === 4) {
+          curContext.uniform4iv(varLocation, varValue);
+        } else if (varValue.length === 3) {
+          curContext.uniform3iv(varLocation, varValue);
+        } else if (varValue.length === 2) {
+          curContext.uniform2iv(varLocation, varValue);
+        } else {
+          curContext.uniform1i(varLocation, varValue);
+        }
+      }
+    }
+
+    function vertexAttribPointer(programObj, varName, size, VBO) {
+      var varLocation = curContext.getAttribLocation(programObj, varName);
+      if (varLocation !== -1) {
+        curContext.bindBuffer(curContext.ARRAY_BUFFER, VBO);
+        curContext.vertexAttribPointer(varLocation, size, curContext.FLOAT, false, 0, 0);
+        curContext.enableVertexAttribArray(varLocation);
+      }
+    }
+
+    function disableVertexAttribPointer(programObj, varName){
+      var varLocation = curContext.getAttribLocation(programObj, varName);
+      if (varLocation !== -1) {
+        curContext.disableVertexAttribArray(varLocation);
+      }
+    }
+
+    function uniformMatrix(programObj, varName, transpose, matrix) {
+      var varLocation = curContext.getUniformLocation(programObj, varName);
+      // the variable won't be found if it was optimized out.
+      if (varLocation !== -1) {
+        if (matrix.length === 16) {
+          curContext.uniformMatrix4fv(varLocation, transpose, matrix);
+        } else if (matrix.length === 9) {
+          curContext.uniformMatrix3fv(varLocation, transpose, matrix);
+        } else {
+          curContext.uniformMatrix2fv(varLocation, transpose, matrix);
+        }
+      }
+    }
+
+    var imageModeCorner = function imageModeCorner(x, y, w, h, whAreSizes) {
+      return {
+        x: x,
+        y: y,
+        w: w,
+        h: h
+      };
+    };
+    var imageModeConvert = imageModeCorner;
+
+    var imageModeCorners = function imageModeCorners(x, y, w, h, whAreSizes) {
+      return {
+        x: x,
+        y: y,
+        w: whAreSizes ? w : w - x,
+        h: whAreSizes ? h : h - y
+      };
+    };
+
+    var imageModeCenter = function imageModeCenter(x, y, w, h, whAreSizes) {
+      return {
+        x: x - w / 2,
+        y: y - h / 2,
+        w: w,
+        h: h
+      };
+    };
+
+    var createProgramObject = function(curContext, vetexShaderSource, fragmentShaderSource) {
+      var vertexShaderObject = curContext.createShader(curContext.VERTEX_SHADER);
+      curContext.shaderSource(vertexShaderObject, vetexShaderSource);
+      curContext.compileShader(vertexShaderObject);
+      if (!curContext.getShaderParameter(vertexShaderObject, curContext.COMPILE_STATUS)) {
+        throw curContext.getShaderInfoLog(vertexShaderObject);
+      }
+
+      var fragmentShaderObject = curContext.createShader(curContext.FRAGMENT_SHADER);
+      curContext.shaderSource(fragmentShaderObject, fragmentShaderSource);
+      curContext.compileShader(fragmentShaderObject);
+      if (!curContext.getShaderParameter(fragmentShaderObject, curContext.COMPILE_STATUS)) {
+        throw curContext.getShaderInfoLog(fragmentShaderObject);
+      }
+
+      var programObject = curContext.createProgram();
+      curContext.attachShader(programObject, vertexShaderObject);
+      curContext.attachShader(programObject, fragmentShaderObject);
+      curContext.linkProgram(programObject);
+      if (!curContext.getProgramParameter(programObject, curContext.LINK_STATUS)) {
+        throw "Error linking shaders.";
+      }
+
+      return programObject;
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Char handling
+    ////////////////////////////////////////////////////////////////////////////
+    var charMap = {};
+
+    var Char = p.Character = function Char(chr) {
+      if (typeof chr === 'string' && chr.length === 1) {
+        this.code = chr.charCodeAt(0);
+      } else {
+        this.code = NaN;
+      }
+
+      return (charMap[this.code] === undef) ? charMap[this.code] = this : charMap[this.code];
+    };
+
+    Char.prototype.toString = function() {
+      return String.fromCharCode(this.code);
+    };
+
+    Char.prototype.valueOf = function() {
+      return this.code;
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // PShape
+    ////////////////////////////////////////////////////////////////////////////
+    var PShape = p.PShape = function(family) {
+      this.family    = family || PConstants.GROUP;
+      this.visible   = true;
+      this.style     = true;
+      this.children  = [];
+      this.nameTable = [];
+      this.params    = [];
+      this.name      = "";
+      this.image     = null;  //type PImage
+      this.matrix    = null;
+      this.kind      = null;
+      this.close     = null;
+      this.width     = null;
+      this.height    = null;
+      this.parent    = null;
+      /* methods */
+      this.isVisible = function(){
+        return this.visible;
+      };
+      this.setVisible = function (visible){
+        this.visible = visible;
+      };
+      this.disableStyle = function(){
+        this.style = false;
+        for(var i = 0; i < this.children.length; i++)
+        {
+          this.children[i].disableStyle();
+        }
+      };
+      this.enableStyle = function(){
+        this.style = true;
+        for(var i = 0; i < this.children.length; i++)
+        {
+          this.children[i].enableStyle();
+        }
+      };
+      this.getFamily = function(){
+        return this.family;
+      };
+      this.getWidth = function(){
+        return this.width;
+      };
+      this.getHeight = function(){
+        return this.height;
+      };
+      this.setName = function(name){
+        this.name = name;
+      };
+      this.getName = function(){
+        return this.name;
+      };
+      this.draw = function(){
+        if (this.visible) {
+          this.pre();
+          this.drawImpl();
+          this.post();
+        }
+      };
+      this.drawImpl = function(){
+        if (this.family === PConstants.GROUP) {
+          this.drawGroup();
+        } else if (this.family === PConstants.PRIMITIVE) {
+          this.drawPrimitive();
+        } else if (this.family === PConstants.GEOMETRY) {
+          this.drawGeometry();
+        } else if (this.family === PConstants.PATH) {
+          this.drawPath();
+        }
+      };
+      this.drawPath = function(){
+        if (this.vertices.length === 0) { return; }
+
+        p.beginShape();
+        var i;
+        if (this.vertexCodes.length === 0) {  // each point is a simple vertex
+          if (this.vertices[0].length === 2) {  // drawing 2D vertices
+            for (i = 0; i < this.vertices.length; i++) {
+              p.vertex(this.vertices[i][0], this.vertices[i][1]);
+            }
+          } else {  // drawing 3D vertices
+            for (i = 0; i < this.vertices.length; i++) {
+              p.vertex(this.vertices[i][0], this.vertices[i][1], this.vertices[i][2]);
+            }
+          }
+        } else {  // coded set of vertices
+          var index = 0;
+          var j;
+          if (this.vertices[0].length === 2) {  // drawing a 2D path
+            for (j = 0; j < this.vertexCodes.length; j++) {
+              switch (this.vertexCodes[j]) {
+              case PConstants.VERTEX:
+                p.vertex(this.vertices[index][0], this.vertices[index][1]);
+                if ( this.vertices[index]["moveTo"] === true) {
+                  vertArray[vertArray.length-1]["moveTo"] = true;
+                } else if ( this.vertices[index]["moveTo"] === false) {
+                  vertArray[vertArray.length-1]["moveTo"] = false;
+                }
+                p.breakShape = false;
+                index++;
+                break;
+              case PConstants.BEZIER_VERTEX:
+                p.bezierVertex(this.vertices[index+0][0], this.vertices[index+0][1],
+                               this.vertices[index+1][0], this.vertices[index+1][1],
+                               this.vertices[index+2][0], this.vertices[index+2][1]);
+                index += 3;
+                break;
+              case PConstants.CURVE_VERTEX:
+                p.curveVertex(this.vertices[index][0], this.vertices[index][1]);
+                index++;
+                break;
+              case PConstants.BREAK:
+                p.breakShape = true;
+                break;
+              }
+            }
+          } else {  // drawing a 3D path
+            for (j = 0; j < this.vertexCodes.length; j++) {
+              switch (this.vertexCodes[j]) {
+                case PConstants.VERTEX:
+                  p.vertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index][2]);
+                  if (this.vertices[index]["moveTo"] === true) {
+                    vertArray[vertArray.length-1]["moveTo"] = true;
+                  } else if (this.vertices[index]["moveTo"] === false) {
+                    vertArray[vertArray.length-1]["moveTo"] = false;
+                  }
+                  p.breakShape = false;
+                  break;
+                case PConstants.BEZIER_VERTEX:
+                  p.bezierVertex(this.vertices[index+0][0], this.vertices[index+0][1], this.vertices[index+0][2],
+                                 this.vertices[index+1][0], this.vertices[index+1][1], this.vertices[index+1][2],
+                                 this.vertices[index+2][0], this.vertices[index+2][1], this.vertices[index+2][2]);
+                  index += 3;
+                  break;
+                case PConstants.CURVE_VERTEX:
+                  p.curveVertex(this.vertices[index][0], this.vertices[index][1], this.vertices[index][2]);
+                  index++;
+                  break;
+                case PConstants.BREAK:
+                  p.breakShape = true;
+                  break;
+              }
+            }
+          }
+        }
+        p.endShape(this.close ? PConstants.CLOSE : PConstants.OPEN);
+      };
+      this.drawGeometry = function() {
+        p.beginShape(this.kind);
+        var i;
+        if (this.style) {
+          for (i = 0; i < this.vertices.length; i++) {
+            p.vertex(this.vertices[i]);
+          }
+        } else {
+          for (i = 0; i < this.vertices.length; i++) {
+            var vert = this.vertices[i];
+            if (vert[2] === 0) {
+              p.vertex(vert[0], vert[1]);
+            } else {
+              p.vertex(vert[0], vert[1], vert[2]);
+            }
+          }
+        }
+        p.endShape();
+      };
+      this.drawGroup = function() {
+        for (var i = 0; i < this.children.length; i++) {
+          this.children[i].draw();
+        }
+      };
+      this.drawPrimitive = function() {
+        switch (this.kind) {
+          case PConstants.POINT:
+            p.point(this.params[0], this.params[1]);
+            break;
+          case PConstants.LINE:
+            if (this.params.length === 4) {  // 2D
+              p.line(this.params[0], this.params[1],
+                     this.params[2], this.params[3]);
+            } else {  // 3D
+              p.line(this.params[0], this.params[1], this.params[2],
+                     this.params[3], this.params[4], this.params[5]);
+            }
+            break;
+          case PConstants.TRIANGLE:
+            p.triangle(this.params[0], this.params[1],
+                       this.params[2], this.params[3],
+                       this.params[4], this.params[5]);
+            break;
+          case PConstants.QUAD:
+            p.quad(this.params[0], this.params[1],
+                   this.params[2], this.params[3],
+                   this.params[4], this.params[5],
+                   this.params[6], this.params[7]);
+            break;
+          case PConstants.RECT:
+            if (this.image !== null) {
+              p.imageMode(PConstants.CORNER);
+              p.image(this.image, this.params[0], this.params[1], this.params[2], this.params[3]);
+            } else {
+              p.rectMode(PConstants.CORNER);
+              p.rect(this.params[0], this.params[1], this.params[2], this.params[3]);
+            }
+            break;
+          case PConstants.ELLIPSE:
+            p.ellipseMode(PConstants.CORNER);
+            p.ellipse(this.params[0], this.params[1], this.params[2], this.params[3]);
+            break;
+          case PConstants.ARC:
+            p.ellipseMode(PConstants.CORNER);
+            p.arc(this.params[0], this.params[1], this.params[2], this.params[3], this.params[4], this.params[5]);
+            break;
+          case PConstants.BOX:
+            if (this.params.length === 1) {
+              p.box(this.params[0]);
+            } else {
+              p.box(this.params[0], this.params[1], this.params[2]);
+            }
+            break;
+          case PConstants.SPHERE:
+            p.sphere(this.params[0]);
+            break;
+        }
+      };
+      this.pre = function() {
+        if (this.matrix) {
+          p.pushMatrix();
+          curContext.transform(this.matrix.elements[0], this.matrix.elements[3], this.matrix.elements[1], this.matrix.elements[4], this.matrix.elements[2], this.matrix.elements[5]);
+          //p.applyMatrix(this.matrix.elements[0],this.matrix.elements[0]);
+        }
+        if (this.style) {
+          p.pushStyle();
+          this.styles();
+        }
+      };
+      this.post = function() {
+        if (this.matrix) {
+          p.popMatrix();
+        }
+        if (this.style) {
+          p.popStyle();
+        }
+      };
+      this.styles = function() {
+        if (this.stroke) {
+          p.stroke(this.strokeColor);
+          p.strokeWeight(this.strokeWeight);
+          p.strokeCap(this.strokeCap);
+          p.strokeJoin(this.strokeJoin);
+        } else {
+          p.noStroke();
+        }
+
+        if (this.fill) {
+          p.fill(this.fillColor);
+
+        } else {
+          p.noFill();
+        }
+      };
+
+      // return the PShape at the specific index from the children array or
+      // return the Phape from a parent shape specified by its name
+      this.getChild = function(child) {
+        if (typeof child === 'number') {
+          return this.children[child];
+        } else {
+          var found,
+              i;
+          if(child === "" || this.name === child){
+            return this;
+          } else {
+            if(this.nameTable.length > 0)
+            {
+              for(i = 0; i < this.nameTable.length || found; i++)
+              {
+                if(this.nameTable[i].getName === child) {
+                  found = this.nameTable[i];
+                }
+              }
+              if (found) { return found; }
+            }
+            for(i = 0; i < this.children.lenth; i++)
+            {
+              found = this.children[i].getChild(child);
+              if(found) { return found; }
+            }
+          }
+          return null;
+        }
+      };
+      this.getChildCount = function () {
+        return this.children.length;
+      };
+      this.addChild = function( child ) {
+        this.children.push(child);
+        child.parent = this;
+        if (child.getName() !== null) {
+          this.addName(child.getName(), child);
+        }
+      };
+      this.addName = function(name,  shape) {
+        if (this.parent !== null) {
+          this.parent.addName( name, shape );
+        } else {
+          this.nameTable.push( [name, shape] );
+        }
+      };
+      this.translate = function() {
+        if(arguments.length === 2)
+        {
+          this.checkMatrix(2);
+          this.matrix.translate(arguments[0], arguments[1]);
+        } else {
+          this.checkMatrix(3);
+          this.matrix.translate(arguments[0], arguments[1], 0);
+        }
+      };
+      this.checkMatrix = function(dimensions) {
+        if(this.matrix === null) {
+          if(dimensions === 2) {
+            this.matrix = new p.PMatrix2D();
+          } else {
+            this.matrix = new p.PMatrix3D();
+          }
+        }else if(dimensions === 3 && this.matrix instanceof p.PMatrix2D) {
+          this.matrix = new p.PMatrix3D();
+        }
+      };
+      this.rotateX = function(angle) {
+        this.rotate(angle, 1, 0, 0);
+      };
+      this.rotateY = function(angle) {
+        this.rotate(angle, 0, 1, 0);
+      };
+      this.rotateZ = function(angle) {
+        this.rotate(angle, 0, 0, 1);
+      };
+      this.rotate = function() {
+        if(arguments.length === 1){
+          this.checkMatrix(2);
+          this.matrix.rotate(arguments[0]);
+        } else {
+          this.checkMatrix(3);
+          this.matrix.rotate(arguments[0], arguments[1], arguments[2] ,arguments[3]);
+        }
+      };
+      this.scale = function() {
+        if(arguments.length === 2) {
+          this.checkMatrix(2);
+          this.matrix.scale(arguments[0], arguments[1]);
+        } else if (arguments.length === 3) {
+          this.checkMatrix(2);
+          this.matrix.scale(arguments[0], arguments[1], arguments[2]);
+        } else {
+          this.checkMatrix(2);
+          this.matrix.scale(arguments[0]);
+        }
+      };
+      this.resetMatrix = function() {
+        this.checkMatrix(2);
+        this.matrix.reset();
+      };
+      this.applyMatrix = function(matrix) {
+        if (arguments.length === 1) {
+          this.applyMatrix(matrix.elements[0], matrix.elements[1], 0, matrix.elements[2],
+                          matrix.elements[3], matrix.elements[4], 0, matrix.elements[5],
+                          0, 0, 1, 0,
+                          0, 0, 0, 1);
+        } else if (arguments.length === 6) {
+          this.checkMatrix(2);
+          this.matrix.apply(arguments[0], arguments[1], arguments[2], 0,
+                            arguments[3], arguments[4], arguments[5], 0,
+                            0,   0,   1,   0,
+                            0,   0,   0,   1);
+
+        } else if (arguments.length === 16) {
+          this.checkMatrix(3);
+          this.matrix.apply(arguments[0], arguments[1], arguments[2], arguments[3],
+                            arguments[4], arguments[5], arguments[6], arguments[7],
+                            arguments[8], arguments[9], arguments[10], arguments[11],
+                            arguments[12], arguments[13], arguments[14], arguments[15]);
+        }
+      };
+      // findChild not in yet
+      // apply missing
+      // contains missing
+      // find child missing
+      // getPrimitive missing
+      // getParams missing
+      // getVertex , getVertexCount missing
+      // getVertexCode , getVertexCodes , getVertexCodeCount missing
+      // getVertexX, getVertexY, getVertexZ missing
+
+    };
+
+    var PShapeSVG = function() {
+      p.PShape.call( this ); // PShape is the base class.
+      if (arguments.length === 1) {
+        this.element  = new p.XMLElement(null, arguments[0]);
+        // set values to their defaults according to the SVG spec
+        this.vertexCodes         = [];
+        this.vertices            = [];
+        this.opacity             = 1;
+
+        this.stroke              = false;
+        this.strokeColor         = PConstants.ALPHA_MASK;
+        this.strokeWeight        = 1;
+        this.strokeCap           = PConstants.SQUARE;  // equivalent to BUTT in svg spec
+        this.strokeJoin          = PConstants.MITER;
+        this.strokeGradient      = null;
+        this.strokeGradientPaint = null;
+        this.strokeName          = null;
+        this.strokeOpacity       = 1;
+
+        this.fill                = true;
+        this.fillColor           = PConstants.ALPHA_MASK;
+        this.fillGradient        = null;
+        this.fillGradientPaint   = null;
+        this.fillName            = null;
+        this.fillOpacity         = 1;
+
+        if (this.element.getName() !== "svg") {
+          throw("root is not <svg>, it's <" + this.element.getName() + ">");
+        }
+      }
+      else if (arguments.length === 2) {
+        if (typeof arguments[1] === 'string') {
+          if (arguments[1].indexOf(".svg") > -1) { //its a filename
+            this.element = new p.XMLElement(null, arguments[1]);
+            // set values to their defaults according to the SVG spec
+            this.vertexCodes         = [];
+            this.vertices            = [];
+            this.opacity             = 1;
+
+            this.stroke              = false;
+            this.strokeColor         = PConstants.ALPHA_MASK;
+            this.strokeWeight        = 1;
+            this.strokeCap           = PConstants.SQUARE;  // equivalent to BUTT in svg spec
+            this.strokeJoin          = PConstants.MITER;
+            this.strokeGradient      = "";
+            this.strokeGradientPaint = "";
+            this.strokeName          = "";
+            this.strokeOpacity       = 1;
+
+            this.fill                = true;
+            this.fillColor           = PConstants.ALPHA_MASK;
+            this.fillGradient        = null;
+            this.fillGradientPaint   = null;
+            this.fillOpacity         = 1;
+
+          }
+        } else { // XMLElement
+          if (arguments[0]) { // PShapeSVG
+            this.element             = arguments[1];
+            this.vertexCodes         = arguments[0].vertexCodes.slice();
+            this.vertices            = arguments[0].vertices.slice();
+
+            this.stroke              = arguments[0].stroke;
+            this.strokeColor         = arguments[0].strokeColor;
+            this.strokeWeight        = arguments[0].strokeWeight;
+            this.strokeCap           = arguments[0].strokeCap;
+            this.strokeJoin          = arguments[0].strokeJoin;
+            this.strokeGradient      = arguments[0].strokeGradient;
+            this.strokeGradientPaint = arguments[0].strokeGradientPaint;
+            this.strokeName          = arguments[0].strokeName;
+
+            this.fill                = arguments[0].fill;
+            this.fillColor           = arguments[0].fillColor;
+            this.fillGradient        = arguments[0].fillGradient;
+            this.fillGradientPaint   = arguments[0].fillGradientPaint;
+            this.fillName            = arguments[0].fillName;
+            this.strokeOpacity       = arguments[0].strokeOpacity;
+            this.fillOpacity         = arguments[0].fillOpacity;
+            this.opacity             = arguments[0].opacity;
+          }
+        }
+      }
+
+      this.name      = this.element.getStringAttribute("id");
+      var displayStr = this.element.getStringAttribute("display", "inline");
+      this.visible   = displayStr !== "none";
+      var str = this.element.getAttribute("transform");
+      if (str) {
+        this.matrix = this.parseMatrix(str);
+      }
+      // not proper parsing of the viewBox, but will cover us for cases where
+      // the width and height of the object is not specified
+      var viewBoxStr = this.element.getStringAttribute("viewBox");
+      if ( viewBoxStr !== null ) {
+        var viewBox = viewBoxStr.split(" ");
+        this.width  = viewBox[2];
+        this.height = viewBox[3];
+      }
+
+      // TODO if viewbox is not same as width/height, then use it to scale
+      // the original objects. for now, viewbox only used when width/height
+      // are empty values (which by the spec means w/h of "100%"
+      var unitWidth  = this.element.getStringAttribute("width");
+      var unitHeight = this.element.getStringAttribute("height");
+      if (unitWidth !== null) {
+        this.width  = this.parseUnitSize(unitWidth);
+        this.height = this.parseUnitSize(unitHeight);
+      } else {
+        if ((this.width === 0) || (this.height === 0)) {
+          // For the spec, the default is 100% and 100%. For purposes
+          // here, insert a dummy value because this is prolly just a
+          // font or something for which the w/h doesn't matter.
+          this.width  = 1;
+          this.height = 1;
+
+          //show warning
+          throw("The width and/or height is not " +
+                                "readable in the <svg> tag of this file.");
+        }
+      }
+      this.parseColors(this.element);
+      this.parseChildren(this.element);
+
+    };
+
+    PShapeSVG.prototype = {
+      // getChild missing
+      // print missing
+      // parse style attributes
+      // styles missing but deals with strokeGradient and fillGradient
+      parseMatrix: function(str) {
+        this.checkMatrix(2);
+        var pieces = [];
+        str.replace(/\s*(\w+)\((.*?)\)/g, function(all) {
+          // get a list of transform definitions
+          pieces.push(p.trim(all));
+        });
+        if (pieces.length === 0) {
+          p.println("Transformation:" + str + " is empty");
+          return null;
+        }
+        for (var i =0; i< pieces.length; i++) {
+          var m = [];
+          pieces[i].replace(/\((.*?)\)/, (function() {
+            return function(all, params) {
+              // get the coordinates that can be separated by spaces or a comma
+              m = params.replace(/,+/g, " ").split(/\s+/);
+            };
+          }()));
+
+          if (pieces[i].indexOf("matrix") !== -1) {
+            this.matrix.set(m[0], m[2], m[4], m[1], m[3], m[5]);
+          } else if (pieces[i].indexOf("translate") !== -1) {
+            var tx = m[0];
+            var ty = (m.length === 2) ? m[1] : 0;
+            this.matrix.translate(tx,ty);
+          } else if (pieces[i].indexOf("scale") !== -1) {
+            var sx = m[0];
+            var sy = (m.length === 2) ? m[1] : m[0];
+            this.matrix.scale(sx,sy);
+          } else if (pieces[i].indexOf("rotate") !== -1) {
+            var angle = m[0];
+            if (m.length === 1) {
+              this.matrix.rotate(p.radians(angle));
+            } else if (m.length === 3) {
+              this.matrix.translate(m[1], m[2]);
+              this.matrix.rotate(p.radians(m[0]));
+              this.matrix.translate(-m[1], -m[2]);
+            }
+          } else if (pieces[i].indexOf("skewX") !== -1) {
+            this.matrix.skewX(parseFloat(m[0]));
+          } else if (pieces[i].indexOf("skewY") !== -1) {
+            this.matrix.skewY(m[0]);
+          }
+        }
+        return this.matrix;
+      },
+      parseChildren:function(element) {
+        var newelement = element.getChildren();
+        var children   = new p.PShape();
+        for (var i = 0; i < newelement.length; i++) {
+          var kid = this.parseChild(newelement[i]);
+          if (kid) {
+            children.addChild(kid);
+          }
+        }
+        this.children.push(children);
+      },
+      getName: function() {
+        return this.name;
+      },
+      parseChild: function( elem ) {
+        var name = elem.getName();
+        var shape;
+        switch (name) {
+          case "g":
+            shape = new PShapeSVG(this, elem);
+            break;
+          case "defs":
+            // generally this will contain gradient info, so may
+            // as well just throw it into a group element for parsing
+            shape = new PShapeSVG(this, elem);
+            break;
+          case "line":
+            shape = new PShapeSVG(this, elem);
+            shape.parseLine();
+            break;
+          case "circle":
+            shape = new PShapeSVG(this, elem);
+            shape.parseEllipse(true);
+            break;
+          case "ellipse":
+            shape = new PShapeSVG(this, elem);
+            shape.parseEllipse(false);
+            break;
+          case "rect":
+            shape = new PShapeSVG(this, elem);
+            shape.parseRect();
+            break;
+          case "polygon":
+            shape = new PShapeSVG(this, elem);
+            shape.parsePoly(true);
+            break;
+          case "polyline":
+            shape = new PShapeSVG(this, elem);
+            shape.parsePoly(false);
+            break;
+          case "path":
+            shape = new PShapeSVG(this, elem);
+            shape.parsePath();
+            break;
+          case "radialGradient":
+            //return new RadialGradient(this, elem);
+            break;
+          case "linearGradient":
+            //return new LinearGradient(this, elem);
+            break;
+          case "text":
+            p.println("Text in SVG files is not currently supported, convert text to outlines instead." );
+            break;
+          case "filter":
+            p.println("Filters are not supported.");
+            break;
+          case "mask":
+            p.println("Masks are not supported.");
+            break;
+          default:
+            p.println("Ignoring  <" + name + "> tag.");
+            break;
+        }
+        return shape;
+      },
+      parsePath: function() {
+        this.family = PConstants.PATH;
+        this.kind = 0;
+        var pathDataChars = [];
+        var c;
+        var pathData = p.trim(this.element.getStringAttribute("d").replace(/[\s,]+/g,' ')); //change multiple spaces and commas to single space
+        if (pathData === null) { return; }
+        pathData = pathData.toCharArray();
+        var cx     = 0,
+            cy     = 0,
+            ctrlX  = 0,
+            ctrlY  = 0,
+            ctrlX1 = 0,
+            ctrlX2 = 0,
+            ctrlY1 = 0,
+            ctrlY2 = 0,
+            endX   = 0,
+            endY   = 0,
+            ppx    = 0,
+            ppy    = 0,
+            px     = 0,
+            py     = 0,
+            i      = 0,
+            j      = 0,
+            valOf  = 0;
+        var str = "";
+        var tmpArray =[];
+        var flag = false;
+        var lastInstruction;
+        var command;
+        while (i< pathData.length) {
+          valOf = pathData[i].valueOf();
+          if ((valOf >= 65 && valOf <= 90) || (valOf >= 97 && valOf <= 122)) { // if its a letter
+            // populate the tmpArray with coordinates
+            j = i;
+            i++;
+            if (i < pathData.length) { // dont go over boundary of array
+              tmpArray = [];
+              valOf = pathData[i].valueOf();
+              while (!((valOf >= 65 && valOf <= 90) || (valOf >= 97 && valOf <= 100) || (valOf >= 102 && valOf <= 122)) && flag === false) { // if its NOT a letter
+                if (valOf === 32) { //if its a space and the str isn't empty
+                  // somethimes you get a space after the letter
+                  if (str !== "") {
+                    tmpArray.push(parseFloat(str));
+                    str = "";
+                  }
+                  i++;
+                } else if (valOf === 45) { //if its a -
+                  // allow for 'e' notation in numbers, e.g. 2.10e-9
+                  if (pathData[i-1].valueOf() === 101) {
+                    str += pathData[i].toString();
+                    i++;
+                  } else {
+                    // sometimes no space separator after (ex: 104.535-16.322)
+                    if (str !== "") {
+                      tmpArray.push(parseFloat(str));
+                    }
+                    str = pathData[i].toString();
+                    i++;
+                  }
+                } else {
+                  str += pathData[i].toString();
+                  i++;
+                }
+                if (i === pathData.length) { // dont go over boundary of array
+                  flag = true;
+                } else {
+                  valOf = pathData[i].valueOf();
+                }
+              }
+            }
+            if (str !== "") {
+              tmpArray.push(parseFloat(str));
+              str = "";
+            }
+            command = pathData[j];
+            switch (command.valueOf()) {
+              case 77:  // M - move to (absolute)
+                if (tmpArray.length >= 2 && tmpArray.length % 2 ===0) { // need one+ pairs of co-ordinates
+                  cx = tmpArray[0];
+                  cy = tmpArray[1];
+                  this.parsePathMoveto(cx, cy);
+                  if (tmpArray.length > 2) {
+                    for (j = 2; j < tmpArray.length; j+=2) {
+                      // absolute line to
+                      cx = tmpArray[j];
+                      cy = tmpArray[j+1];
+                      this.parsePathLineto(cx,cy);
+                    }
+                  }
+                }
+                break;
+              case 109:  // m - move to (relative)
+                if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates
+                  this.parsePathMoveto(cx,cy);
+                  if (tmpArray.length > 2) {
+                    for (j = 2; j < tmpArray.length; j+=2) {
+                      // relative line to
+                      cx += tmpArray[j];
+                      cy += tmpArray[j + 1];
+                      this.parsePathLineto(cx,cy);
+                    }
+                  }
+                }
+                break;
+              case 76: // L - lineto (absolute)
+              if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates
+                for (j = 0; j < tmpArray.length; j+=2) {
+                  cx = tmpArray[j];
+                  cy = tmpArray[j + 1];
+                  this.parsePathLineto(cx,cy);
+                }
+              }
+              break;
+
+              case 108: // l - lineto (relative)
+                if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=2) {
+                    cx += tmpArray[j];
+                    cy += tmpArray[j+1];
+                    this.parsePathLineto(cx,cy);
+                  }
+                }
+                break;
+
+              case 72: // H - horizontal lineto (absolute)
+                for (j = 0; j < tmpArray.length; j++) { // multiple x co-ordinates can be provided
+                  cx = tmpArray[j];
+                  this.parsePathLineto(cx, cy);
+                }
+                break;
+
+              case 104: // h - horizontal lineto (relative)
+                for (j = 0; j < tmpArray.length; j++) { // multiple x co-ordinates can be provided
+                  cx += tmpArray[j];
+                  this.parsePathLineto(cx, cy);
+                }
+                break;
+
+              case 86: // V - vertical lineto (absolute)
+                for (j = 0; j < tmpArray.length; j++) { // multiple y co-ordinates can be provided
+                  cy = tmpArray[j];
+                  this.parsePathLineto(cx, cy);
+                }
+                break;
+
+              case 118: // v - vertical lineto (relative)
+                for (j = 0; j < tmpArray.length; j++) { // multiple y co-ordinates can be provided
+                  cy += tmpArray[j];
+                  this.parsePathLineto(cx, cy);
+                }
+                break;
+
+              case 67: // C - curve to (absolute)
+                if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { // need one+ multiples of 6 co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=6) {
+                    ctrlX1 = tmpArray[j];
+                    ctrlY1 = tmpArray[j + 1];
+                    ctrlX2 = tmpArray[j + 2];
+                    ctrlY2 = tmpArray[j + 3];
+                    endX   = tmpArray[j + 4];
+                    endY   = tmpArray[j + 5];
+                    this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 99: // c - curve to (relative)
+                if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { // need one+ multiples of 6 co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=6) {
+                    ctrlX1 = cx + tmpArray[j];
+                    ctrlY1 = cy + tmpArray[j + 1];
+                    ctrlX2 = cx + tmpArray[j + 2];
+                    ctrlY2 = cy + tmpArray[j + 3];
+                    endX   = cx + tmpArray[j + 4];
+                    endY   = cy + tmpArray[j + 5];
+                    this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 83: // S - curve to shorthand (absolute)
+                if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=4) {
+                    if (lastInstruction.toLowerCase() ===  "c" || lastInstruction.toLowerCase() ===  "s") {
+                      ppx    = this.vertices[ this.vertices.length-2 ][0];
+                      ppy    = this.vertices[ this.vertices.length-2 ][1];
+                      px     = this.vertices[ this.vertices.length-1 ][0];
+                      py     = this.vertices[ this.vertices.length-1 ][1];
+                      ctrlX1 = px + (px - ppx);
+                      ctrlY1 = py + (py - ppy);
+                    } else {
+                      //If there is no previous curve, the current point will be used as the first control point.
+                      ctrlX1 = this.vertices[this.vertices.length-1][0];
+                      ctrlY1 = this.vertices[this.vertices.length-1][1];
+                    }
+                    ctrlX2 = tmpArray[j];
+                    ctrlY2 = tmpArray[j + 1];
+                    endX   = tmpArray[j + 2];
+                    endY   = tmpArray[j + 3];
+                    this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 115: // s - curve to shorthand (relative)
+                if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=4) {
+                    if (lastInstruction.toLowerCase() ===  "c" || lastInstruction.toLowerCase() ===  "s") {
+                      ppx    = this.vertices[this.vertices.length-2][0];
+                      ppy    = this.vertices[this.vertices.length-2][1];
+                      px     = this.vertices[this.vertices.length-1][0];
+                      py     = this.vertices[this.vertices.length-1][1];
+                      ctrlX1 = px + (px - ppx);
+                      ctrlY1 = py + (py - ppy);
+                    } else {
+                      //If there is no previous curve, the current point will be used as the first control point.
+                      ctrlX1 = this.vertices[this.vertices.length-1][0];
+                      ctrlY1 = this.vertices[this.vertices.length-1][1];
+                    }
+                    ctrlX2 = cx + tmpArray[j];
+                    ctrlY2 = cy + tmpArray[j + 1];
+                    endX   = cx + tmpArray[j + 2];
+                    endY   = cy + tmpArray[j + 3];
+                    this.parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 81: // Q - quadratic curve to (absolute)
+                if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=4) {
+                    ctrlX = tmpArray[j];
+                    ctrlY = tmpArray[j + 1];
+                    endX  = tmpArray[j + 2];
+                    endY  = tmpArray[j + 3];
+                    this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 113: // q - quadratic curve to (relative)
+                if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { // need one+ multiples of 4 co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=4) {
+                    ctrlX = cx + tmpArray[j];
+                    ctrlY = cy + tmpArray[j + 1];
+                    endX  = cx + tmpArray[j + 2];
+                    endY  = cy + tmpArray[j + 3];
+                    this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 84: // T - quadratic curve to shorthand (absolute)
+                if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=2) {
+                    if (lastInstruction.toLowerCase() ===  "q" || lastInstruction.toLowerCase() ===  "t") {
+                      ppx   = this.vertices[this.vertices.length-2][0];
+                      ppy   = this.vertices[this.vertices.length-2][1];
+                      px    = this.vertices[this.vertices.length-1][0];
+                      py    = this.vertices[this.vertices.length-1][1];
+                      ctrlX = px + (px - ppx);
+                      ctrlY = py + (py - ppy);
+                    } else {
+                      // If there is no previous command or if the previous command was not a Q, q, T or t,
+                      // assume the control point is coincident with the current point.
+                      ctrlX = cx;
+                      ctrlY = cy;
+                    }
+                    endX  = tmpArray[j];
+                    endY  = tmpArray[j + 1];
+                    this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 116:  // t - quadratic curve to shorthand (relative)
+                if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { // need one+ pairs of co-ordinates
+                  for (j = 0; j < tmpArray.length; j+=2) {
+                    if (lastInstruction.toLowerCase() ===  "q" || lastInstruction.toLowerCase() ===  "t") {
+                      ppx   = this.vertices[this.vertices.length-2][0];
+                      ppy   = this.vertices[this.vertices.length-2][1];
+                      px    = this.vertices[this.vertices.length-1][0];
+                      py    = this.vertices[this.vertices.length-1][1];
+                      ctrlX = px + (px - ppx);
+                      ctrlY = py + (py - ppy);
+                    } else {
+                      // If there is no previous command or if the previous command was not a Q, q, T or t,
+                      // assume the control point is coincident with the current point.
+                      ctrlX = cx;
+                      ctrlY = cy;
+                    }
+                    endX  = cx + tmpArray[j];
+                    endY  = cy + tmpArray[j + 1];
+                    this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
+                    cx = endX;
+                    cy = endY;
+                  }
+                }
+                break;
+
+              case 90: //Z
+              case 122: //z
+                this.close = true;
+                break;
+            }
+            lastInstruction = command.toString();
+          } else { i++;}
+        }
+      },
+      parsePathQuadto: function(x1, y1, cx, cy, x2, y2) {
+        if (this.vertices.length > 0) {
+          this.parsePathCode(PConstants.BEZIER_VERTEX);
+          // x1/y1 already covered by last moveto, lineto, or curveto
+          this.parsePathVertex(x1 + ((cx-x1)*2/3), y1 + ((cy-y1)*2/3));
+          this.parsePathVertex(x2 + ((cx-x2)*2/3), y2 + ((cy-y2)*2/3));
+          this.parsePathVertex(x2, y2);
+        } else {
+          throw ("Path must start with M/m");
+        }
+      },
+      parsePathCurveto : function(x1,  y1, x2, y2, x3, y3) {
+        if (this.vertices.length > 0) {
+          this.parsePathCode(PConstants.BEZIER_VERTEX );
+          this.parsePathVertex(x1, y1);
+          this.parsePathVertex(x2, y2);
+          this.parsePathVertex(x3, y3);
+        } else {
+          throw ("Path must start with M/m");
+        }
+      },
+      parsePathLineto: function(px, py) {
+        if (this.vertices.length > 0) {
+          this.parsePathCode(PConstants.VERTEX);
+          this.parsePathVertex(px, py);
+          // add property to distinguish between curContext.moveTo or curContext.lineTo
+          this.vertices[this.vertices.length-1]["moveTo"] = false;
+        } else {
+          throw ("Path must start with M/m");
+        }
+      },
+      parsePathMoveto: function(px, py) {
+        if (this.vertices.length > 0) {
+          this.parsePathCode(PConstants.BREAK);
+        }
+        this.parsePathCode(PConstants.VERTEX);
+        this.parsePathVertex(px, py);
+        // add property to distinguish between curContext.moveTo or curContext.lineTo
+        this.vertices[this.vertices.length-1]["moveTo"] = true;
+      },
+      parsePathVertex: function(x,  y) {
+        var verts = [];
+        verts[0]  = x;
+        verts[1]  = y;
+        this.vertices.push(verts);
+      },
+      parsePathCode: function(what) {
+        this.vertexCodes.push(what);
+      },
+      parsePoly: function(val) {
+        this.family    = PConstants.PATH;
+        this.close     = val;
+        var pointsAttr = p.trim(this.element.getStringAttribute("points").replace(/[,\s]+/g,' '));
+        if (pointsAttr !== null) {
+          //split into array
+          var pointsBuffer = pointsAttr.split(" ");
+          if (pointsBuffer.length % 2 === 0) {
+            for (var i = 0; i < pointsBuffer.length; i++) {
+              var verts = [];
+              verts[0]  = pointsBuffer[i];
+              verts[1]  = pointsBuffer[++i];
+              this.vertices.push(verts);
+            }
+          } else {
+            p.println("Error parsing polygon points: odd number of coordinates provided");
+          }
+        }
+      },
+      parseRect: function() {
+        this.kind      = PConstants.RECT;
+        this.family    = PConstants.PRIMITIVE;
+        this.params    = [];
+        this.params[0] = this.element.getFloatAttribute("x");
+        this.params[1] = this.element.getFloatAttribute("y");
+        this.params[2] = this.element.getFloatAttribute("width");
+        this.params[3] = this.element.getFloatAttribute("height");
+
+      },
+      parseEllipse: function(val) {
+        this.kind   = PConstants.ELLIPSE;
+        this.family = PConstants.PRIMITIVE;
+        this.params = [];
+
+        this.params[0] = this.element.getFloatAttribute("cx");
+        this.params[1] = this.element.getFloatAttribute("cy");
+
+        var rx, ry;
+        if (val) {
+          rx = ry = this.element.getFloatAttribute("r");
+        } else {
+          rx = this.element.getFloatAttribute("rx");
+          ry = this.element.getFloatAttribute("ry");
+        }
+        this.params[0] -= rx;
+        this.params[1] -= ry;
+
+        this.params[2] = rx*2;
+        this.params[3] = ry*2;
+      },
+      parseLine: function() {
+        this.kind = PConstants.LINE;
+        this.family = PConstants.PRIMITIVE;
+        this.params = [];
+        this.params[0] = this.element.getFloatAttribute("x1");
+        this.params[1] = this.element.getFloatAttribute("y1");
+        this.params[2] = this.element.getFloatAttribute("x2");
+        this.params[3] = this.element.getFloatAttribute("y2");
+      },
+      parseColors: function(element) {
+        if (element.hasAttribute("opacity")) {
+          this.setOpacity(element.getAttribute("opacity"));
+        }
+        if (element.hasAttribute("stroke")) {
+          this.setStroke(element.getAttribute("stroke"));
+        }
+        if (element.hasAttribute("stroke-width")) {
+          // if NaN (i.e. if it's 'inherit') then default back to the inherit setting
+          this.setStrokeWeight(element.getAttribute("stroke-width"));
+        }
+        if (element.hasAttribute("stroke-linejoin") ) {
+          this.setStrokeJoin(element.getAttribute("stroke-linejoin"));
+        }
+        if (element.hasAttribute("stroke-linecap")) {
+          this.setStrokeCap(element.getStringAttribute("stroke-linecap"));
+        }
+        // fill defaults to black (though stroke defaults to "none")
+        // http://www.w3.org/TR/SVG/painting.html#FillProperties
+        if (element.hasAttribute("fill")) {
+          this.setFill(element.getStringAttribute("fill"));
+        }
+        if (element.hasAttribute("style")) {
+          var styleText   = element.getStringAttribute("style");
+          var styleTokens = styleText.toString().split( ";" );
+
+          for (var i = 0; i < styleTokens.length; i++) {
+            var tokens = p.trim(styleTokens[i].split( ":" ));
+            switch(tokens[0]){
+              case "fill":
+                this.setFill(tokens[1]);
+                break;
+              case "fill-opacity":
+
+                this.setFillOpacity(tokens[1]);
+
+                break;
+              case "stroke":
+                this.setStroke(tokens[1]);
+                break;
+              case "stroke-width":
+                this.setStrokeWeight(tokens[1]);
+                break;
+              case "stroke-linecap":
+                this.setStrokeCap(tokens[1]);
+                break;
+              case "stroke-linejoin":
+                this.setStrokeJoin(tokens[1]);
+                break;
+              case "stroke-opacity":
+                this.setStrokeOpacity(tokens[1]);
+                break;
+              case "opacity":
+                this.setOpacity(tokens[1]);
+                break;
+              // Other attributes are not yet implemented
+            }
+          }
+        }
+      },
+      setFillOpacity: function(opacityText) {
+        this.fillOpacity = parseFloat(opacityText);
+        this.fillColor   = this.fillOpacity * 255  << 24 | this.fillColor & 0xFFFFFF;
+      },
+      setFill: function (fillText) {
+        var opacityMask = this.fillColor & 0xFF000000;
+        if (fillText === "none") {
+          this.fill = false;
+        } else if (fillText.indexOf("#") === 0) {
+          this.fill      = true;
+          this.fillColor = opacityMask | (parseInt(fillText.substring(1), 16 )) & 0xFFFFFF;
+        } else if (fillText.indexOf("rgb") === 0) {
+          this.fill      = true;
+          this.fillColor = opacityMask | this.parseRGB(fillText);
+        } else if (fillText.indexOf("url(#") === 0) {
+          this.fillName = fillText.substring(5, fillText.length - 1 );
+          /*Object fillObject = findChild(fillName);
+          if (fillObject instanceof Gradient) {
+            fill = true;
+            fillGradient = (Gradient) fillObject;
+            fillGradientPaint = calcGradientPaint(fillGradient); //, opacity);
+          } else {
+            System.err.println("url " + fillName + " refers to unexpected data");
+          }*/
+        } else {
+          if (colors[fillText]) {
+            this.fill      = true;
+            this.fillColor = opacityMask | (parseInt(colors[fillText].substring(1), 16)) & 0xFFFFFF;
+          }
+        }
+      },
+      setOpacity: function(opacity) {
+        this.strokeColor = parseFloat(opacity) * 255 << 24 | this.strokeColor & 0xFFFFFF;
+        this.fillColor   = parseFloat(opacity) * 255 << 24 | this.fillColor & 0xFFFFFF;
+      },
+      setStroke: function(strokeText) {
+        var opacityMask = this.strokeColor & 0xFF000000;
+        if (strokeText === "none") {
+          this.stroke = false;
+        } else if (strokeText.charAt( 0 ) === "#") {
+          this.stroke      = true;
+          this.strokeColor = opacityMask | (parseInt( strokeText.substring( 1 ), 16 )) & 0xFFFFFF;
+        } else if (strokeText.indexOf( "rgb" ) === 0 ) {
+          this.stroke = true;
+          this.strokeColor = opacityMask | this.parseRGB(strokeText);
+        } else if (strokeText.indexOf( "url(#" ) === 0) {
+          this.strokeName = strokeText.substring(5, strokeText.length - 1);
+            //this.strokeObject = findChild(strokeName);
+          /*if (strokeObject instanceof Gradient) {
+            strokeGradient = (Gradient) strokeObject;
+            strokeGradientPaint = calcGradientPaint(strokeGradient); //, opacity);
+          } else {
+            System.err.println("url " + strokeName + " refers to unexpected data");
+          }*/
+        } else {
+          if (colors[strokeText]){
+            this.stroke      = true;
+            this.strokeColor = opacityMask | (parseInt(colors[strokeText].substring(1), 16)) & 0xFFFFFF;
+          }
+        }
+      },
+      setStrokeWeight: function(weight) {
+        this.strokeWeight = this.parseUnitSize(weight);
+      },
+      setStrokeJoin: function(linejoin) {
+        if (linejoin === "miter") {
+          this.strokeJoin = PConstants.MITER;
+
+        } else if (linejoin === "round") {
+          this.strokeJoin = PConstants.ROUND;
+
+        } else if (linejoin === "bevel") {
+          this.strokeJoin = PConstants.BEVEL;
+        }
+      },
+      setStrokeCap: function (linecap) {
+        if (linecap === "butt") {
+          this.strokeCap = PConstants.SQUARE;
+
+        } else if (linecap === "round") {
+          this.strokeCap = PConstants.ROUND;
+
+        } else if (linecap === "square") {
+          this.strokeCap = PConstants.PROJECT;
+        }
+      },
+      setStrokeOpacity: function (opacityText) {
+        this.strokeOpacity = parseFloat(opacityText);
+        this.strokeColor   = this.strokeOpacity * 255 << 24 | this.strokeColor & 0xFFFFFF;
+      },
+      parseRGB: function(color) {
+        var sub    = color.substring(color.indexOf('(') + 1, color.indexOf(')'));
+        var values = sub.split(", ");
+        return (values[0] << 16) | (values[1] << 8) | (values[2]);
+      },
+      parseUnitSize: function (text) {
+        var len = text.length - 2;
+        if (len < 0) { return text; }
+        if (text.indexOf("pt") === len) {
+          return parseFloat(text.substring(0, len)) * 1.25;
+        } else if (text.indexOf("pc") === len) {
+          return parseFloat( text.substring( 0, len)) * 15;
+        } else if (text.indexOf("mm") === len) {
+          return parseFloat( text.substring(0, len)) * 3.543307;
+        } else if (text.indexOf("cm") === len) {
+          return parseFloat(text.substring(0, len)) * 35.43307;
+        } else if (text.indexOf("in") === len) {
+          return parseFloat(text.substring(0, len)) * 90;
+        } else if (text.indexOf("px") === len) {
+          return parseFloat(text.substring(0, len));
+        } else {
+          return parseFloat(text);
+        }
+      }
+    };
+
+    p.shape = function(shape, x, y, width, height) {
+      if (arguments.length >= 1 && arguments[0] !== null) {
+        if (shape.isVisible()) {
+          p.pushMatrix();
+          if (curShapeMode === PConstants.CENTER) {
+            if (arguments.length === 5) {
+              p.translate(x - width/2, y - height/2);
+              p.scale(width / shape.getWidth(), height / shape.getHeight());
+            } else if (arguments.length === 3) {
+              p.translate(x - shape.getWidth()/2, - shape.getHeight()/2);
+            } else {
+              p.translate(-shape.getWidth()/2, -shape.getHeight()/2);
+            }
+          } else if (curShapeMode === PConstants.CORNER) {
+            if (arguments.length === 5) {
+              p.translate(x, y);
+              p.scale(width / shape.getWidth(), height / shape.getHeight());
+            } else if (arguments.length === 3) {
+              p.translate(x, y);
+            }
+          } else if (curShapeMode === PConstants.CORNERS) {
+            if (arguments.length === 5) {
+              width  -= x;
+              height -= y;
+              p.translate(x, y);
+              p.scale(width / shape.getWidth(), height / shape.getHeight());
+            } else if (arguments.length === 3) {
+              p.translate(x, y);
+            }
+          }
+          shape.draw();
+          if ((arguments.length === 1 && curShapeMode === PConstants.CENTER ) || arguments.length > 1) {
+            p.popMatrix();
+          }
+        }
+      }
+    };
+
+    p.shapeMode = function (mode) {
+      curShapeMode = mode;
+    };
+
+    p.loadShape = function (filename) {
+      if (arguments.length === 1) {
+        if (filename.indexOf(".svg") > -1) {
+          return new PShapeSVG(null, filename);
+        }
+      }
+      return null;
+    };
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // XMLAttribute
+    ////////////////////////////////////////////////////////////////////////////
+    var XMLAttribute = function(fname, n, nameSpace, v, t){
+      this.fullName = fname || "";
+      this.name = n || "";
+      this.namespace = nameSpace || "";
+      this.value = v;
+      this.type = t;
+    };
+    XMLAttribute.prototype = {
+      getName: function() {
+        return this.name;
+      },
+      getFullName: function() {
+        return this.fullName;
+      },
+      getNamespace: function() {
+        return this.namespace;
+      },
+      getValue: function() {
+        return this.value;
+      },
+      getType: function() {
+        return this.type;
+      },
+      setValue: function(newval) {
+        this.value = newval;
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // XMLElement
+    ////////////////////////////////////////////////////////////////////////////
+    var XMLElement = p.XMLElement = function() {
+      if (arguments.length === 4) {
+        this.attributes = [];
+        this.children   = [];
+        this.fullName   = arguments[0] || "";
+        if (arguments[1]) {
+            this.name = arguments[1];
+        } else {
+            var index = this.fullName.indexOf(':');
+            if (index >= 0) {
+                this.name = this.fullName.substring(index + 1);
+            } else {
+                this.name = this.fullName;
+            }
+        }
+        this.namespace = arguments[1];
+        this.content   = "";
+        this.lineNr    = arguments[3];
+        this.systemID  = arguments[2];
+        this.parent    = null;
+      }
+      else if ((arguments.length === 2 && arguments[1].indexOf(".") > -1) ) { // filename or svg xml element
+        this.attributes = [];
+        this.children   = [];
+        this.fullName   = "";
+        this.name       = "";
+        this.namespace  = "";
+        this.content    = "";
+        this.systemID   = "";
+        this.lineNr     = "";
+        this.parent     = null;
+        this.parse(arguments[arguments.length -1]);
+      } else if (arguments.length === 1 && typeof arguments[0] === "string"){
+        //xml string
+        this.attributes = [];
+        this.children   = [];
+        this.fullName   = "";
+        this.name       = "";
+        this.namespace  = "";
+        this.content    = "";
+        this.systemID   = "";
+        this.lineNr     = "";
+        this.parent     = null;
+        this.parse(arguments[0]);
+      }
+      else { //empty ctor
+        this.attributes = [];
+        this.children   = [];
+        this.fullName   = "";
+        this.name       = "";
+        this.namespace  = "";
+        this.content    = "";
+        this.systemID   = "";
+        this.lineNr     = "";
+        this.parent     = null;
+
+      }
+      return this;
+    };
+    /*XMLElement methods
+      missing: enumerateAttributeNames(), enumerateChildren(),
+      NOTE: parse does not work when a url is passed in
+    */
+    XMLElement.prototype = {
+      parse: function(filename) {
+        var xmlDoc;
+        try {
+          if (filename.indexOf(".xml") > -1 || filename.indexOf(".svg") > -1) {
+            filename = ajax(filename);
+          }
+          xmlDoc = new DOMParser().parseFromString(filename, "text/xml");
+          var elements = xmlDoc.documentElement;
+          if (elements) {
+            this.parseChildrenRecursive(null, elements);
+          } else {
+            throw ("Error loading document");
+          }
+          return this;
+        } catch(e) {
+          throw(e);
+        }
+      },
+      createElement: function () {
+        if (arguments.length === 2) {
+          return new XMLElement(arguments[0], arguments[1], null, null);
+        } else {
+          return new XMLElement(arguments[0], arguments[1], arguments[2], arguments[3]);
+        }
+      },
+      hasAttribute: function (name) {
+        return this.getAttribute(name) !== null;
+        //2 parameter call missing
+      },
+      createPCDataElement: function () {
+        return new XMLElement();
+      },
+      equals: function(object){
+        if (typeof object === "Object") {
+          return this.equalsXMLElement(object);
+        }
+      },
+      equalsXMLElement: function (object) {
+        if (object instanceof XMLElement) {
+          if (this.name !== object.getLocalName) { return false; }
+          if (this.attributes.length !== object.getAttributeCount()) { return false; }
+          for (var i = 0; i < this.attributes.length; i++){
+            if (! object.hasAttribute(this.attributes[i].getName(), this.attributes[i].getNamespace())) { return false; }
+            if (this.attributes[i].getValue() !== object.attributes[i].getValue()) { return false; }
+            if (this.attributes[i].getType()  !== object.attributes[i].getType()) { return false; }
+          }
+          if (this.children.length !== object.getChildCount()) { return false; }
+          var child1, child2;
+          for (i = 0; i < this.children.length; i++) {
+            child1 = this.getChildAtIndex(i);
+            child2 = object.getChildAtIndex(i);
+            if (! child1.equalsXMLElement(child2)) { return false; }
+          }
+          return true;
+        }
+      },
+      getContent: function(){
+         return this.content;
+      },
+      getAttribute: function (){
+        var attribute;
+        if( arguments.length === 2 ){
+          attribute = this.findAttribute(arguments[0]);
+          if (attribute) {
+            return attribute.getValue();
+          } else {
+            return arguments[1];
+          }
+        } else if (arguments.length === 1) {
+          attribute = this.findAttribute(arguments[0]);
+          if (attribute) {
+            return attribute.getValue();
+          } else {
+            return null;
+          }
+        }
+      },
+      getStringAttribute: function() {
+        if (arguments.length === 1) {
+          return this.getAttribute(arguments[0]);
+        } else if (arguments.length === 2){
+          return this.getAttribute(arguments[0], arguments[1]);
+        } else {
+          return this.getAttribute(arguments[0], arguments[1],arguments[2]);
+        }
+      },
+      getFloatAttribute: function() {
+        if (arguments.length === 1 ) {
+          return parseFloat(this.getAttribute(arguments[0], 0));
+        } else if (arguments.length === 2 ){
+          return this.getAttribute(arguments[0], arguments[1]);
+        } else {
+          return this.getAttribute(arguments[0], arguments[1],arguments[2]);
+        }
+      },
+      getIntAttribute: function () {
+        if (arguments.length === 1) {
+          return this.getAttribute( arguments[0], 0 );
+        } else if (arguments.length === 2) {
+          return this.getAttribute(arguments[0], arguments[1]);
+        } else {
+          return this.getAttribute(arguments[0], arguments[1],arguments[2]);
+        }
+      },
+      hasChildren: function () {
+        return this.children.length > 0 ;
+      },
+      addChild: function (child) {
+        if (child !== null) {
+          child.parent = this;
+          this.children.push(child);
+        }
+      },
+      insertChild: function (child, index) {
+        if (child) {
+          if ((child.getLocalName() === null) && (! this.hasChildren())) {
+            var lastChild = this.children[this.children.length -1];
+            if (lastChild.getLocalName() === null) {
+                lastChild.setContent(lastChild.getContent() + child.getContent());
+                return;
+            }
+          }
+          child.parent = this;
+          this.children.splice(index,0,child);
+        }
+      },
+      getChild: function (index){
+        if (typeof index  === "number") {
+          return this.children[index];
+        }
+        else if (index.indexOf('/') !== -1) { // path was given
+          this.getChildRecursive(index.split("/"), 0);
+        } else {
+          var kid, kidName;
+          for (var i = 0; i < this.getChildCount(); i++) {
+            kid = this.getChild(i);
+            kidName = kid.getName();
+            if (kidName !== null && kidName === index) {
+                return kid;
+            }
+          }
+          return null;
+        }
+      },
+      getChildren: function(){
+        if (arguments.length === 1) {
+          if (typeof arguments[0]  === "number") {
+            return this.getChild( arguments[0]);
+          } else if (arguments[0].indexOf('/') !== -1) { // path was given
+            return this.getChildrenRecursive( arguments[0].split("/"), 0);
+          } else {
+            var matches = [];
+            var kid, kidName;
+            for (var i = 0; i < this.getChildCount(); i++) {
+              kid = this.getChild(i);
+              kidName = kid.getName();
+              if (kidName !== null && kidName === arguments[0]) {
+                matches.push(kid);
+              }
+            }
+            return matches;
+          }
+        }else {
+          return this.children;
+        }
+      },
+      getChildCount: function(){
+        return this.children.length;
+      },
+      getChildRecursive: function (items, offset) {
+        var kid, kidName;
+        for(var i = 0; i < this.getChildCount(); i++) {
+            kid = this.getChild(i);
+            kidName = kid.getName();
+            if (kidName !== null && kidName === items[offset]) {
+              if (offset === items.length-1) {
+                return kid;
+              } else {
+                offset += 1;
+                return kid.getChildRecursive(items, offset);
+              }
+            }
+        }
+        return null;
+      },
+      getChildrenRecursive: function (items, offset) {
+        if (offset === items.length-1) {
+          return this.getChildren(items[offset]);
+        }
+        var matches = this.getChildren(items[offset]);
+        var kidMatches;
+        for (var i = 0; i < matches.length; i++) {
+          kidMatches = matches[i].getChildrenRecursive(items, offset+1);
+        }
+        return kidMatches;
+      },
+      parseChildrenRecursive: function (parent , elementpath){
+        var xmlelement,
+          xmlattribute,
+          tmpattrib;
+        if (!parent) {
+          this.fullName = elementpath.localName;
+          this.name     = elementpath.nodeName;
+          this.content  = elementpath.textContent || "";
+          xmlelement    = this;
+        } else { // a parent
+          xmlelement         = new XMLElement(elementpath.localName, elementpath.nodeName, "", "");
+          xmlelement.content = elementpath.textContent || "";
+          xmlelement.parent  = parent;
+        }
+
+        for (var l = 0; l < elementpath.attributes.length; l++) {
+          tmpattrib    = elementpath.attributes[l];
+          xmlattribute = new XMLAttribute(tmpattrib.getname , tmpattrib.nodeName, tmpattrib.namespaceURI , tmpattrib.nodeValue , tmpattrib.nodeType);
+          xmlelement.attributes.push(xmlattribute);
+        }
+
+        for (var node in elementpath.childNodes){
+          if(elementpath.childNodes[node].nodeType === 1) { //ELEMENT_NODE type
+            xmlelement.children.push( xmlelement.parseChildrenRecursive(xmlelement, elementpath.childNodes[node]));
+          }
+        }
+        return xmlelement;
+      },
+      isLeaf: function(){
+        return this.hasChildren();
+      },
+      listChildren: function() {
+        var arr = [];
+        for (var i = 0; i < this.children.length; i++) {
+          arr.push( this.getChild(i).getName());
+        }
+        return arr;
+      },
+      removeAttribute: function (name , namespace) {
+        this.namespace = namespace || "";
+        for (var i = 0; i < this.attributes.length; i++){
+          if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) {
+            this.attributes.splice(i, 0);
+          }
+        }
+      },
+      removeChild: function(child) {
+        if (child) {
+          for (var i = 0; i < this.children.length; i++) {
+            if (this.children[i].equalsXMLElement(child)) {
+              this.children.splice(i, 0);
+            }
+          }
+        }
+      },
+      removeChildAtIndex: function(index) {
+        if (this.children.length > index) { //make sure its not outofbounds
+          this.children.splice(index, 0);
+        }
+      },
+      findAttribute: function (name, namespace) {
+        this.namespace = namespace || "";
+        for (var i = 0; i < this.attributes.length; i++ ) {
+          if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) {
+             return this.attributes[i];
+          }
+        }
+      },
+      setAttribute: function() {
+        var attr;
+        if (arguments.length === 3) {
+          var index = arguments[0].indexOf(':');
+          var name  = arguments[0].substring(index + 1);
+          attr      = this.findAttribute( name, arguments[1] );
+          if (attr) {
+            attr.setValue(arguments[2]);
+          } else {
+            attr = new XMLAttribute(arguments[0], name, arguments[1], arguments[2], "CDATA");
+            this.attributes.addElement(attr);
+          }
+        } else {
+          attr = this.findAttribute(arguments[0]);
+          if (attr) {
+            attr.setValue(arguments[1]);
+          } else {
+            attr = new XMLAttribute(arguments[0], arguments[0], null, arguments[1], "CDATA");
+            this.attributes.addElement(attr);
+          }
+        }
+      },
+      setContent: function(content) {
+        this.content = content;
+      },
+      setName: function() {
+        if (arguments.length === 1) {
+          this.name      = arguments[0];
+          this.fullName  = arguments[0];
+          this.namespace = arguments[0];
+        } else {
+          var index = arguments[0].indexOf(':');
+          if ((arguments[1] === null) || (index < 0)) {
+              this.name = arguments[0];
+          } else {
+              this.name = arguments[0].substring(index + 1);
+          }
+          this.fullName  = arguments[0];
+          this.namespace = arguments[1];
+        }
+      },
+      getName: function() {
+        return this.fullName;
+      }
+    };
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // 2D Matrix
+    ////////////////////////////////////////////////////////////////////////////
+
+    /*
+      Helper function for printMatrix(). Finds the largest scalar
+      in the matrix, then number of digits left of the decimal.
+      Call from PMatrix2D and PMatrix3D's print() function.
+    */
+    var printMatrixHelper = function printMatrixHelper(elements) {
+      var big = 0;
+      for (var i = 0; i < elements.length; i++) {
+        if (i !== 0) {
+          big = Math.max(big, Math.abs(elements[i]));
+        } else {
+          big = Math.abs(elements[i]);
+        }
+      }
+
+      var digits = (big + "").indexOf(".");
+      if (digits === 0) {
+        digits = 1;
+      } else if (digits === -1) {
+        digits = (big + "").length;
+      }
+
+      return digits;
+    };
+
+    var PMatrix2D = p.PMatrix2D = function() {
+      if (arguments.length === 0) {
+        this.reset();
+      } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) {
+        this.set(arguments[0].array());
+      } else if (arguments.length === 6) {
+        this.set(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
+      }
+    };
+
+    PMatrix2D.prototype = {
+      set: function() {
+        if (arguments.length === 6) {
+          var a = arguments;
+          this.set([a[0], a[1], a[2],
+                    a[3], a[4], a[5]]);
+        } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) {
+          this.elements = arguments[0].array();
+        } else if (arguments.length === 1 && arguments[0] instanceof Array) {
+          this.elements = arguments[0].slice();
+        }
+      },
+      get: function() {
+        var outgoing = new PMatrix2D();
+        outgoing.set(this.elements);
+        return outgoing;
+      },
+      reset: function() {
+        this.set([1, 0, 0, 0, 1, 0]);
+      },
+      // Returns a copy of the element values.
+      array: function array() {
+        return this.elements.slice();
+      },
+      translate: function(tx, ty) {
+        this.elements[2] = tx * this.elements[0] + ty * this.elements[1] + this.elements[2];
+        this.elements[5] = tx * this.elements[3] + ty * this.elements[4] + this.elements[5];
+      },
+      transpose: function() {
+        // Does nothing in Processing.
+      },
+      mult: function(source, target) {
+        var x, y;
+        if (source instanceof PVector) {
+          x = source.x;
+          y = source.y;
+          if (!target) {
+            target = new PVector();
+          }
+        } else if (source instanceof Array) {
+          x = source[0];
+          y = source[1];
+          if (!target) {
+            target = [];
+          }
+        }
+        if (target instanceof Array) {
+          target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2];
+          target[1] = this.elements[3] * x + this.elements[4] * y + this.elements[5];
+        } else if (target instanceof PVector) {
+          target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2];
+          target.y = this.elements[3] * x + this.elements[4] * y + this.elements[5];
+          target.z = 0;
+        }
+        return target;
+      },
+      multX: function(x, y) {
+        return (x * this.elements[0] + y * this.elements[1] + this.elements[2]);
+      },
+      multY: function(x, y) {
+        return (x * this.elements[3] + y * this.elements[4] + this.elements[5]);
+      },
+      skewX: function(angle) {
+        this.apply(1, 0, 1, angle, 0, 0);
+      },
+      skewY: function(angle) {
+        this.apply(1, 0, 1,  0, angle, 0);
+      },
+      determinant: function() {
+        return (this.elements[0] * this.elements[4] - this.elements[1] * this.elements[3]);
+      },
+      invert: function() {
+        var d = this.determinant();
+        if ( Math.abs( d ) > PConstants.FLOAT_MIN ) {
+          var old00 = this.elements[0];
+          var old01 = this.elements[1];
+          var old02 = this.elements[2];
+          var old10 = this.elements[3];
+          var old11 = this.elements[4];
+          var old12 = this.elements[5];
+          this.elements[0] =  old11 / d;
+          this.elements[3] = -old10 / d;
+          this.elements[1] = -old01 / d;
+          this.elements[1] =  old00 / d;
+          this.elements[2] = (old01 * old12 - old11 * old02) / d;
+          this.elements[5] = (old10 * old02 - old00 * old12) / d;
+          return true;
+        }
+        return false;
+      },
+      scale: function(sx, sy) {
+        if (sx && !sy) {
+          sy = sx;
+        }
+        if (sx && sy) {
+          this.elements[0] *= sx;
+          this.elements[1] *= sy;
+          this.elements[3] *= sx;
+          this.elements[4] *= sy;
+        }
+      },
+      apply: function() {
+        var source;
+        if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) {
+          source = arguments[0].array();
+        } else if (arguments.length === 6) {
+          source = Array.prototype.slice.call(arguments);
+        } else if (arguments.length === 1 && arguments[0] instanceof Array) {
+          source = arguments[0];
+        }
+
+        var result = [0, 0, this.elements[2],
+                      0, 0, this.elements[5]];
+        var e = 0;
+        for (var row = 0; row < 2; row++) {
+          for (var col = 0; col < 3; col++, e++) {
+            result[e] += this.elements[row * 3 + 0] * source[col + 0] +
+                         this.elements[row * 3 + 1] * source[col + 3];
+          }
+        }
+        this.elements = result.slice();
+      },
+      preApply: function() {
+        var source;
+        if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) {
+          source = arguments[0].array();
+        } else if (arguments.length === 6) {
+          source = Array.prototype.slice.call(arguments);
+        } else if (arguments.length === 1 && arguments[0] instanceof Array) {
+          source = arguments[0];
+        }
+        var result = [0, 0, source[2],
+                      0, 0, source[5]];
+        result[2] = source[2] + this.elements[2] * source[0] + this.elements[5] * source[1];
+        result[5] = source[5] + this.elements[2] * source[3] + this.elements[5] * source[4];
+        result[0] = this.elements[0] * source[0] + this.elements[3] * source[1];
+        result[3] = this.elements[0] * source[3] + this.elements[3] * source[4];
+        result[1] = this.elements[1] * source[0] + this.elements[4] * source[1];
+        result[4] = this.elements[1] * source[3] + this.elements[4] * source[4];
+        this.elements = result.slice();
+      },
+      rotate: function(angle) {
+        var c = Math.cos(angle);
+        var s = Math.sin(angle);
+        var temp1 = this.elements[0];
+        var temp2 = this.elements[1];
+        this.elements[0] =  c * temp1 + s * temp2;
+        this.elements[1] = -s * temp1 + c * temp2;
+        temp1 = this.elements[3];
+        temp2 = this.elements[4];
+        this.elements[3] =  c * temp1 + s * temp2;
+        this.elements[4] = -s * temp1 + c * temp2;
+      },
+      rotateZ: function(angle) {
+        this.rotate(angle);
+      },
+      print: function() {
+        var digits = printMatrixHelper(this.elements);
+        var output = "" + p.nfs(this.elements[0], digits, 4) + " " +
+                     p.nfs(this.elements[1], digits, 4) + " " +
+                     p.nfs(this.elements[2], digits, 4) + "\n" +
+                     p.nfs(this.elements[3], digits, 4) + " " +
+                     p.nfs(this.elements[4], digits, 4) + " " +
+                     p.nfs(this.elements[5], digits, 4) + "\n\n";
+        p.println(output);
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // PMatrix3D
+    ////////////////////////////////////////////////////////////////////////////
+
+    var PMatrix3D = p.PMatrix3D = function PMatrix3D() {
+      // When a matrix is created, it is set to an identity matrix
+      this.reset();
+    };
+
+    PMatrix3D.prototype = {
+      set: function() {
+        if (arguments.length === 16) {
+          this.elements = Array.prototype.slice.call(arguments);
+        } else if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) {
+          this.elements = arguments[0].array();
+        } else if (arguments.length === 1 && arguments[0] instanceof Array) {
+          this.elements = arguments[0].slice();
+        }
+      },
+      get: function() {
+        var outgoing = new PMatrix3D();
+        outgoing.set(this.elements);
+        return outgoing;
+      },
+      reset: function() {
+        this.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
+      },
+      // Returns a copy of the element values.
+      array: function array() {
+        return this.elements.slice();
+      },
+      translate: function(tx, ty, tz) {
+        if (tz === undef) {
+          tz = 0;
+        }
+
+        this.elements[3]  += tx * this.elements[0]  + ty * this.elements[1]  + tz * this.elements[2];
+        this.elements[7]  += tx * this.elements[4]  + ty * this.elements[5]  + tz * this.elements[6];
+        this.elements[11] += tx * this.elements[8]  + ty * this.elements[9]  + tz * this.elements[10];
+        this.elements[15] += tx * this.elements[12] + ty * this.elements[13] + tz * this.elements[14];
+      },
+      transpose: function() {
+        var temp = this.elements.slice();
+        this.elements[0]  = temp[0];
+        this.elements[1]  = temp[4];
+        this.elements[2]  = temp[8];
+        this.elements[3]  = temp[12];
+        this.elements[4]  = temp[1];
+        this.elements[5]  = temp[5];
+        this.elements[6]  = temp[9];
+        this.elements[7]  = temp[13];
+        this.elements[8]  = temp[2];
+        this.elements[9]  = temp[6];
+        this.elements[10] = temp[10];
+        this.elements[11] = temp[14];
+        this.elements[12] = temp[3];
+        this.elements[13] = temp[7];
+        this.elements[14] = temp[11];
+        this.elements[15] = temp[15];
+      },
+      /*
+        You must either pass in two PVectors or two arrays,
+        don't mix between types. You may also omit a second
+        argument and simply read the result from the return.
+      */
+      mult: function(source, target) {
+        var x, y, z, w;
+        if (source instanceof PVector) {
+          x = source.x;
+          y = source.y;
+          z = source.z;
+          w = 1;
+          if (!target) {
+            target = new PVector();
+          }
+        } else if (source instanceof Array) {
+          x = source[0];
+          y = source[1];
+          z = source[2];
+          w = source[3] || 1;
+
+          if (!target || target.length !== 3 && target.length !== 4) {
+            target = [0, 0, 0];
+          }
+        }
+
+        if (target instanceof Array) {
+          if (target.length === 3) {
+            target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3];
+            target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7];
+            target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11];
+          } else if (target.length === 4) {
+            target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w;
+            target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w;
+            target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w;
+            target[3] = this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w;
+          }
+        }
+        if (target instanceof PVector) {
+          target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3];
+          target.y = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7];
+          target.z = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11];
+        }
+        return target;
+      },
+      preApply: function() {
+        var source;
+        if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) {
+          source = arguments[0].array();
+        } else if (arguments.length === 16) {
+          source = Array.prototype.slice.call(arguments);
+        } else if (arguments.length === 1 && arguments[0] instanceof Array) {
+          source = arguments[0];
+        }
+
+        var result = [0, 0, 0, 0,
+                      0, 0, 0, 0,
+                      0, 0, 0, 0,
+                      0, 0, 0, 0];
+        var e = 0;
+        for (var row = 0; row < 4; row++) {
+          for (var col = 0; col < 4; col++, e++) {
+            result[e] += this.elements[col + 0] * source[row * 4 + 0] + this.elements[col + 4] *
+                         source[row * 4 + 1] + this.elements[col + 8] * source[row * 4 + 2] +
+                         this.elements[col + 12] * source[row * 4 + 3];
+          }
+        }
+        this.elements = result.slice();
+      },
+      apply: function() {
+        var source;
+        if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) {
+          source = arguments[0].array();
+        } else if (arguments.length === 16) {
+          source = Array.prototype.slice.call(arguments);
+        } else if (arguments.length === 1 && arguments[0] instanceof Array) {
+          source = arguments[0];
+        }
+
+        var result = [0, 0, 0, 0,
+                      0, 0, 0, 0,
+                      0, 0, 0, 0,
+                      0, 0, 0, 0];
+        var e = 0;
+        for (var row = 0; row < 4; row++) {
+          for (var col = 0; col < 4; col++, e++) {
+            result[e] += this.elements[row * 4 + 0] * source[col + 0] + this.elements[row * 4 + 1] *
+                         source[col + 4] + this.elements[row * 4 + 2] * source[col + 8] +
+                         this.elements[row * 4 + 3] * source[col + 12];
+          }
+        }
+        this.elements = result.slice();
+      },
+      rotate: function(angle, v0, v1, v2) {
+        if (!v1) {
+          this.rotateZ(angle);
+        } else {
+          // TODO should make sure this vector is normalized
+          var c = p.cos(angle);
+          var s = p.sin(angle);
+          var t = 1.0 - c;
+
+          this.apply((t * v0 * v0) + c,
+                     (t * v0 * v1) - (s * v2),
+                     (t * v0 * v2) + (s * v1),
+                     0,
+                     (t * v0 * v1) + (s * v2),
+                     (t * v1 * v1) + c,
+                     (t * v1 * v2) - (s * v0),
+                     0,
+                     (t * v0 * v2) - (s * v1),
+                     (t * v1 * v2) + (s * v0),
+                     (t * v2 * v2) + c,
+                     0, 0, 0, 0, 1);
+        }
+      },
+      invApply: function() {
+        if (inverseCopy === undef) {
+          inverseCopy = new PMatrix3D();
+        }
+        var a = arguments;
+        inverseCopy.set(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8],
+                        a[9], a[10], a[11], a[12], a[13], a[14], a[15]);
+
+        if (!inverseCopy.invert()) {
+          return false;
+        }
+        this.preApply(inverseCopy);
+        return true;
+      },
+      rotateX: function(angle) {
+        var c = p.cos(angle);
+        var s = p.sin(angle);
+        this.apply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]);
+      },
+
+      rotateY: function(angle) {
+        var c = p.cos(angle);
+        var s = p.sin(angle);
+        this.apply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]);
+      },
+      rotateZ: function(angle) {
+        var c = Math.cos(angle);
+        var s = Math.sin(angle);
+        this.apply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
+      },
+      // Uniform scaling if only one value passed in
+      scale: function(sx, sy, sz) {
+        if (sx && !sy && !sz) {
+          sy = sz = sx;
+        } else if (sx && sy && !sz) {
+          sz = 1;
+        }
+
+        if (sx && sy && sz) {
+          this.elements[0]  *= sx;
+          this.elements[1]  *= sy;
+          this.elements[2]  *= sz;
+          this.elements[4]  *= sx;
+          this.elements[5]  *= sy;
+          this.elements[6]  *= sz;
+          this.elements[8]  *= sx;
+          this.elements[9]  *= sy;
+          this.elements[10] *= sz;
+          this.elements[12] *= sx;
+          this.elements[13] *= sy;
+          this.elements[14] *= sz;
+        }
+      },
+      skewX: function(angle) {
+        var t = Math.tan(angle);
+        this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+      },
+      skewY: function(angle) {
+        var t = Math.tan(angle);
+        this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+      },
+      multX: function(x, y, z, w) {
+        if (!z) {
+          return this.elements[0] * x + this.elements[1] * y + this.elements[3];
+        } else if (!w) {
+          return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3];
+        } else {
+          return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w;
+        }
+      },
+      multY: function(x, y, z, w) {
+        if (!z) {
+          return this.elements[4] * x + this.elements[5] * y + this.elements[7];
+        } else if (!w) {
+          return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7];
+        } else {
+          return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w;
+        }
+      },
+      multZ: function(x, y, z, w) {
+        if (!w) {
+          return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11];
+        } else {
+          return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w;
+        }
+      },
+      multW: function(x, y, z, w) {
+        if (!w) {
+          return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15];
+        } else {
+          return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w;
+        }
+      },
+      invert: function() {
+        var fA0 = this.elements[0] * this.elements[5] - this.elements[1] * this.elements[4];
+        var fA1 = this.elements[0] * this.elements[6] - this.elements[2] * this.elements[4];
+        var fA2 = this.elements[0] * this.elements[7] - this.elements[3] * this.elements[4];
+        var fA3 = this.elements[1] * this.elements[6] - this.elements[2] * this.elements[5];
+        var fA4 = this.elements[1] * this.elements[7] - this.elements[3] * this.elements[5];
+        var fA5 = this.elements[2] * this.elements[7] - this.elements[3] * this.elements[6];
+        var fB0 = this.elements[8] * this.elements[13] - this.elements[9] * this.elements[12];
+        var fB1 = this.elements[8] * this.elements[14] - this.elements[10] * this.elements[12];
+        var fB2 = this.elements[8] * this.elements[15] - this.elements[11] * this.elements[12];
+        var fB3 = this.elements[9] * this.elements[14] - this.elements[10] * this.elements[13];
+        var fB4 = this.elements[9] * this.elements[15] - this.elements[11] * this.elements[13];
+        var fB5 = this.elements[10] * this.elements[15] - this.elements[11] * this.elements[14];
+
+        // Determinant
+        var fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
+
+        // Account for a very small value
+        // return false if not successful.
+        if (Math.abs(fDet) <= 1e-9) {
+          return false;
+        }
+
+        var kInv = [];
+        kInv[0]  = +this.elements[5] * fB5 - this.elements[6] * fB4 + this.elements[7] * fB3;
+        kInv[4]  = -this.elements[4] * fB5 + this.elements[6] * fB2 - this.elements[7] * fB1;
+        kInv[8]  = +this.elements[4] * fB4 - this.elements[5] * fB2 + this.elements[7] * fB0;
+        kInv[12] = -this.elements[4] * fB3 + this.elements[5] * fB1 - this.elements[6] * fB0;
+        kInv[1]  = -this.elements[1] * fB5 + this.elements[2] * fB4 - this.elements[3] * fB3;
+        kInv[5]  = +this.elements[0] * fB5 - this.elements[2] * fB2 + this.elements[3] * fB1;
+        kInv[9]  = -this.elements[0] * fB4 + this.elements[1] * fB2 - this.elements[3] * fB0;
+        kInv[13] = +this.elements[0] * fB3 - this.elements[1] * fB1 + this.elements[2] * fB0;
+        kInv[2]  = +this.elements[13] * fA5 - this.elements[14] * fA4 + this.elements[15] * fA3;
+        kInv[6]  = -this.elements[12] * fA5 + this.elements[14] * fA2 - this.elements[15] * fA1;
+        kInv[10] = +this.elements[12] * fA4 - this.elements[13] * fA2 + this.elements[15] * fA0;
+        kInv[14] = -this.elements[12] * fA3 + this.elements[13] * fA1 - this.elements[14] * fA0;
+        kInv[3]  = -this.elements[9] * fA5 + this.elements[10] * fA4 - this.elements[11] * fA3;
+        kInv[7]  = +this.elements[8] * fA5 - this.elements[10] * fA2 + this.elements[11] * fA1;
+        kInv[11] = -this.elements[8] * fA4 + this.elements[9] * fA2 - this.elements[11] * fA0;
+        kInv[15] = +this.elements[8] * fA3 - this.elements[9] * fA1 + this.elements[10] * fA0;
+
+        // Inverse using Determinant
+        var fInvDet = 1.0 / fDet;
+        kInv[0]  *= fInvDet;
+        kInv[1]  *= fInvDet;
+        kInv[2]  *= fInvDet;
+        kInv[3]  *= fInvDet;
+        kInv[4]  *= fInvDet;
+        kInv[5]  *= fInvDet;
+        kInv[6]  *= fInvDet;
+        kInv[7]  *= fInvDet;
+        kInv[8]  *= fInvDet;
+        kInv[9]  *= fInvDet;
+        kInv[10] *= fInvDet;
+        kInv[11] *= fInvDet;
+        kInv[12] *= fInvDet;
+        kInv[13] *= fInvDet;
+        kInv[14] *= fInvDet;
+        kInv[15] *= fInvDet;
+
+        this.elements = kInv.slice();
+        return true;
+      },
+      toString: function() {
+        var str = "";
+        for (var i = 0; i < 15; i++) {
+          str += this.elements[i] + ", ";
+        }
+        str += this.elements[15];
+        return str;
+      },
+      print: function() {
+        var digits = printMatrixHelper(this.elements);
+
+        var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) +
+                     " " + p.nfs(this.elements[2], digits, 4) + " " + p.nfs(this.elements[3], digits, 4) +
+                     "\n" + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) +
+                     " " + p.nfs(this.elements[6], digits, 4) + " " + p.nfs(this.elements[7], digits, 4) +
+                     "\n" + p.nfs(this.elements[8], digits, 4) + " " + p.nfs(this.elements[9], digits, 4) +
+                     " " + p.nfs(this.elements[10], digits, 4) + " " + p.nfs(this.elements[11], digits, 4) +
+                     "\n" + p.nfs(this.elements[12], digits, 4) + " " + p.nfs(this.elements[13], digits, 4) +
+                     " " + p.nfs(this.elements[14], digits, 4) + " " + p.nfs(this.elements[15], digits, 4) + "\n\n";
+        p.println(output);
+      },
+      invTranslate: function(tx, ty, tz) {
+        this.preApply(1, 0, 0, -tx, 0, 1, 0, -ty, 0, 0, 1, -tz, 0, 0, 0, 1);
+      },
+      invRotateX: function(angle) {
+        var c = Math.cos(-angle);
+        var s = Math.sin(-angle);
+        this.preApply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]);
+      },
+      invRotateY: function(angle) {
+        var c = Math.cos(-angle);
+        var s = Math.sin(-angle);
+        this.preApply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]);
+      },
+      invRotateZ: function(angle) {
+        var c = Math.cos(-angle);
+        var s = Math.sin(-angle);
+        this.preApply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
+      },
+      invScale: function(x, y, z) {
+        this.preApply([1 / x, 0, 0, 0, 0, 1 / y, 0, 0, 0, 0, 1 / z, 0, 0, 0, 0, 1]);
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Matrix Stack
+    ////////////////////////////////////////////////////////////////////////////
+
+    var PMatrixStack = p.PMatrixStack = function PMatrixStack() {
+      this.matrixStack = [];
+    };
+
+    PMatrixStack.prototype.load = function load() {
+      var tmpMatrix;
+      if (p.use3DContext) {
+        tmpMatrix = new PMatrix3D();
+      } else {
+        tmpMatrix = new PMatrix2D();
+      }
+
+      if (arguments.length === 1) {
+        tmpMatrix.set(arguments[0]);
+      } else {
+        tmpMatrix.set(arguments);
+      }
+      this.matrixStack.push(tmpMatrix);
+    };
+
+    PMatrixStack.prototype.push = function push() {
+      this.matrixStack.push(this.peek());
+    };
+
+    PMatrixStack.prototype.pop = function pop() {
+      return this.matrixStack.pop();
+    };
+
+    PMatrixStack.prototype.peek = function peek() {
+      var tmpMatrix;
+      if (p.use3DContext) {
+        tmpMatrix = new PMatrix3D();
+      } else {
+        tmpMatrix = new PMatrix2D();
+      }
+
+      tmpMatrix.set(this.matrixStack[this.matrixStack.length - 1]);
+      return tmpMatrix;
+    };
+
+    PMatrixStack.prototype.mult = function mult(matrix) {
+      this.matrixStack[this.matrixStack.length - 1].apply(matrix);
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Array handling
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.split = function(str, delim) {
+      return str.split(delim);
+    };
+
+    p.splitTokens = function(str, tokens) {
+      if (arguments.length === 1) {
+        tokens = "\n\t\r\f ";
+      }
+
+      tokens = "[" + tokens + "]";
+
+      var ary = [];
+      var index = 0;
+      var pos = str.search(tokens);
+
+      while (pos >= 0) {
+        if (pos === 0) {
+          str = str.substring(1);
+        } else {
+          ary[index] = str.substring(0, pos);
+          index++;
+          str = str.substring(pos);
+        }
+        pos = str.search(tokens);
+      }
+
+      if (str.length > 0) {
+        ary[index] = str;
+      }
+
+      if (ary.length === 0) {
+        ary = undef;
+      }
+
+      return ary;
+    };
+
+    p.append = function(array, element) {
+      array[array.length] = element;
+      return array;
+    };
+
+    p.concat = function(array1, array2) {
+      return array1.concat(array2);
+    };
+
+    p.sort = function(array, numElem) {
+      var ret = [];
+
+      // depending on the type used (int, float) or string
+      // we'll need to use a different compare function
+      if (array.length > 0) {
+        // copy since we need to return another array
+        var elemsToCopy = numElem > 0 ? numElem : array.length;
+        for (var i = 0; i < elemsToCopy; i++) {
+          ret.push(array[i]);
+        }
+        if (typeof array[0] === "string") {
+          ret.sort();
+        }
+        // int or float
+        else {
+          ret.sort(function(a, b) {
+            return a - b;
+          });
+        }
+
+        // copy on the rest of the elements that were not sorted in case the user
+        // only wanted a subset of an array to be sorted.
+        if (numElem > 0) {
+          for (var j = ret.length; j < array.length; j++) {
+            ret.push(array[j]);
+          }
+        }
+      }
+      return ret;
+    };
+
+    /**
+      splice inserts "value" which can be either a scalar or an array
+      into "array" at position "index".
+    */
+    p.splice = function(array, value, index) {
+
+      // Trying to splice an empty array into "array" in P5 won't do
+      // anything, just return the original.
+      if(value.length === 0)
+      {
+        return array;
+      }
+
+      // If the second argument was an array, we'll need to iterate over all
+      // the "value" elements and add one by one because
+      // array.splice(index, 0, value);
+      // would create a multi-dimensional array which isn't what we want.
+      if(value instanceof Array) {
+        for(var i = 0, j = index; i < value.length; j++,i++) {
+          array.splice(j, 0, value[i]);
+        }
+      } else {
+        array.splice(index, 0, value);
+      }
+
+      return array;
+    };
+
+    p.subset = function(array, offset, length) {
+      if (arguments.length === 2) {
+        return array.slice(offset, array.length - offset);
+      } else if (arguments.length === 3) {
+        return array.slice(offset, offset + length);
+      }
+    };
+
+    p.join = function(array, seperator) {
+      return array.join(seperator);
+    };
+
+    p.shorten = function(ary) {
+      var newary = [];
+
+      // copy array into new array
+      var len = ary.length;
+      for (var i = 0; i < len; i++) {
+        newary[i] = ary[i];
+      }
+      newary.pop();
+
+      return newary;
+    };
+
+    p.expand = function(ary, newSize) {
+      var temp = ary.slice(0);
+      if (arguments.length === 1) {
+        // double size of array
+        temp.length = ary.length * 2;
+        return temp;
+      } else if (arguments.length === 2) {
+        // size is newSize
+        temp.length = newSize;
+        return temp;
+      }
+    };
+
+    p.arrayCopy = function() { // src, srcPos, dest, destPos, length) {
+      var src, srcPos = 0, dest, destPos = 0, length;
+
+      if (arguments.length === 2) {
+        // recall itself and copy src to dest from start index 0 to 0 of src.length
+        src = arguments[0];
+        dest = arguments[1];
+        length = src.length;
+      } else if (arguments.length === 3) {
+        // recall itself and copy src to dest from start index 0 to 0 of length
+        src = arguments[0];
+        dest = arguments[1];
+        length = arguments[2];
+      } else if (arguments.length === 5) {
+        src = arguments[0];
+        srcPos = arguments[1];
+        dest = arguments[2];
+        destPos = arguments[3];
+        length = arguments[4];
+      }
+
+      // copy src to dest from index srcPos to index destPos of length recursivly on objects
+      for (var i = srcPos, j = destPos; i < length + srcPos; i++, j++) {
+        if (dest[j] !== undef) {
+          dest[j] = src[i];
+        } else {
+          throw "array index out of bounds exception";
+        }
+      }
+    };
+
+    p.reverse = function(array) {
+      return array.reverse();
+    };
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Color functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    // helper functions for internal blending modes
+    p.mix = function(a, b, f) {
+      return a + (((b - a) * f) >> 8);
+    };
+
+    p.peg = function(n) {
+      return (n < 0) ? 0 : ((n > 255) ? 255 : n);
+    };
+
+    // blending modes
+    p.modes = {
+      replace: function(c1, c2) {
+        return c2;
+      },
+      blend: function(c1, c2) {
+        var f = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                p.mix(c1 & PConstants.RED_MASK, c2 & PConstants.RED_MASK, f) & PConstants.RED_MASK |
+                p.mix(c1 & PConstants.GREEN_MASK, c2 & PConstants.GREEN_MASK, f) & PConstants.GREEN_MASK |
+                p.mix(c1 & PConstants.BLUE_MASK, c2 & PConstants.BLUE_MASK, f));
+      },
+      add: function(c1, c2) {
+        var f = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                Math.min(((c1 & PConstants.RED_MASK) + ((c2 & PConstants.RED_MASK) >> 8) * f), PConstants.RED_MASK) & PConstants.RED_MASK |
+                Math.min(((c1 & PConstants.GREEN_MASK) + ((c2 & PConstants.GREEN_MASK) >> 8) * f), PConstants.GREEN_MASK) & PConstants.GREEN_MASK |
+                Math.min((c1 & PConstants.BLUE_MASK) + (((c2 & PConstants.BLUE_MASK) * f) >> 8), PConstants.BLUE_MASK));
+      },
+      subtract: function(c1, c2) {
+        var f = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                Math.max(((c1 & PConstants.RED_MASK) - ((c2 & PConstants.RED_MASK) >> 8) * f), PConstants.GREEN_MASK) & PConstants.RED_MASK |
+                Math.max(((c1 & PConstants.GREEN_MASK) - ((c2 & PConstants.GREEN_MASK) >> 8) * f), PConstants.BLUE_MASK) & PConstants.GREEN_MASK |
+                Math.max((c1 & PConstants.BLUE_MASK) - (((c2 & PConstants.BLUE_MASK) * f) >> 8), 0));
+      },
+      lightest: function(c1, c2) {
+        var f = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                Math.max(c1 & PConstants.RED_MASK, ((c2 & PConstants.RED_MASK) >> 8) * f) & PConstants.RED_MASK |
+                Math.max(c1 & PConstants.GREEN_MASK, ((c2 & PConstants.GREEN_MASK) >> 8) * f) & PConstants.GREEN_MASK |
+                Math.max(c1 & PConstants.BLUE_MASK, ((c2 & PConstants.BLUE_MASK) * f) >> 8));
+      },
+      darkest: function(c1, c2) {
+        var f = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                p.mix(c1 & PConstants.RED_MASK, Math.min(c1 & PConstants.RED_MASK, ((c2 & PConstants.RED_MASK) >> 8) * f), f) & PConstants.RED_MASK |
+                p.mix(c1 & PConstants.GREEN_MASK, Math.min(c1 & PConstants.GREEN_MASK, ((c2 & PConstants.GREEN_MASK) >> 8) * f), f) & PConstants.GREEN_MASK |
+                p.mix(c1 & PConstants.BLUE_MASK, Math.min(c1 & PConstants.BLUE_MASK, ((c2 & PConstants.BLUE_MASK) * f) >> 8), f));
+      },
+      difference: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = (ar > br) ? (ar - br) : (br - ar);
+        var cg = (ag > bg) ? (ag - bg) : (bg - ag);
+        var cb = (ab > bb) ? (ab - bb) : (bb - ab);
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      exclusion: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = ar + br - ((ar * br) >> 7);
+        var cg = ag + bg - ((ag * bg) >> 7);
+        var cb = ab + bb - ((ab * bb) >> 7);
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      multiply: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = (ar * br) >> 8;
+        var cg = (ag * bg) >> 8;
+        var cb = (ab * bb) >> 8;
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      screen: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = 255 - (((255 - ar) * (255 - br)) >> 8);
+        var cg = 255 - (((255 - ag) * (255 - bg)) >> 8);
+        var cb = 255 - (((255 - ab) * (255 - bb)) >> 8);
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      hard_light: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = (br < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7));
+        var cg = (bg < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7));
+        var cb = (bb < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7));
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      soft_light: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = ((ar * br) >> 7) + ((ar * ar) >> 8) - ((ar * ar * br) >> 15);
+        var cg = ((ag * bg) >> 7) + ((ag * ag) >> 8) - ((ag * ag * bg) >> 15);
+        var cb = ((ab * bb) >> 7) + ((ab * ab) >> 8) - ((ab * ab * bb) >> 15);
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      overlay: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = (ar < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7));
+        var cg = (ag < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7));
+        var cb = (ab < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7));
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      dodge: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = (br === 255) ? 255 : p.peg((ar << 8) / (255 - br)); // division requires pre-peg()-ing
+        var cg = (bg === 255) ? 255 : p.peg((ag << 8) / (255 - bg)); // "
+        var cb = (bb === 255) ? 255 : p.peg((ab << 8) / (255 - bb)); // "
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      },
+      burn: function(c1, c2) {
+        var f  = (c2 & PConstants.ALPHA_MASK) >>> 24;
+        var ar = (c1 & PConstants.RED_MASK) >> 16;
+        var ag = (c1 & PConstants.GREEN_MASK) >> 8;
+        var ab = (c1 & PConstants.BLUE_MASK);
+        var br = (c2 & PConstants.RED_MASK) >> 16;
+        var bg = (c2 & PConstants.GREEN_MASK) >> 8;
+        var bb = (c2 & PConstants.BLUE_MASK);
+        // formula:
+        var cr = (br === 0) ? 0 : 255 - p.peg(((255 - ar) << 8) / br); // division requires pre-peg()-ing
+        var cg = (bg === 0) ? 0 : 255 - p.peg(((255 - ag) << 8) / bg); // "
+        var cb = (bb === 0) ? 0 : 255 - p.peg(((255 - ab) << 8) / bb); // "
+        // alpha blend (this portion will always be the same)
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                (p.peg(ar + (((cr - ar) * f) >> 8)) << 16) |
+                (p.peg(ag + (((cg - ag) * f) >> 8)) << 8) |
+                (p.peg(ab + (((cb - ab) * f) >> 8))));
+      }
+    };
+
+    function color$4(aValue1, aValue2, aValue3, aValue4) {
+      var r, g, b, a;
+
+      if (curColorMode === PConstants.HSB) {
+        var rgb = p.color.toRGB(aValue1, aValue2, aValue3);
+        r = rgb[0];
+        g = rgb[1];
+        b = rgb[2];
+      } else {
+        r = Math.round(255 * (aValue1 / colorModeX));
+        g = Math.round(255 * (aValue2 / colorModeY));
+        b = Math.round(255 * (aValue3 / colorModeZ));
+      }
+
+      a = Math.round(255 * (aValue4 / colorModeA));
+
+      // Limit values greater than 255
+      r = (r > 255) ? 255 : r;
+      g = (g > 255) ? 255 : g;
+      b = (b > 255) ? 255 : b;
+      a = (a > 255) ? 255 : a;
+
+      // Create color int
+      return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK;
+    }
+
+    function color$2(aValue1, aValue2) {
+      var a;
+
+      // Color int and alpha
+      if (aValue1 & PConstants.ALPHA_MASK) {
+        a = Math.round(255 * (aValue2 / colorModeA));
+        a = (a > 255) ? 255 : a;
+
+        return aValue1 - (aValue1 & PConstants.ALPHA_MASK) + ((a << 24) & PConstants.ALPHA_MASK);
+      }
+      // Grayscale and alpha
+      else {
+        if (curColorMode === PConstants.RGB) {
+          return color$4(aValue1, aValue1, aValue1, aValue2);
+        } else if (curColorMode === PConstants.HSB) {
+          return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, aValue2);
+        }
+      }
+    }
+
+    function color$1(aValue1) {
+      // Grayscale
+      if (aValue1 <= colorModeX && aValue1 >= 0) {
+          if (curColorMode === PConstants.RGB) {
+            return color$4(aValue1, aValue1, aValue1, colorModeA);
+          } else if (curColorMode === PConstants.HSB) {
+            return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, colorModeA);
+          }
+      }
+      // Color int
+      else if (aValue1) {
+        return aValue1;
+      }
+    }
+
+    p.color = function color(aValue1, aValue2, aValue3, aValue4) {
+      // 4 arguments: (R, G, B, A) or (H, S, B, A)
+      if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef && aValue4 !== undef) {
+        return color$4(aValue1, aValue2, aValue3, aValue4);
+      }
+
+      // 3 arguments: (R, G, B) or (H, S, B)
+      else if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef) {
+        return color$4(aValue1, aValue2, aValue3, colorModeA);
+      }
+
+      // 2 arguments: (Color, A) or (Grayscale, A)
+      else if (aValue1 !== undef && aValue2 !== undef) {
+        return color$2(aValue1, aValue2);
+      }
+
+      // 1 argument: (Grayscale) or (Color)
+      else if (typeof aValue1 === "number") {
+        return color$1(aValue1);
+      }
+
+      // Default
+      else {
+        return color$4(colorModeX, colorModeY, colorModeZ, colorModeA);
+      }
+    };
+
+    // Ease of use function to extract the colour bits into a string
+    p.color.toString = function(colorInt) {
+      return "rgba(" + ((colorInt & PConstants.RED_MASK) >>> 16) + "," + ((colorInt & PConstants.GREEN_MASK) >>> 8) +
+             "," + ((colorInt & PConstants.BLUE_MASK)) + "," + ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255 + ")";
+    };
+
+    // Easy of use function to pack rgba values into a single bit-shifted color int.
+    p.color.toInt = function(r, g, b, a) {
+      return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK;
+    };
+
+    // Creates a simple array in [R, G, B, A] format, [255, 255, 255, 255]
+    p.color.toArray = function(colorInt) {
+      return [(colorInt & PConstants.RED_MASK) >>> 16, (colorInt & PConstants.GREEN_MASK) >>> 8,
+              colorInt & PConstants.BLUE_MASK, (colorInt & PConstants.ALPHA_MASK) >>> 24];
+    };
+
+    // Creates a WebGL color array in [R, G, B, A] format. WebGL wants the color ranges between 0 and 1, [1, 1, 1, 1]
+    p.color.toGLArray = function(colorInt) {
+      return [((colorInt & PConstants.RED_MASK) >>> 16) / 255, ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255,
+              (colorInt & PConstants.BLUE_MASK) / 255, ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255];
+    };
+
+    // HSB conversion function from Mootools, MIT Licensed
+    p.color.toRGB = function(h, s, b) {
+      // Limit values greater than range
+      h = (h > colorModeX) ? colorModeX : h;
+      s = (s > colorModeY) ? colorModeY : s;
+      b = (b > colorModeZ) ? colorModeZ : b;
+
+      h = (h / colorModeX) * 360;
+      s = (s / colorModeY) * 100;
+      b = (b / colorModeZ) * 100;
+
+      var br = Math.round(b / 100 * 255);
+
+      if (s === 0) { // Grayscale
+        return [br, br, br];
+      } else {
+        var hue = h % 360;
+        var f = hue % 60;
+        var p = Math.round((b * (100 - s)) / 10000 * 255);
+        var q = Math.round((b * (6000 - s * f)) / 600000 * 255);
+        var t = Math.round((b * (6000 - s * (60 - f))) / 600000 * 255);
+        switch (Math.floor(hue / 60)) {
+        case 0:
+          return [br, t, p];
+        case 1:
+          return [q, br, p];
+        case 2:
+          return [p, br, t];
+        case 3:
+          return [p, q, br];
+        case 4:
+          return [t, p, br];
+        case 5:
+          return [br, p, q];
+        }
+      }
+    };
+
+    p.color.toHSB = function( colorInt ) {
+      var red, green, blue;
+
+      red   = ((colorInt & PConstants.RED_MASK) >>> 16) / 255;
+      green = ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255;
+      blue  = (colorInt & PConstants.BLUE_MASK) / 255;
+
+      var max = p.max(p.max(red,green), blue),
+          min = p.min(p.min(red,green), blue),
+          hue, saturation;
+
+      if (min === max) {
+        return [0, 0, max];
+      } else {
+        saturation = (max - min) / max;
+
+        if (red === max) {
+          hue = (green - blue) / (max - min);
+        } else if (green === max) {
+          hue = 2 + ((blue - red) / (max - min));
+        } else {
+          hue = 4 + ((red - green) / (max - min));
+        }
+
+        hue /= 6;
+
+        if (hue < 0) {
+          hue += 1;
+        } else if (hue > 1) {
+          hue -= 1;
+        }
+      }
+      return [hue*colorModeX, saturation*colorModeY, max*colorModeZ];
+    };
+
+    p.brightness = function(colInt){
+      return  p.color.toHSB(colInt)[2];
+    };
+
+    p.saturation = function(colInt){
+      return  p.color.toHSB(colInt)[1];
+    };
+
+    p.hue = function(colInt){
+      return  p.color.toHSB(colInt)[0];
+    };
+
+    var verifyChannel = function verifyChannel(aColor) {
+      if (aColor.constructor === Array) {
+        return aColor;
+      } else {
+        return p.color(aColor);
+      }
+    };
+
+    p.red = function(aColor) {
+      return ((aColor & PConstants.RED_MASK) >>> 16) / 255 * colorModeX;
+    };
+
+    p.green = function(aColor) {
+      return ((aColor & PConstants.GREEN_MASK) >>> 8) / 255 * colorModeY;
+    };
+
+    p.blue = function(aColor) {
+      return (aColor & PConstants.BLUE_MASK) / 255 * colorModeZ;
+    };
+
+    p.alpha = function(aColor) {
+      return ((aColor & PConstants.ALPHA_MASK) >>> 24) / 255 * colorModeA;
+    };
+
+    p.lerpColor = function lerpColor(c1, c2, amt) {
+      // Get RGBA values for Color 1 to floats
+      var colorBits1 = p.color(c1);
+      var r1 = (colorBits1 & PConstants.RED_MASK) >>> 16;
+      var g1 = (colorBits1 & PConstants.GREEN_MASK) >>> 8;
+      var b1 = (colorBits1 & PConstants.BLUE_MASK);
+      var a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA;
+
+      // Get RGBA values for Color 2 to floats
+      var colorBits2 = p.color(c2);
+      var r2 = (colorBits2 & PConstants.RED_MASK) >>> 16;
+      var g2 = (colorBits2 & PConstants.GREEN_MASK) >>> 8;
+      var b2 = (colorBits2 & PConstants.BLUE_MASK);
+      var a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA;
+
+      // Return lerp value for each channel, INT for color, Float for Alpha-range
+      var r = parseInt(p.lerp(r1, r2, amt), 10);
+      var g = parseInt(p.lerp(g1, g2, amt), 10);
+      var b = parseInt(p.lerp(b1, b2, amt), 10);
+      var a = parseFloat(p.lerp(a1, a2, amt) * colorModeA);
+
+      return p.color.toInt(r, g, b, a);
+    };
+
+    // Forced default color mode for #aaaaaa style
+    p.defaultColor = function(aValue1, aValue2, aValue3) {
+      var tmpColorMode = curColorMode;
+      curColorMode = PConstants.RGB;
+      var c = p.color(aValue1 / 255 * colorModeX, aValue2 / 255 * colorModeY, aValue3 / 255 * colorModeZ);
+      curColorMode = tmpColorMode;
+      return c;
+    };
+
+    p.colorMode = function colorMode() { // mode, range1, range2, range3, range4
+      curColorMode = arguments[0];
+      if (arguments.length > 1) {
+        colorModeX   = arguments[1];
+        colorModeY   = arguments[2] || arguments[1];
+        colorModeZ   = arguments[3] || arguments[1];
+        colorModeA   = arguments[4] || arguments[1];
+      }
+    };
+
+    p.blendColor = function(c1, c2, mode) {
+      var color = 0;
+      switch (mode) {
+      case PConstants.REPLACE:
+        color = p.modes.replace(c1, c2);
+        break;
+      case PConstants.BLEND:
+        color = p.modes.blend(c1, c2);
+        break;
+      case PConstants.ADD:
+        color = p.modes.add(c1, c2);
+        break;
+      case PConstants.SUBTRACT:
+        color = p.modes.subtract(c1, c2);
+        break;
+      case PConstants.LIGHTEST:
+        color = p.modes.lightest(c1, c2);
+        break;
+      case PConstants.DARKEST:
+        color = p.modes.darkest(c1, c2);
+        break;
+      case PConstants.DIFFERENCE:
+        color = p.modes.difference(c1, c2);
+        break;
+      case PConstants.EXCLUSION:
+        color = p.modes.exclusion(c1, c2);
+        break;
+      case PConstants.MULTIPLY:
+        color = p.modes.multiply(c1, c2);
+        break;
+      case PConstants.SCREEN:
+        color = p.modes.screen(c1, c2);
+        break;
+      case PConstants.HARD_LIGHT:
+        color = p.modes.hard_light(c1, c2);
+        break;
+      case PConstants.SOFT_LIGHT:
+        color = p.modes.soft_light(c1, c2);
+        break;
+      case PConstants.OVERLAY:
+        color = p.modes.overlay(c1, c2);
+        break;
+      case PConstants.DODGE:
+        color = p.modes.dodge(c1, c2);
+        break;
+      case PConstants.BURN:
+        color = p.modes.burn(c1, c2);
+        break;
+      }
+      return color;
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Canvas-Matrix manipulation
+    ////////////////////////////////////////////////////////////////////////////
+
+    function saveContext() {
+      curContext.save();
+    }
+
+    function restoreContext() {
+      curContext.restore();
+      isStrokeDirty = true;
+      isFillDirty = true;
+    }
+
+    p.printMatrix = function printMatrix() {
+      modelView.print();
+    };
+
+    p.translate = function translate(x, y, z) {
+      if (p.use3DContext) {
+        forwardTransform.translate(x, y, z);
+        reverseTransform.invTranslate(x, y, z);
+      } else {
+        curContext.translate(x, y);
+      }
+    };
+
+    p.scale = function scale(x, y, z) {
+      if (p.use3DContext) {
+        forwardTransform.scale(x, y, z);
+        reverseTransform.invScale(x, y, z);
+      } else {
+        curContext.scale(x, y || x);
+      }
+    };
+
+    p.pushMatrix = function pushMatrix() {
+      if (p.use3DContext) {
+        userMatrixStack.load(modelView);
+      } else {
+        saveContext();
+      }
+    };
+
+    p.popMatrix = function popMatrix() {
+      if (p.use3DContext) {
+        modelView.set(userMatrixStack.pop());
+      } else {
+        restoreContext();
+      }
+    };
+
+    p.resetMatrix = function resetMatrix() {
+      if (p.use3DContext) {
+        forwardTransform.reset();
+        reverseTransform.reset();
+      } else {
+        curContext.setTransform(1,0,0,1,0,0);
+      }
+    };
+
+    p.applyMatrix = function applyMatrix() {
+      var a = arguments;
+      if (!p.use3DContext) {
+        for (var cnt = a.length; cnt < 16; cnt++) {
+          a[cnt] = 0;
+        }
+        a[10] = a[15] = 1;
+      }
+
+      forwardTransform.apply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]);
+      reverseTransform.invApply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]);
+    };
+
+    p.rotateX = function(angleInRadians) {
+      forwardTransform.rotateX(angleInRadians);
+      reverseTransform.invRotateX(angleInRadians);
+    };
+
+    p.rotateZ = function(angleInRadians) {
+      forwardTransform.rotateZ(angleInRadians);
+      reverseTransform.invRotateZ(angleInRadians);
+    };
+
+    p.rotateY = function(angleInRadians) {
+      forwardTransform.rotateY(angleInRadians);
+      reverseTransform.invRotateY(angleInRadians);
+    };
+
+    p.rotate = function rotate(angleInRadians) {
+      if (p.use3DContext) {
+        forwardTransform.rotateZ(angleInRadians);
+        reverseTransform.invRotateZ(angleInRadians);
+      } else {
+        curContext.rotate(angleInRadians);
+      }
+    };
+
+    p.pushStyle = function pushStyle() {
+      // Save the canvas state.
+      saveContext();
+
+      p.pushMatrix();
+
+      var newState = {
+        'doFill': doFill,
+        'currentFillColor': currentFillColor,
+        'doStroke': doStroke,
+        'currentStrokeColor': currentStrokeColor,
+        'curTint': curTint,
+        'curRectMode': curRectMode,
+        'curColorMode': curColorMode,
+        'colorModeX': colorModeX,
+        'colorModeZ': colorModeZ,
+        'colorModeY': colorModeY,
+        'colorModeA': colorModeA,
+        'curTextFont': curTextFont,
+        'curTextSize': curTextSize
+      };
+
+      styleArray.push(newState);
+    };
+
+    p.popStyle = function popStyle() {
+      var oldState = styleArray.pop();
+
+      if (oldState) {
+        restoreContext();
+
+        p.popMatrix();
+
+        doFill = oldState.doFill;
+        currentFillColor = oldState.currentFillColor;
+        doStroke = oldState.doStroke;
+        currentStrokeColor = oldState.currentStrokeColor;
+        curTint = oldState.curTint;
+        curRectMode = oldState.curRectmode;
+        curColorMode = oldState.curColorMode;
+        colorModeX = oldState.colorModeX;
+        colorModeZ = oldState.colorModeZ;
+        colorModeY = oldState.colorModeY;
+        colorModeA = oldState.colorModeA;
+        curTextFont = oldState.curTextFont;
+        curTextSize = oldState.curTextSize;
+      } else {
+        throw "Too many popStyle() without enough pushStyle()";
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Time based functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.year = function year() {
+      return new Date().getFullYear();
+    };
+    p.month = function month() {
+      return new Date().getMonth() + 1;
+    };
+    p.day = function day() {
+      return new Date().getDate();
+    };
+    p.hour = function hour() {
+      return new Date().getHours();
+    };
+    p.minute = function minute() {
+      return new Date().getMinutes();
+    };
+    p.second = function second() {
+      return new Date().getSeconds();
+    };
+    p.millis = function millis() {
+      return new Date().getTime() - start;
+    };
+
+    p.redraw = function redraw() {
+      var sec = (new Date().getTime() - timeSinceLastFPS) / 1000;
+      framesSinceLastFPS++;
+      var fps = framesSinceLastFPS / sec;
+
+      // recalculate FPS every half second for better accuracy.
+      if (sec > 0.5) {
+        timeSinceLastFPS = new Date().getTime();
+        framesSinceLastFPS = 0;
+        p.__frameRate = fps;
+      }
+
+      p.frameCount++;
+
+      inDraw = true;
+
+      if (p.use3DContext) {
+        // even if the color buffer isn't cleared with background(),
+        // the depth buffer needs to be cleared regardless.
+        curContext.clear(curContext.DEPTH_BUFFER_BIT);
+        // Delete all the lighting states and the materials the
+        // user set in the last draw() call.
+        p.noLights();
+        p.lightFalloff(1, 0, 0);
+        p.shininess(1);
+        p.ambient(255, 255, 255);
+        p.specular(0, 0, 0);
+        p.camera();
+        p.draw();
+      } else {
+        saveContext();
+        p.draw();
+        restoreContext();
+      }
+
+      inDraw = false;
+    };
+
+    p.noLoop = function noLoop() {
+      doLoop = false;
+      loopStarted = false;
+      clearInterval(looping);
+    };
+
+    p.loop = function loop() {
+      if (loopStarted) {
+        return;
+      }
+
+      looping = window.setInterval(function() {
+        try {
+          if (document.hasFocus instanceof Function) {
+            p.focused = document.hasFocus();
+          }
+          p.redraw();
+        } catch(e_loop) {
+          window.clearInterval(looping);
+          throw e_loop;
+        }
+      }, curMsPerFrame);
+
+      doLoop = true;
+      loopStarted = true;
+    };
+
+    p.frameRate = function frameRate(aRate) {
+      curFrameRate = aRate;
+      curMsPerFrame = 1000 / curFrameRate;
+
+      // clear and reset interval
+      if (doLoop) {
+        p.noLoop();
+        p.loop();
+      }
+    };
+
+    var eventHandlers = [];
+
+    p.exit = function exit() {
+      window.clearInterval(looping);
+
+      Processing.removeInstance(p.externals.canvas.id);
+
+      for (var i=0, ehl=eventHandlers.length; i<ehl; i++) {
+        var elem = eventHandlers[i][0],
+            type = eventHandlers[i][1],
+            fn   = eventHandlers[i][2];
+
+        if (elem.removeEventListener) {
+          elem.removeEventListener(type, fn, false);
+        } else if (elem.detachEvent) {
+          elem.detachEvent("on" + type, fn);
+        }
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // MISC functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.cursor = function cursor() {
+      if (arguments.length > 1 || (arguments.length === 1 && arguments[0] instanceof p.PImage)) {
+        var image = arguments[0],
+          x, y;
+        if (arguments.length >= 3) {
+          x = arguments[1];
+          y = arguments[2];
+          if (x < 0 || y < 0 || y >= image.height || x >= image.width) {
+            throw "x and y must be non-negative and less than the dimensions of the image";
+          }
+        } else {
+          x = image.width >>> 1;
+          y = image.height >>> 1;
+        }
+
+        // see https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property
+        var imageDataURL = image.toDataURL();
+        var style = "url(\"" + imageDataURL + "\") " + x + " " + y + ", default";
+        curCursor = curElement.style.cursor = style;
+      } else if (arguments.length === 1) {
+        var mode = arguments[0];
+        curCursor = curElement.style.cursor = mode;
+      } else {
+        curCursor = curElement.style.cursor = oldCursor;
+      }
+    };
+
+    p.noCursor = function noCursor() {
+      curCursor = curElement.style.cursor = PConstants.NOCURSOR;
+    };
+
+    p.link = function(href, target) {
+      if (target !== undef) {
+        window.open(href, target);
+      } else {
+        window.location = href;
+      }
+    };
+
+    // PGraphics methods
+    // TODO: These functions are suppose to be called before any operations are called on the
+    //       PGraphics object. They currently do nothing.
+    p.beginDraw = function beginDraw() {};
+    p.endDraw = function endDraw() {};
+
+    // Imports an external Processing.js library
+    p.Import = function Import(lib) {
+      // Replace evil-eval method with a DOM <script> tag insert method that
+      // binds new lib code to the Processing.lib names-space and the current
+      // p context. -F1LT3R
+    };
+
+    var contextMenu = function(e) {
+      e.preventDefault();
+      e.stopPropagation();
+    };
+
+    p.disableContextMenu = function disableContextMenu() {
+      curElement.addEventListener('contextmenu', contextMenu, false);
+    };
+
+    p.enableContextMenu = function enableContextMenu() {
+      curElement.removeEventListener('contextmenu', contextMenu, false);
+    };
+
+    p.status = function(text) {
+      window.status = text;
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Binary Functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    function decToBin(value, numBitsInValue) {
+      var mask = 1;
+      mask = mask << (numBitsInValue - 1);
+
+      var str = "";
+      for (var i = 0; i < numBitsInValue; i++) {
+        str += (mask & value) ? "1" : "0";
+        mask = mask >>> 1;
+      }
+      return str;
+    }
+
+    /*
+      This function does not always work when trying to convert
+      colors and bytes to binary values because the types passed in
+      cannot be determined.
+    */
+    p.binary = function(num, numBits) {
+      var numBitsInValue = 32;
+
+      // color, int, byte
+      if (typeof num === "number") {
+        if(numBits){
+          numBitsInValue = numBits;
+        }
+        return decToBin(num, numBitsInValue);
+      }
+
+      // char
+      if (num instanceof Char) {
+        num = num.toString().charCodeAt(0);
+        if (numBits) {
+          numBitsInValue = 32;
+        } else {
+          numBitsInValue = 16;
+        }
+      }
+
+      var str = decToBin(num, numBitsInValue);
+
+      // trim string if user wanted less chars
+      if (numBits) {
+        str = str.substr(-numBits);
+      }
+      return str;
+    };
+
+    p.unbinary = function unbinary(binaryString) {
+      var binaryPattern = new RegExp("^[0|1]{8}$");
+      var addUp = 0;
+      var i;
+
+      if (binaryString instanceof Array) {
+        var values = [];
+        for (i = 0; i < binaryString.length; i++) {
+          values[i] = p.unbinary(binaryString[i]);
+        }
+        return values;
+      } else {
+        if (isNaN(binaryString)) {
+          throw "NaN_Err";
+        } else {
+          if (arguments.length === 1 || binaryString.length === 8) {
+            if (binaryPattern.test(binaryString)) {
+              for (i = 0; i < 8; i++) {
+                addUp += (Math.pow(2, i) * parseInt(binaryString.charAt(7 - i), 10));
+              }
+              return addUp + "";
+            } else {
+              throw "notBinary: the value passed into unbinary was not an 8 bit binary number";
+            }
+          } else {
+            throw "longErr";
+          }
+        }
+      }
+    };
+
+    function nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group) {
+      var sign = (value < 0) ? minus : plus;
+      var autoDetectDecimals = rightDigits === 0;
+      var rightDigitsOfDefault = (rightDigits === undef || rightDigits < 0) ? 0 : rightDigits;
+
+
+      // Change the way the number is 'floored' based on whether it is odd or even.
+      if (rightDigits < 0 && Math.floor(value) % 2 === 1) {
+        // Make sure 1.49 rounds to 1, but 1.5 rounds to 2.
+        if ((value - Math.floor(value)) >= 0.5) {
+          value += 1;
+        }
+      }
+
+
+      var absValue = Math.abs(value);
+      if(autoDetectDecimals) {
+        rightDigitsOfDefault = 1;
+        absValue *= 10;
+        while(Math.abs(Math.round(absValue) - absValue) > 1e-6 && rightDigitsOfDefault < 7) {
+          ++rightDigitsOfDefault;
+          absValue *= 10;
+        }
+      } else if (rightDigitsOfDefault !== 0) {
+        absValue *= Math.pow(10, rightDigitsOfDefault);
+      }
+
+      var number = Math.round(absValue);
+      var buffer = "";
+      var totalDigits = leftDigits + rightDigitsOfDefault;
+      while (totalDigits > 0 || number > 0) {
+        totalDigits--;
+        buffer = "" + (number % 10) + buffer;
+        number = Math.floor(number / 10);
+      }
+      if (group !== undef) {
+        var i = buffer.length - 3 - rightDigitsOfDefault;
+        while(i > 0) {
+          buffer = buffer.substring(0,i) + group + buffer.substring(i);
+          i-=3;
+        }
+      }
+      if (rightDigitsOfDefault > 0) {
+        return sign + buffer.substring(0, buffer.length - rightDigitsOfDefault) +
+               "." + buffer.substring(buffer.length - rightDigitsOfDefault, buffer.length);
+      } else {
+         return sign + buffer;
+      }
+    }
+
+    function nfCore(value, plus, minus, leftDigits, rightDigits, group) {
+      if (value instanceof Array) {
+        var arr = [];
+        for (var i = 0, len = value.length; i < len; i++) {
+          arr.push(nfCoreScalar(value[i], plus, minus, leftDigits, rightDigits, group));
+        }
+        return arr;
+      } else {
+        return nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group);
+      }
+    }
+
+    // TODO: drop this and use nfCore (see below) code when we've fixed the rounding bug in nfCore
+    p.nf = function() {
+      var str, num, pad, arr, left, right, isNegative, test, i;
+
+      if (arguments.length === 2 && typeof arguments[0] === 'number' && typeof arguments[1] === 'number' && (arguments[0] + "").indexOf('.') === -1) {
+        num = arguments[0];
+        pad = arguments[1];
+
+        isNegative = num < 0;
+
+        if (isNegative) {
+          num = Math.abs(num);
+        }
+
+        str = "" + num;
+        for (i = pad - str.length; i > 0; i--) {
+          str = "0" + str;
+        }
+
+        if (isNegative) {
+          str = "-" + str;
+        }
+      } else if (arguments.length === 2 && typeof arguments[0] === 'object' && arguments[0].constructor === Array && typeof arguments[1] === 'number') {
+        arr = arguments[0];
+        pad = arguments[1];
+
+        str = new Array(arr.length);
+
+        for (i = 0; i < arr.length && str !== undef; i++) {
+          test = p.nf(arr[i], pad);
+          if (test === undef) {
+            str = undef;
+          } else {
+            str[i] = test;
+          }
+        }
+      } else if (arguments.length === 3 && typeof arguments[0] === 'number' && typeof arguments[1] === 'number' && typeof arguments[2] === 'number' && (arguments[0] + "").indexOf('.') >= 0) {
+        num = arguments[0];
+        left = arguments[1];
+        right = arguments[2];
+
+        isNegative = num < 0;
+
+        if (isNegative) {
+          num = Math.abs(num);
+        }
+
+        // Change the way the number is 'floored' based on whether it is odd or even.
+        if (right < 0 && Math.floor(num) % 2 === 1) {
+          // Make sure 1.49 rounds to 1, but 1.5 rounds to 2.
+          if ((num) - Math.floor(num) >= 0.5) {
+            num = num + 1;
+          }
+        }
+
+        str = "" + num;
+
+        for (i = left - str.indexOf('.'); i > 0; i--) {
+          str = "0" + str;
+        }
+
+        var numDec = str.length - str.indexOf('.') - 1;
+        if (numDec <= right) {
+          for (i = right - (str.length - str.indexOf('.') - 1); i > 0; i--) {
+            str = str + "0";
+          }
+        } else if (right > 0) {
+          str = str.substring(0, str.length - (numDec - right));
+        } else if (right < 0) {
+
+          str = str.substring(0, str.indexOf('.'));
+        }
+
+        if (isNegative) {
+          str = "-" + str;
+        }
+      } else if (arguments.length === 3 && typeof arguments[0] === 'object' && arguments[0].constructor === Array && typeof arguments[1] === 'number' && typeof arguments[2] === 'number') {
+        arr = arguments[0];
+        left = arguments[1];
+        right = arguments[2];
+
+        str = new Array(arr.length);
+
+        for (i = 0; i < arr.length && str !== undef; i++) {
+          test = p.nf(arr[i], left, right);
+          if (test === undef) {
+            str = undef;
+          } else {
+            str[i] = test;
+          }
+        }
+      }
+
+      return str;
+    };
+
+// TODO: need to switch nf over to using nfCore...
+//    p.nf  = function(value, leftDigits, rightDigits) { return nfCore(value, "", "-", leftDigits, rightDigits); };
+    p.nfs = function(value, leftDigits, rightDigits) { return nfCore(value, " ", "-", leftDigits, rightDigits); };
+    p.nfp = function(value, leftDigits, rightDigits) { return nfCore(value, "+", "-", leftDigits, rightDigits); };
+    p.nfc = function(value, leftDigits, rightDigits) { return nfCore(value, "", "-", leftDigits, rightDigits, ","); };
+
+    var decimalToHex = function decimalToHex(d, padding) {
+      //if there is no padding value added, default padding to 8 else go into while statement.
+      padding = (padding === undef || padding === null) ? padding = 8 : padding;
+      if (d < 0) {
+        d = 0xFFFFFFFF + d + 1;
+      }
+      var hex = Number(d).toString(16).toUpperCase();
+      while (hex.length < padding) {
+        hex = "0" + hex;
+      }
+      if (hex.length >= padding) {
+        hex = hex.substring(hex.length - padding, hex.length);
+      }
+      return hex;
+    };
+
+    // note: since we cannot keep track of byte, int types by default the returned string is 8 chars long
+    // if no 2nd argument is passed.  closest compromise we can use to match java implementation Feb 5 2010
+    // also the char parser has issues with chars that are not digits or letters IE: !@#$%^&*
+    p.hex = function hex(value, len) {
+      if (arguments.length === 1) {
+        if (value instanceof Char) {
+          len = 4;
+        } else { // int or byte, indistinguishable at the moment, default to 8
+          len = 8;
+        }
+      }
+      return decimalToHex(value, len);
+    };
+
+    function unhexScalar(hex) {
+      var value = parseInt("0x" + hex, 16);
+
+      // correct for int overflow java expectation
+      if (value > 2147483647) {
+        value -= 4294967296;
+      }
+      return value;
+    }
+
+    p.unhex = function(hex) {
+      if (hex instanceof Array) {
+        var arr = [];
+        for (var i = 0; i < hex.length; i++) {
+          arr.push(unhexScalar(hex[i]));
+        }
+        return arr;
+      } else {
+        return unhexScalar(hex);
+      }
+    };
+
+    // Load a file or URL into strings
+    p.loadStrings = function loadStrings(filename) {
+      return (localStorage[filename] ? localStorage[filename] : ajax(filename).slice(0, -1)).split("\n");
+    };
+
+    // Writes an array of strings to a file, one line per string
+    p.saveStrings = function saveStrings(filename, strings) {
+      localStorage[filename] = strings.join('\n');
+    };
+
+    p.loadBytes = function loadBytes(url, strings) {
+      var string = ajax(url);
+      var ret = [];
+
+      for (var i = 0; i < string.length; i++) {
+        ret.push(string.charCodeAt(i));
+      }
+
+      return ret;
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // String Functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.matchAll = function matchAll(aString, aRegExp) {
+      var results = [],
+          latest;
+      var regexp = new RegExp(aRegExp, "g");
+      while ((latest = regexp.exec(aString)) !== null) {
+        results.push(latest);
+        if (latest[0].length === 0) {
+          ++regexp.lastIndex;
+        }
+      }
+      return results.length > 0 ? results : null;
+    };
+
+    String.prototype.replaceAll = function(re, replace) {
+      return this.replace(new RegExp(re, "g"), replace);
+    };
+
+    String.prototype.equals = function equals(str) {
+      return this.valueOf() === str.valueOf();
+    };
+
+    String.prototype.toCharArray = function() {
+      var chars = this.split("");
+      for (var i = chars.length - 1; i >= 0; i--) {
+        chars[i] = new Char(chars[i]);
+      }
+      return chars;
+    };
+
+    p.match = function(str, regexp) {
+      return str.match(regexp);
+    };
+
+    // tinylog lite JavaScript library
+    // http://purl.eligrey.com/tinylog/lite
+    /*global tinylog,print*/
+    var tinylogLite = (function() {
+      "use strict";
+
+      var tinylogLite = {},
+        undef = "undefined",
+        func = "function",
+        False = !1,
+        True = !0,
+        logLimit = 512,
+        log = "log";
+
+      if (typeof tinylog !== undef && typeof tinylog[log] === func) {
+        // pre-existing tinylog present
+        tinylogLite[log] = tinylog[log];
+      } else if (typeof document !== undef && !document.fake) {
+        (function() {
+          // DOM document
+          var doc = document,
+
+          $div = "div",
+          $style = "style",
+          $title = "title",
+
+          containerStyles = {
+            zIndex: 10000,
+            position: "fixed",
+            bottom: "0px",
+            width: "100%",
+            height: "15%",
+            fontFamily: "sans-serif",
+            color: "#ccc",
+            backgroundColor: "black"
+          },
+          outputStyles = {
+            position: "relative",
+            fontFamily: "monospace",
+            overflow: "auto",
+            height: "100%",
+            paddingTop: "5px"
+          },
+          resizerStyles = {
+            height: "5px",
+            marginTop: "-5px",
+            cursor: "n-resize",
+            backgroundColor: "darkgrey"
+          },
+          closeButtonStyles = {
+            position: "absolute",
+            top: "5px",
+            right: "20px",
+            color: "#111",
+            MozBorderRadius: "4px",
+            webkitBorderRadius: "4px",
+            borderRadius: "4px",
+            cursor: "pointer",
+            fontWeight: "normal",
+            textAlign: "center",
+            padding: "3px 5px",
+            backgroundColor: "#333",
+            fontSize: "12px"
+          },
+          entryStyles = {
+            //borderBottom: "1px solid #d3d3d3",
+            minHeight: "16px"
+          },
+          entryTextStyles = {
+            fontSize: "12px",
+            margin: "0 8px 0 8px",
+            maxWidth: "100%",
+            whiteSpace: "pre-wrap",
+            overflow: "auto"
+          },
+
+          view = doc.defaultView,
+            docElem = doc.documentElement,
+            docElemStyle = docElem[$style],
+
+          setStyles = function() {
+            var i = arguments.length,
+              elemStyle, styles, style;
+
+            while (i--) {
+              styles = arguments[i--];
+              elemStyle = arguments[i][$style];
+
+              for (style in styles) {
+                if (styles.hasOwnProperty(style)) {
+                  elemStyle[style] = styles[style];
+                }
+              }
+            }
+          },
+
+          observer = function(obj, event, handler) {
+            if (obj.addEventListener) {
+              obj.addEventListener(event, handler, False);
+            } else if (obj.attachEvent) {
+              obj.attachEvent("on" + event, handler);
+            }
+            return [obj, event, handler];
+          },
+          unobserve = function(obj, event, handler) {
+            if (obj.removeEventListener) {
+              obj.removeEventListener(event, handler, False);
+            } else if (obj.detachEvent) {
+              obj.detachEvent("on" + event, handler);
+            }
+          },
+          clearChildren = function(node) {
+            var children = node.childNodes,
+              child = children.length;
+
+            while (child--) {
+              node.removeChild(children.item(0));
+            }
+          },
+          append = function(to, elem) {
+            return to.appendChild(elem);
+          },
+          createElement = function(localName) {
+            return doc.createElement(localName);
+          },
+          createTextNode = function(text) {
+            return doc.createTextNode(text);
+          },
+
+          createLog = tinylogLite[log] = function(message) {
+            // don't show output log until called once
+            var uninit,
+              originalPadding = docElemStyle.paddingBottom,
+              container = createElement($div),
+              containerStyle = container[$style],
+              resizer = append(container, createElement($div)),
+              output = append(container, createElement($div)),
+              closeButton = append(container, createElement($div)),
+              resizingLog = False,
+              previousHeight = False,
+              previousScrollTop = False,
+              messages = 0,
+
+              updateSafetyMargin = function() {
+                // have a blank space large enough to fit the output box at the page bottom
+                docElemStyle.paddingBottom = container.clientHeight + "px";
+              },
+              setContainerHeight = function(height) {
+                var viewHeight = view.innerHeight,
+                  resizerHeight = resizer.clientHeight;
+
+                // constrain the container inside the viewport's dimensions
+                if (height < 0) {
+                  height = 0;
+                } else if (height + resizerHeight > viewHeight) {
+                  height = viewHeight - resizerHeight;
+                }
+
+                containerStyle.height = height / viewHeight * 100 + "%";
+
+                updateSafetyMargin();
+              },
+              observers = [
+                observer(doc, "mousemove", function(evt) {
+                  if (resizingLog) {
+                    setContainerHeight(view.innerHeight - evt.clientY);
+                    output.scrollTop = previousScrollTop;
+                  }
+                }),
+
+                observer(doc, "mouseup", function() {
+                  if (resizingLog) {
+                    resizingLog = previousScrollTop = False;
+                  }
+                }),
+
+                observer(resizer, "dblclick", function(evt) {
+                  evt.preventDefault();
+
+                  if (previousHeight) {
+                    setContainerHeight(previousHeight);
+                    previousHeight = False;
+                  } else {
+                    previousHeight = container.clientHeight;
+                    containerStyle.height = "0px";
+                  }
+                }),
+
+                observer(resizer, "mousedown", function(evt) {
+                  evt.preventDefault();
+                  resizingLog = True;
+                  previousScrollTop = output.scrollTop;
+                }),
+
+                observer(resizer, "contextmenu", function() {
+                  resizingLog = False;
+                }),
+
+                observer(closeButton, "click", function() {
+                  uninit();
+                })
+              ];
+
+            uninit = function() {
+              // remove observers
+              var i = observers.length;
+
+              while (i--) {
+                unobserve.apply(tinylogLite, observers[i]);
+              }
+
+              // remove tinylog lite from the DOM
+              docElem.removeChild(container);
+              docElemStyle.paddingBottom = originalPadding;
+
+              clearChildren(output);
+              clearChildren(container);
+
+              tinylogLite[log] = createLog;
+            };
+
+            setStyles(
+            container, containerStyles, output, outputStyles, resizer, resizerStyles, closeButton, closeButtonStyles);
+
+            closeButton[$title] = "Close Log";
+            append(closeButton, createTextNode("\u2716"));
+
+            resizer[$title] = "Double-click to toggle log minimization";
+
+            docElem.insertBefore(container, docElem.firstChild);
+
+            tinylogLite[log] = function(message) {
+              if (messages === logLimit) {
+                output.removeChild(output.firstChild);
+              } else {
+                messages++;
+              }
+
+              var entry = append(output, createElement($div)),
+                entryText = append(entry, createElement($div));
+
+              entry[$title] = (new Date()).toLocaleTimeString();
+
+              setStyles(
+              entry, entryStyles, entryText, entryTextStyles);
+
+              append(entryText, createTextNode(message));
+              output.scrollTop = output.scrollHeight;
+            };
+
+            tinylogLite[log](message);
+          };
+        }());
+      } else if (typeof print === func) { // JS shell
+        tinylogLite[log] = print;
+      }
+
+      return tinylogLite;
+    }()),
+
+    logBuffer = [];
+
+    p.console = window.console || tinylogLite;
+
+    p.println = function println(message) {
+      var bufferLen = logBuffer.length;
+      if (bufferLen) {
+        tinylogLite.log(logBuffer.join(""));
+        logBuffer.length = 0; // clear log buffer
+      }
+
+      if (arguments.length === 0 && bufferLen === 0) {
+        tinylogLite.log("");
+      } else if (arguments.length !== 0) {
+        tinylogLite.log(message);
+      }
+    };
+
+    p.print = function print(message) {
+      logBuffer.push(message);
+    };
+
+    // Alphanumeric chars arguments automatically converted to numbers when
+    // passed in, and will come out as numbers.
+    p.str = function str(val) {
+      if (val instanceof Array) {
+        var arr = [];
+        for (var i = 0; i < val.length; i++) {
+          arr.push(val[i] + "");
+        }
+        return arr;
+      } else {
+        return (val + "");
+      }
+    };
+
+    p.trim = function(str) {
+      if (str instanceof Array) {
+        var arr = [];
+        for (var i = 0; i < str.length; i++) {
+          arr.push(str[i].replace(/^\s*/, '').replace(/\s*$/, '').replace(/\r*$/, ''));
+        }
+        return arr;
+      } else {
+        return str.replace(/^\s*/, '').replace(/\s*$/, '').replace(/\r*$/, '');
+      }
+    };
+
+    // Conversion
+    function booleanScalar(val) {
+      if (typeof val === 'number') {
+        return val !== 0;
+      } else if (typeof val === 'boolean') {
+        return val;
+      } else if (typeof val === 'string') {
+        return val.toLowerCase() === 'true';
+      } else if (val instanceof Char) {
+        // 1, T or t
+        return val.code === 49 || val.code === 84 || val.code === 116;
+      }
+    }
+
+    p['boolean'] = function(val) {
+      if (val instanceof Array) {
+        var ret = [];
+        for (var i = 0; i < val.length; i++) {
+          ret.push(booleanScalar(val[i]));
+        }
+        return ret;
+      } else {
+        return booleanScalar(val);
+      }
+    };
+
+    // a byte is a number between -128 and 127
+    p['byte'] = function(aNumber) {
+      if (aNumber instanceof Array) {
+        var bytes = [];
+        for (var i = 0; i < aNumber.length; i++) {
+          bytes.push((0 - (aNumber[i] & 0x80)) | (aNumber[i] & 0x7F));
+        }
+        return bytes;
+      } else {
+        return (0 - (aNumber & 0x80)) | (aNumber & 0x7F);
+      }
+    };
+
+    p['char'] = function(key) {
+      if (typeof key === "number") {
+        return new Char(String.fromCharCode(key & 0xFFFF));
+      } else if (key instanceof Array) {
+        var ret = [];
+        for (var i = 0; i < key.length; i++) {
+          ret.push(new Char(String.fromCharCode(key[i] & 0xFFFF)));
+        }
+        return ret;
+      } else {
+        throw "char() may receive only one argument of type int, byte, int[], or byte[].";
+      }
+    };
+
+    // Processing doc claims good argument types are: int, char, byte, boolean,
+    // String, int[], char[], byte[], boolean[], String[].
+    // floats should not work. However, floats with only zeroes right of the
+    // decimal will work because JS converts those to int.
+    function floatScalar(val) {
+      if (typeof val === 'number') {
+        return val;
+      } else if (typeof val === 'boolean') {
+        return val ? 1 : 0;
+      } else if (typeof val === 'string') {
+        return parseFloat(val);
+      } else if (val instanceof Char) {
+        return val.code;
+      }
+    }
+
+    p['float'] = function(val) {
+      if (val instanceof Array) {
+        var ret = [];
+        for (var i = 0; i < val.length; i++) {
+          ret.push(floatScalar(val[i]));
+        }
+        return ret;
+      } else {
+        return floatScalar(val);
+      }
+    };
+
+    function intScalar(val) {
+      if (typeof val === 'number') {
+        return val & 0xFFFFFFFF;
+      } else if (typeof val === 'boolean') {
+        return val ? 1 : 0;
+      } else if (typeof val === 'string') {
+        var number = parseInt(val, 10); // Force decimal radix. Don't convert hex or octal (just like p5)
+        return number & 0xFFFFFFFF;
+      } else if (val instanceof Char) {
+        return val.code;
+      }
+    }
+
+    p['int'] = function(val) {
+      if (val instanceof Array) {
+        var ret = [];
+        for (var i = 0; i < val.length; i++) {
+          if (typeof val[i] === 'string' && !/^\s*[+\-]?\d+\s*$/.test(val[i])) {
+            ret.push(0);
+          } else {
+            ret.push(intScalar(val[i]));
+          }
+        }
+        return ret;
+      } else {
+        return intScalar(val);
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Math functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    // Calculation
+    p.abs = Math.abs;
+
+    p.ceil = Math.ceil;
+
+    p.constrain = function(aNumber, aMin, aMax) {
+      return aNumber > aMax ? aMax : aNumber < aMin ? aMin : aNumber;
+    };
+
+    p.dist = function() {
+      var dx, dy, dz;
+      if (arguments.length === 4) {
+        dx = arguments[0] - arguments[2];
+        dy = arguments[1] - arguments[3];
+        return Math.sqrt(dx * dx + dy * dy);
+      } else if (arguments.length === 6) {
+        dx = arguments[0] - arguments[3];
+        dy = arguments[1] - arguments[4];
+        dz = arguments[2] - arguments[5];
+        return Math.sqrt(dx * dx + dy * dy + dz * dz);
+      }
+    };
+
+    p.exp = Math.exp;
+
+    p.floor = Math.floor;
+
+    p.lerp = function(value1, value2, amt) {
+      return ((value2 - value1) * amt) + value1;
+    };
+
+    p.log = Math.log;
+
+    p.mag = function(a, b, c) {
+      if (arguments.length === 2) {
+        return Math.sqrt(a * a + b * b);
+      } else if (arguments.length === 3) {
+        return Math.sqrt(a * a + b * b + c * c);
+      }
+    };
+
+    p.map = function(value, istart, istop, ostart, ostop) {
+      return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
+    };
+
+    p.max = function() {
+      if (arguments.length === 2) {
+        return arguments[0] < arguments[1] ? arguments[1] : arguments[0];
+      } else {
+        var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used
+        if (! ("length" in numbers && numbers.length > 0)) {
+          throw "Non-empty array is expected";
+        }
+        var max = numbers[0],
+          count = numbers.length;
+        for (var i = 1; i < count; ++i) {
+          if (max < numbers[i]) {
+            max = numbers[i];
+          }
+        }
+        return max;
+      }
+    };
+
+    p.min = function() {
+      if (arguments.length === 2) {
+        return arguments[0] < arguments[1] ? arguments[0] : arguments[1];
+      } else {
+        var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used
+        if (! ("length" in numbers && numbers.length > 0)) {
+          throw "Non-empty array is expected";
+        }
+        var min = numbers[0],
+          count = numbers.length;
+        for (var i = 1; i < count; ++i) {
+          if (min > numbers[i]) {
+            min = numbers[i];
+          }
+        }
+        return min;
+      }
+    };
+
+    p.norm = function(aNumber, low, high) {
+      return (aNumber - low) / (high - low);
+    };
+
+    p.pow = Math.pow;
+
+    p.round = Math.round;
+
+    p.sq = function(aNumber) {
+      return aNumber * aNumber;
+    };
+
+    p.sqrt = Math.sqrt;
+
+    // Trigonometry
+    p.acos = Math.acos;
+
+    p.asin = Math.asin;
+
+    p.atan = Math.atan;
+
+    p.atan2 = Math.atan2;
+
+    p.cos = Math.cos;
+
+    p.degrees = function(aAngle) {
+      return (aAngle * 180) / Math.PI;
+    };
+
+    p.radians = function(aAngle) {
+      return (aAngle / 180) * Math.PI;
+    };
+
+    p.sin = Math.sin;
+
+    p.tan = Math.tan;
+
+    var currentRandom = Math.random;
+
+    p.random = function random() {
+      if(arguments.length === 0) {
+        return currentRandom();
+      } else if(arguments.length === 1) {
+        return currentRandom() * arguments[0];
+      } else {
+        var aMin = arguments[0], aMax = arguments[1];
+        return currentRandom() * (aMax - aMin) + aMin;
+      }
+    };
+
+    // Pseudo-random generator
+    function Marsaglia(i1, i2) {
+      // from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c
+      var z=i1 || 362436069, w= i2 || 521288629;
+      var nextInt = function() {
+        z=(36969*(z&65535)+(z>>>16)) & 0xFFFFFFFF;
+        w=(18000*(w&65535)+(w>>>16)) & 0xFFFFFFFF;
+        return (((z&0xFFFF)<<16) | (w&0xFFFF)) & 0xFFFFFFFF;
+      };
+
+      this.nextDouble = function() {
+        var i = nextInt() / 4294967296;
+        return i < 0 ? 1 + i : i;
+      };
+      this.nextInt = nextInt;
+    }
+    Marsaglia.createRandomized = function() {
+      var now = new Date();
+      return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF);
+    };
+
+    p.randomSeed = function(seed) {
+      currentRandom = (new Marsaglia(seed)).nextDouble;
+    };
+
+    // Random
+    p.Random = function(seed) {
+      var haveNextNextGaussian = false, nextNextGaussian, random;
+
+      this.nextGaussian = function() {
+        if (haveNextNextGaussian) {
+          haveNextNextGaussian = false;
+          return nextNextGaussian;
+        } else {
+          var v1, v2, s;
+          do {
+            v1 = 2 * random() - 1; // between -1.0 and 1.0
+            v2 = 2 * random() - 1; // between -1.0 and 1.0
+            s = v1 * v1 + v2 * v2;
+          }
+          while (s >= 1 || s === 0);
+
+          var multiplier = Math.sqrt(-2 * Math.log(s) / s);
+          nextNextGaussian = v2 * multiplier;
+          haveNextNextGaussian = true;
+
+          return v1 * multiplier;
+        }
+      };
+
+      // by default use standard random, otherwise seeded
+      random = (seed === undef) ? Math.random : (new Marsaglia(seed)).nextDouble;
+    };
+
+    // Noise functions and helpers
+    function PerlinNoise(seed) {
+      var rnd = seed !== undef ? new Marsaglia(seed) : Marsaglia.createRandomized();
+      var i, j;
+      // http://www.noisemachine.com/talk1/17b.html
+      // http://mrl.nyu.edu/~perlin/noise/
+      // generate permutation
+      var perm = new Array(512);
+      for(i=0;i<256;++i) { perm[i] = i; }
+      for(i=0;i<256;++i) { var t = perm[j = rnd.nextInt() & 0xFF]; perm[j] = perm[i]; perm[i] = t; }
+      // copy to avoid taking mod in perm[0];
+      for(i=0;i<256;++i) { perm[i + 256] = perm[i]; }
+
+      function grad3d(i,x,y,z) {
+        var h = i & 15; // convert into 12 gradient directions
+        var u = h<8 ? x : y,
+            v = h<4 ? y : h===12||h===14 ? x : z;
+        return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v);
+      }
+
+      function grad2d(i,x,y) {
+        var v = (i & 1) === 0 ? x : y;
+        return (i&2) === 0 ? -v : v;
+      }
+
+      function grad1d(i,x) {
+        return (i&1) === 0 ? -x : x;
+      }
+
+      function lerp(t,a,b) { return a + t * (b - a); }
+
+      this.noise3d = function(x, y, z) {
+        var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255;
+        x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
+        var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z;
+        var p0 = perm[X]+Y, p00 = perm[p0] + Z, p01 = perm[p0 + 1] + Z,
+            p1 = perm[X + 1] + Y, p10 = perm[p1] + Z, p11 = perm[p1 + 1] + Z;
+        return lerp(fz,
+          lerp(fy, lerp(fx, grad3d(perm[p00], x, y, z), grad3d(perm[p10], x-1, y, z)),
+                   lerp(fx, grad3d(perm[p01], x, y-1, z), grad3d(perm[p11], x-1, y-1,z))),
+          lerp(fy, lerp(fx, grad3d(perm[p00 + 1], x, y, z-1), grad3d(perm[p10 + 1], x-1, y, z-1)),
+                   lerp(fx, grad3d(perm[p01 + 1], x, y-1, z-1), grad3d(perm[p11 + 1], x-1, y-1,z-1))));
+      };
+
+      this.noise2d = function(x, y) {
+        var X = Math.floor(x)&255, Y = Math.floor(y)&255;
+        x -= Math.floor(x); y -= Math.floor(y);
+        var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y;
+        var p0 = perm[X]+Y, p1 = perm[X + 1] + Y;
+        return lerp(fy,
+          lerp(fx, grad2d(perm[p0], x, y), grad2d(perm[p1], x-1, y)),
+          lerp(fx, grad2d(perm[p0 + 1], x, y-1), grad2d(perm[p1 + 1], x-1, y-1)));
+      };
+
+      this.noise1d = function(x) {
+        var X = Math.floor(x)&255;
+        x -= Math.floor(x);
+        var fx = (3-2*x)*x*x;
+        return lerp(fx, grad1d(perm[X], x), grad1d(perm[X+1], x-1));
+      };
+    }
+
+    // processing defaults
+    var noiseProfile = { generator: undef, octaves: 4, fallout: 0.5, seed: undef};
+
+    p.noise = function(x, y, z) {
+      if(noiseProfile.generator === undef) {
+        // caching
+        noiseProfile.generator = new PerlinNoise(noiseProfile.seed);
+      }
+      var generator = noiseProfile.generator;
+      var effect = 1, k = 1, sum = 0;
+      for(var i=0; i<noiseProfile.octaves; ++i) {
+        effect *= noiseProfile.fallout;
+        switch (arguments.length) {
+        case 1:
+          sum += effect * (1 + generator.noise1d(k*x))/2; break;
+        case 2:
+          sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break;
+        case 3:
+          sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break;
+        }
+        k *= 2;
+      }
+      return sum;
+    };
+
+    p.noiseDetail = function(octaves, fallout) {
+      noiseProfile.octaves = octaves;
+      if(fallout !== undef) {
+        noiseProfile.fallout = fallout;
+      }
+    };
+
+    p.noiseSeed = function(seed) {
+      noiseProfile.seed = seed;
+      noiseProfile.generator = undef;
+    };
+
+    // Set default background behavior for 2D and 3D contexts
+    var refreshBackground = function() {
+      if (!curSketch.options.isTransparent) {
+        if (p.use3DContext) {
+          // fill background default opaque gray
+          curContext.clearColor(204 / 255, 204 / 255, 204 / 255, 1.0);
+          curContext.clear(curContext.COLOR_BUFFER_BIT | curContext.DEPTH_BUFFER_BIT);
+        } else {
+          // fill background default opaque gray
+          curContext.fillStyle = "rgb(204, 204, 204)";
+          curContext.fillRect(0, 0, p.width, p.height);
+          isFillDirty = true;
+        }
+      }
+    };
+
+    // Changes the size of the Canvas ( this resets context properties like 'lineCap', etc.
+    p.size = function size(aWidth, aHeight, aMode) {
+      if (aMode && (aMode === PConstants.WEBGL)) {
+        // get the 3D rendering context
+        try {
+          // If the HTML <canvas> dimensions differ from the
+          // dimensions specified in the size() call in the sketch, for
+          // 3D sketches, browsers will either not render or render the
+          // scene incorrectly. To fix this, we need to adjust the
+          // width and height attributes of the canvas.
+          if (curElement.width !== aWidth || curElement.height !== aHeight) {
+            curElement.setAttribute("width", aWidth);
+            curElement.setAttribute("height", aHeight);
+          }
+          curContext = curElement.getContext("experimental-webgl");
+          p.use3DContext = true;
+          canTex = curContext.createTexture(); // texture
+        } catch(e_size) {
+          Processing.debug(e_size);
+        }
+
+        if (!curContext) {
+          throw "OPENGL 3D context is not supported on this browser.";
+        } else {
+          for (var i = 0; i < PConstants.SINCOS_LENGTH; i++) {
+            sinLUT[i] = p.sin(i * (PConstants.PI / 180) * 0.5);
+            cosLUT[i] = p.cos(i * (PConstants.PI / 180) * 0.5);
+          }
+          // Set defaults
+          curContext.viewport(0, 0, curElement.width, curElement.height);
+          curContext.enable(curContext.DEPTH_TEST);
+          curContext.enable(curContext.BLEND);
+          curContext.blendFunc(curContext.SRC_ALPHA, curContext.ONE_MINUS_SRC_ALPHA);
+          refreshBackground(); // sets clearColor default;
+
+          // Create the program objects to render 2D (points, lines) and
+          // 3D (spheres, boxes) shapes. Because 2D shapes are not lit,
+          // lighting calculations could be ommitted from that program object.
+          programObject2D = createProgramObject(curContext, vertexShaderSource2D, fragmentShaderSource2D);
+
+          // set the defaults
+          curContext.useProgram(programObject2D);
+          p.strokeWeight(1.0);
+
+          programObject3D = createProgramObject(curContext, vertexShaderSource3D, fragmentShaderSource3D);
+          programObjectUnlitShape = createProgramObject(curContext, vShaderSrcUnlitShape, fShaderSrcUnlitShape);
+
+          // Now that the programs have been compiled, we can set the default
+          // states for the lights.
+          curContext.useProgram(programObject3D);
+
+          // assume we aren't using textures by default
+          uniformi(programObject3D, "usingTexture", usingTexture);
+          p.lightFalloff(1, 0, 0);
+          p.shininess(1);
+          p.ambient(255, 255, 255);
+          p.specular(0, 0, 0);
+
+          // Create buffers for 3D primitives
+          boxBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, boxBuffer);
+          curContext.bufferData(curContext.ARRAY_BUFFER, boxVerts, curContext.STATIC_DRAW);
+
+          boxNormBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, boxNormBuffer);
+          curContext.bufferData(curContext.ARRAY_BUFFER, boxNorms, curContext.STATIC_DRAW);
+
+          boxOutlineBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, boxOutlineBuffer);
+          curContext.bufferData(curContext.ARRAY_BUFFER, boxOutlineVerts, curContext.STATIC_DRAW);
+
+          // used to draw the rectangle and the outline
+          rectBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, rectBuffer);
+          curContext.bufferData(curContext.ARRAY_BUFFER, rectVerts, curContext.STATIC_DRAW);
+
+          rectNormBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, rectNormBuffer);
+          curContext.bufferData(curContext.ARRAY_BUFFER, rectNorms, curContext.STATIC_DRAW);
+
+          // The sphere vertices are specified dynamically since the user
+          // can change the level of detail. Everytime the user does that
+          // using sphereDetail(), the new vertices are calculated.
+          sphereBuffer = curContext.createBuffer();
+
+          lineBuffer = curContext.createBuffer();
+
+          // Shape buffers
+          fillBuffer = curContext.createBuffer();
+          fillColorBuffer = curContext.createBuffer();
+          strokeColorBuffer = curContext.createBuffer();
+          shapeTexVBO = curContext.createBuffer();
+
+          pointBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, pointBuffer);
+          curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0, 0, 0]), curContext.STATIC_DRAW);
+
+          textBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, textBuffer );
+          curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([1,1,0,-1,1,0,-1,-1,0,1,-1,0]), curContext.STATIC_DRAW);
+
+          textureBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ARRAY_BUFFER, textureBuffer);
+          curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0,0,1,0,1,1,0,1]), curContext.STATIC_DRAW);
+
+          indexBuffer = curContext.createBuffer();
+          curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer);
+          curContext.bufferData(curContext.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2,2,3,0]), curContext.STATIC_DRAW);
+
+          cam = new PMatrix3D();
+          cameraInv = new PMatrix3D();
+          forwardTransform = new PMatrix3D();
+          reverseTransform = new PMatrix3D();
+          modelView = new PMatrix3D();
+          modelViewInv = new PMatrix3D();
+          projection = new PMatrix3D();
+          p.camera();
+          p.perspective();
+          forwardTransform = modelView;
+          reverseTransform = modelViewInv;
+
+          userMatrixStack = new PMatrixStack();
+          // used by both curve and bezier, so just init here
+          curveBasisMatrix = new PMatrix3D();
+          curveToBezierMatrix = new PMatrix3D();
+          curveDrawMatrix = new PMatrix3D();
+          bezierDrawMatrix = new PMatrix3D();
+          bezierBasisInverse = new PMatrix3D();
+          bezierBasisMatrix = new PMatrix3D();
+          bezierBasisMatrix.set(-1, 3, -3, 1, 3, -6, 3, 0, -3, 3, 0, 0, 1, 0, 0, 0);
+        }
+        p.stroke(0);
+        p.fill(255);
+      } else {
+        if (curContext === undef) {
+          // size() was called without p.init() default context, ie. p.createGraphics()
+          curContext = curElement.getContext("2d");
+          p.use3DContext = false;
+          userMatrixStack = new PMatrixStack();
+          modelView = new PMatrix2D();
+        }
+      }
+
+      // The default 2d context has already been created in the p.init() stage if
+      // a 3d context was not specified. This is so that a 2d context will be
+      // available if size() was not called.
+      var props = {
+        fillStyle: curContext.fillStyle,
+        strokeStyle: curContext.strokeStyle,
+        lineCap: curContext.lineCap,
+        lineJoin: curContext.lineJoin
+      };
+      curElement.width = p.width = aWidth || 100;
+      curElement.height = p.height = aHeight || 100;
+
+      for (var j in props) {
+        if (props) {
+          curContext[j] = props[j];
+        }
+      }
+
+      // redraw the background if background was called before size
+      refreshBackground();
+
+      // set 5% for pixels to cache (or 1000)
+      maxPixelsCached = Math.max(1000, aWidth * aHeight * 0.05);
+
+      // Externalize the context
+      p.externals.context = curContext;
+
+      p.toImageData = function() {
+        if(!p.use3DContext){
+          return curContext.getImageData(0, 0, this.width, this.height);
+        } else {
+          var c = document.createElement("canvas");
+          var ctx = c.getContext("2d");
+          var obj = ctx.createImageData(this.width, this.height);
+          var uBuff = new Uint8Array(this.width * this.height * 4);
+          curContext.readPixels(0,0,this.width,this.height,curContext.RGBA,curContext.UNSIGNED_BYTE, uBuff);
+          for(var i =0; i < uBuff.length; i++){
+            obj.data[i] = uBuff[(this.height - 1 - Math.floor(i / 4 / this.width)) * this.width * 4 + (i % (this.width * 4))];
+          }
+
+          return obj;
+        }
+      };
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Lights
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.ambientLight = function(r, g, b, x, y, z) {
+      if (p.use3DContext) {
+        if (lightCount === PConstants.MAX_LIGHTS) {
+          throw "can only create " + PConstants.MAX_LIGHTS + " lights";
+        }
+
+        var pos = new PVector(x, y, z);
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.mult(pos, pos);
+
+        curContext.useProgram(programObject3D);
+        uniformf(programObject3D, "lights[" + lightCount + "].color", [r / 255, g / 255, b / 255]);
+        uniformf(programObject3D, "lights[" + lightCount + "].position", pos.array());
+        uniformi(programObject3D, "lights[" + lightCount + "].type", 0);
+        uniformi(programObject3D, "lightCount", ++lightCount);
+      }
+    };
+
+    p.directionalLight = function(r, g, b, nx, ny, nz) {
+      if (p.use3DContext) {
+        if (lightCount === PConstants.MAX_LIGHTS) {
+          throw "can only create " + PConstants.MAX_LIGHTS + " lights";
+        }
+
+        curContext.useProgram(programObject3D);
+
+        // Less code than manually multiplying, but I'll fix
+        // this when I have more time.
+        var dir = [nx, ny, nz, 0.0000001];
+
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.mult(dir, dir);
+
+        uniformf(programObject3D, "lights[" + lightCount + "].color", [r / 255, g / 255, b / 255]);
+        uniformf(programObject3D, "lights[" + lightCount + "].position", [-dir[0], -dir[1], -dir[2]]);
+        uniformi(programObject3D, "lights[" + lightCount + "].type", 1);
+        uniformi(programObject3D, "lightCount", ++lightCount);
+      }
+    };
+
+    p.lightFalloff = function lightFalloff(constant, linear, quadratic) {
+      if (p.use3DContext) {
+        curContext.useProgram(programObject3D);
+        uniformf(programObject3D, "falloff", [constant, linear, quadratic]);
+      }
+    };
+
+    p.lightSpecular = function lightSpecular(r, g, b) {
+      if (p.use3DContext) {
+        curContext.useProgram(programObject3D);
+        uniformf(programObject3D, "specular", [r / 255, g / 255, b / 255]);
+      }
+    };
+
+    /*
+      Sets the default ambient light, directional light,
+      falloff, and specular values. P5 Documentation says specular()
+      is set, but the code calls lightSpecular().
+    */
+    p.lights = function lights() {
+      p.ambientLight(128, 128, 128);
+      p.directionalLight(128, 128, 128, 0, 0, -1);
+      p.lightFalloff(1, 0, 0);
+      p.lightSpecular(0, 0, 0);
+    };
+
+    p.pointLight = function(r, g, b, x, y, z) {
+      if (p.use3DContext) {
+        if (lightCount === PConstants.MAX_LIGHTS) {
+          throw "can only create " + PConstants.MAX_LIGHTS + " lights";
+        }
+
+        // place the point in view space once instead of once per vertex
+        // in the shader.
+        var pos = new PVector(x, y, z);
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.mult(pos, pos);
+
+        curContext.useProgram(programObject3D);
+        uniformf(programObject3D, "lights[" + lightCount + "].color", [r / 255, g / 255, b / 255]);
+        uniformf(programObject3D, "lights[" + lightCount + "].position", pos.array());
+        uniformi(programObject3D, "lights[" + lightCount + "].type", 2);
+        uniformi(programObject3D, "lightCount", ++lightCount);
+      }
+    };
+
+    /*
+      Disables lighting so the all shapes drawn after this
+      will not be lit.
+    */
+    p.noLights = function noLights() {
+      if (p.use3DContext) {
+        lightCount = 0;
+        curContext.useProgram(programObject3D);
+        uniformi(programObject3D, "lightCount", lightCount);
+      }
+    };
+
+    /*
+      r,g,b - Color of the light
+      x,y,z - position of the light in modeling space
+      nx,ny,nz - direction of the spotlight
+      angle - in radians
+      concentration -
+    */
+    p.spotLight = function spotLight(r, g, b, x, y, z, nx, ny, nz, angle, concentration) {
+      if (p.use3DContext) {
+        if (lightCount === PConstants.MAX_LIGHTS) {
+          throw "can only create " + PConstants.MAX_LIGHTS + " lights";
+        }
+
+        curContext.useProgram(programObject3D);
+
+        // place the point in view space once instead of once per vertex
+        // in the shader.
+        var pos = new PVector(x, y, z);
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.mult(pos, pos);
+
+        // transform the spotlight's direction
+        // need to find a solution for this one. Maybe manual mult?
+        var dir = [nx, ny, nz, 0.0000001];
+        view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.mult(dir, dir);
+
+        uniformf(programObject3D, "lights[" + lightCount + "].color", [r / 255, g / 255, b / 255]);
+        uniformf(programObject3D, "lights[" + lightCount + "].position", pos.array());
+        uniformf(programObject3D, "lights[" + lightCount + "].direction", [dir[0], dir[1], dir[2]]);
+        uniformf(programObject3D, "lights[" + lightCount + "].concentration", concentration);
+        uniformf(programObject3D, "lights[" + lightCount + "].angle", angle);
+        uniformi(programObject3D, "lights[" + lightCount + "].type", 3);
+        uniformi(programObject3D, "lightCount", ++lightCount);
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Camera functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.beginCamera = function beginCamera() {
+      if (manipulatingCamera) {
+        throw ("You cannot call beginCamera() again before calling endCamera()");
+      } else {
+        manipulatingCamera = true;
+        forwardTransform = cameraInv;
+        reverseTransform = cam;
+      }
+    };
+
+    p.endCamera = function endCamera() {
+      if (!manipulatingCamera) {
+        throw ("You cannot call endCamera() before calling beginCamera()");
+      } else {
+        modelView.set(cam);
+        modelViewInv.set(cameraInv);
+        forwardTransform = modelView;
+        reverseTransform = modelViewInv;
+        manipulatingCamera = false;
+      }
+    };
+
+    p.camera = function camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) {
+      if (arguments.length === 0) {
+        //in case canvas is resized
+        cameraX = curElement.width / 2;
+        cameraY = curElement.height / 2;
+        cameraZ = cameraY / Math.tan(cameraFOV / 2);
+        eyeX = cameraX;
+        eyeY = cameraY;
+        eyeZ = cameraZ;
+        centerX = cameraX;
+        centerY = cameraY;
+        centerZ = 0;
+        upX = 0;
+        upY = 1;
+        upZ = 0;
+      }
+
+      var z = new p.PVector(eyeX - centerX, eyeY - centerY, eyeZ - centerZ);
+      var y = new p.PVector(upX, upY, upZ);
+      var transX, transY, transZ;
+      z.normalize();
+      var x = p.PVector.cross(y, z);
+      y = p.PVector.cross(z, x);
+      x.normalize();
+      y.normalize();
+
+      cam.set(x.x, x.y, x.z, 0, y.x, y.y, y.z, 0, z.x, z.y, z.z, 0, 0, 0, 0, 1);
+
+      cam.translate(-eyeX, -eyeY, -eyeZ);
+
+      cameraInv.reset();
+      cameraInv.invApply(x.x, x.y, x.z, 0, y.x, y.y, y.z, 0, z.x, z.y, z.z, 0, 0, 0, 0, 1);
+
+      cameraInv.translate(eyeX, eyeY, eyeZ);
+
+      modelView.set(cam);
+      modelViewInv.set(cameraInv);
+    };
+
+    p.perspective = function perspective(fov, aspect, near, far) {
+      if (arguments.length === 0) {
+        //in case canvas is resized
+        cameraY = curElement.height / 2;
+        cameraZ = cameraY / Math.tan(cameraFOV / 2);
+        cameraNear = cameraZ / 10;
+        cameraFar = cameraZ * 10;
+        cameraAspect = curElement.width / curElement.height;
+        fov = cameraFOV;
+        aspect = cameraAspect;
+        near = cameraNear;
+        far = cameraFar;
+      }
+
+      var yMax, yMin, xMax, xMin;
+      yMax = near * Math.tan(fov / 2);
+      yMin = -yMax;
+      xMax = yMax * aspect;
+      xMin = yMin * aspect;
+      p.frustum(xMin, xMax, yMin, yMax, near, far);
+    };
+
+    p.frustum = function frustum(left, right, bottom, top, near, far) {
+      frustumMode = true;
+      projection = new PMatrix3D();
+      projection.set((2 * near) / (right - left), 0, (right + left) / (right - left),
+                     0, 0, (2 * near) / (top - bottom), (top + bottom) / (top - bottom),
+                     0, 0, 0, -(far + near) / (far - near), -(2 * far * near) / (far - near),
+                     0, 0, -1, 0);
+    };
+
+    p.ortho = function ortho(left, right, bottom, top, near, far) {
+      if (arguments.length === 0) {
+        left = 0;
+        right = p.width;
+        bottom = 0;
+        top = p.height;
+        near = -10;
+        far = 10;
+      }
+
+      var x = 2 / (right - left);
+      var y = 2 / (top - bottom);
+      var z = -2 / (far - near);
+
+      var tx = -(right + left) / (right - left);
+      var ty = -(top + bottom) / (top - bottom);
+      var tz = -(far + near) / (far - near);
+
+      projection = new PMatrix3D();
+      projection.set(x, 0, 0, tx, 0, y, 0, ty, 0, 0, z, tz, 0, 0, 0, 1);
+
+      frustumMode = false;
+    };
+
+    p.printProjection = function() {
+      projection.print();
+    };
+
+    p.printCamera = function() {
+      cam.print();
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Shapes
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.box = function(w, h, d) {
+      if (p.use3DContext) {
+        // user can uniformly scale the box by
+        // passing in only one argument.
+        if (!h || !d) {
+          h = d = w;
+        }
+
+        // Modeling transformation
+        var model = new PMatrix3D();
+        model.scale(w, h, d);
+
+        // viewing transformation needs to have Y flipped
+        // becuase that's what Processing does.
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.transpose();
+
+        var proj = new PMatrix3D();
+        proj.set(projection);
+        proj.transpose();
+
+        if (doFill === true) {
+          curContext.useProgram(programObject3D);
+
+          disableVertexAttribPointer(programObject3D, "aTexture");
+
+          uniformMatrix(programObject3D, "model", false, model.array());
+          uniformMatrix(programObject3D, "view", false, view.array());
+          uniformMatrix(programObject3D, "projection", false, proj.array());
+
+          // fix stitching problems. (lines get occluded by triangles
+          // since they share the same depth values). This is not entirely
+          // working, but it's a start for drawing the outline. So
+          // developers can start playing around with styles.
+          curContext.enable(curContext.POLYGON_OFFSET_FILL);
+          curContext.polygonOffset(1, 1);
+          uniformf(programObject3D, "color", fillStyle);
+
+          // Create the normal transformation matrix
+          var v = new PMatrix3D();
+          v.set(view);
+
+          var m = new PMatrix3D();
+          m.set(model);
+
+          v.mult(m);
+
+          var normalMatrix = new PMatrix3D();
+          normalMatrix.set(v);
+          normalMatrix.invert();
+          normalMatrix.transpose();
+
+          uniformMatrix(programObject3D, "normalTransform", false, normalMatrix.array());
+
+          vertexAttribPointer(programObject3D, "Vertex", 3, boxBuffer);
+          vertexAttribPointer(programObject3D, "Normal", 3, boxNormBuffer);
+
+          // Ugly hack. Can't simply disable the vertex attribute
+          // array. No idea why, so I'm passing in dummy data.
+          vertexAttribPointer(programObject3D, "aColor", 3, boxNormBuffer);
+
+          curContext.drawArrays(curContext.TRIANGLES, 0, boxVerts.length / 3);
+          curContext.disable(curContext.POLYGON_OFFSET_FILL);
+        }
+
+        if (lineWidth > 0 && doStroke) {
+          curContext.useProgram(programObject2D);
+          uniformMatrix(programObject2D, "model", false, model.array());
+          uniformMatrix(programObject2D, "view", false, view.array());
+          uniformMatrix(programObject2D, "projection", false, proj.array());
+
+          uniformf(programObject2D, "color", strokeStyle);
+          uniformi(programObject2D, "picktype", 0);
+
+          vertexAttribPointer(programObject2D, "Vertex", 3, boxOutlineBuffer);
+          disableVertexAttribPointer(programObject2D, "aTextureCoord");
+
+          curContext.lineWidth(lineWidth);
+          curContext.drawArrays(curContext.LINES, 0, boxOutlineVerts.length / 3);
+        }
+      }
+    };
+
+    var initSphere = function() {
+      var i;
+      sphereVerts = [];
+
+      for (i = 0; i < sphereDetailU; i++) {
+        sphereVerts.push(0);
+        sphereVerts.push(-1);
+        sphereVerts.push(0);
+        sphereVerts.push(sphereX[i]);
+        sphereVerts.push(sphereY[i]);
+        sphereVerts.push(sphereZ[i]);
+      }
+      sphereVerts.push(0);
+      sphereVerts.push(-1);
+      sphereVerts.push(0);
+      sphereVerts.push(sphereX[0]);
+      sphereVerts.push(sphereY[0]);
+      sphereVerts.push(sphereZ[0]);
+
+      var v1, v11, v2;
+
+      // middle rings
+      var voff = 0;
+      for (i = 2; i < sphereDetailV; i++) {
+        v1 = v11 = voff;
+        voff += sphereDetailU;
+        v2 = voff;
+        for (var j = 0; j < sphereDetailU; j++) {
+          sphereVerts.push(parseFloat(sphereX[v1]));
+          sphereVerts.push(parseFloat(sphereY[v1]));
+          sphereVerts.push(parseFloat(sphereZ[v1++]));
+          sphereVerts.push(parseFloat(sphereX[v2]));
+          sphereVerts.push(parseFloat(sphereY[v2]));
+          sphereVerts.push(parseFloat(sphereZ[v2++]));
+        }
+
+        // close each ring
+        v1 = v11;
+        v2 = voff;
+
+        sphereVerts.push(parseFloat(sphereX[v1]));
+        sphereVerts.push(parseFloat(sphereY[v1]));
+        sphereVerts.push(parseFloat(sphereZ[v1]));
+        sphereVerts.push(parseFloat(sphereX[v2]));
+        sphereVerts.push(parseFloat(sphereY[v2]));
+        sphereVerts.push(parseFloat(sphereZ[v2]));
+      }
+
+      // add the northern cap
+      for (i = 0; i < sphereDetailU; i++) {
+        v2 = voff + i;
+
+        sphereVerts.push(parseFloat(sphereX[v2]));
+        sphereVerts.push(parseFloat(sphereY[v2]));
+        sphereVerts.push(parseFloat(sphereZ[v2]));
+        sphereVerts.push(0);
+        sphereVerts.push(1);
+        sphereVerts.push(0);
+      }
+
+      sphereVerts.push(parseFloat(sphereX[voff]));
+      sphereVerts.push(parseFloat(sphereY[voff]));
+      sphereVerts.push(parseFloat(sphereZ[voff]));
+      sphereVerts.push(0);
+      sphereVerts.push(1);
+      sphereVerts.push(0);
+
+      //set the buffer data
+      curContext.bindBuffer(curContext.ARRAY_BUFFER, sphereBuffer);
+      curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(sphereVerts), curContext.STATIC_DRAW);
+    };
+
+    p.sphereDetail = function sphereDetail(ures, vres) {
+      var i;
+
+      if (arguments.length === 1) {
+        ures = vres = arguments[0];
+      }
+
+      if (ures < 3) {
+        ures = 3;
+      } // force a minimum res
+      if (vres < 2) {
+        vres = 2;
+      } // force a minimum res
+      // if it hasn't changed do nothing
+      if ((ures === sphereDetailU) && (vres === sphereDetailV)) {
+        return;
+      }
+
+      var delta = PConstants.SINCOS_LENGTH / ures;
+      var cx = new Array(ures);
+      var cz = new Array(ures);
+      // calc unit circle in XZ plane
+      for (i = 0; i < ures; i++) {
+        cx[i] = cosLUT[parseInt((i * delta) % PConstants.SINCOS_LENGTH, 10)];
+        cz[i] = sinLUT[parseInt((i * delta) % PConstants.SINCOS_LENGTH, 10)];
+      }
+
+      // computing vertexlist
+      // vertexlist starts at south pole
+      var vertCount = ures * (vres - 1) + 2;
+      var currVert = 0;
+
+      // re-init arrays to store vertices
+      sphereX = new Array(vertCount);
+      sphereY = new Array(vertCount);
+      sphereZ = new Array(vertCount);
+
+      var angle_step = (PConstants.SINCOS_LENGTH * 0.5) / vres;
+      var angle = angle_step;
+
+      // step along Y axis
+      for (i = 1; i < vres; i++) {
+        var curradius = sinLUT[parseInt(angle % PConstants.SINCOS_LENGTH, 10)];
+        var currY = -cosLUT[parseInt(angle % PConstants.SINCOS_LENGTH, 10)];
+        for (var j = 0; j < ures; j++) {
+          sphereX[currVert] = cx[j] * curradius;
+          sphereY[currVert] = currY;
+          sphereZ[currVert++] = cz[j] * curradius;
+        }
+        angle += angle_step;
+      }
+      sphereDetailU = ures;
+      sphereDetailV = vres;
+
+      // make the sphere verts and norms
+      initSphere();
+    };
+
+    p.sphere = function() {
+      if (p.use3DContext) {
+        var sRad = arguments[0], c;
+
+        if ((sphereDetailU < 3) || (sphereDetailV < 2)) {
+          p.sphereDetail(30);
+        }
+
+        // Modeling transformation
+        var model = new PMatrix3D();
+        model.scale(sRad, sRad, sRad);
+
+        // viewing transformation needs to have Y flipped
+        // becuase that's what Processing does.
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.transpose();
+
+        var proj = new PMatrix3D();
+        proj.set(projection);
+        proj.transpose();
+
+        if (doFill === true) {
+          // Create a normal transformation matrix
+          var v = new PMatrix3D();
+          v.set(view);
+
+          var m = new PMatrix3D();
+          m.set(model);
+
+          v.mult(m);
+
+          var normalMatrix = new PMatrix3D();
+          normalMatrix.set(v);
+          normalMatrix.invert();
+          normalMatrix.transpose();
+
+          curContext.useProgram(programObject3D);
+          disableVertexAttribPointer(programObject3D, "aTexture");
+
+          uniformMatrix(programObject3D, "model", false, model.array());
+          uniformMatrix(programObject3D, "view", false, view.array());
+          uniformMatrix(programObject3D, "projection", false, proj.array());
+          uniformMatrix(programObject3D, "normalTransform", false, normalMatrix.array());
+
+          vertexAttribPointer(programObject3D, "Vertex", 3, sphereBuffer);
+          vertexAttribPointer(programObject3D, "Normal", 3, sphereBuffer);
+
+          // Ugly hack. Can't simply disable the vertex attribute
+          // array. No idea why, so I'm passing in dummy data.
+          vertexAttribPointer(programObject3D, "aColor", 3, sphereBuffer);
+
+          // fix stitching problems. (lines get occluded by triangles
+          // since they share the same depth values). This is not entirely
+          // working, but it's a start for drawing the outline. So
+          // developers can start playing around with styles.
+          curContext.enable(curContext.POLYGON_OFFSET_FILL);
+          curContext.polygonOffset(1, 1);
+
+          uniformf(programObject3D, "color", fillStyle);
+
+          curContext.drawArrays(curContext.TRIANGLE_STRIP, 0, sphereVerts.length / 3);
+          curContext.disable(curContext.POLYGON_OFFSET_FILL);
+        }
+
+        if (lineWidth > 0 && doStroke) {
+          curContext.useProgram(programObject2D);
+          uniformMatrix(programObject2D, "model", false, model.array());
+          uniformMatrix(programObject2D, "view", false, view.array());
+          uniformMatrix(programObject2D, "projection", false, proj.array());
+
+          vertexAttribPointer(programObject2D, "Vertex", 3, sphereBuffer);
+          disableVertexAttribPointer(programObject2D, "aTextureCoord");
+
+          uniformf(programObject2D, "color", strokeStyle);
+          uniformi(programObject2D, "picktype", 0);
+
+          curContext.lineWidth(lineWidth);
+          curContext.drawArrays(curContext.LINE_STRIP, 0, sphereVerts.length / 3);
+        }
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Coordinates
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.modelX = function modelX(x, y, z) {
+      var mv = modelView.array();
+      var ci = cameraInv.array();
+
+      var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3];
+      var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7];
+      var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11];
+      var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15];
+
+      var ox = ci[0] * ax + ci[1] * ay + ci[2] * az + ci[3] * aw;
+      var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw;
+
+      return (ow !== 0) ? ox / ow : ox;
+    };
+
+    p.modelY = function modelY(x, y, z) {
+      var mv = modelView.array();
+      var ci = cameraInv.array();
+
+      var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3];
+      var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7];
+      var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11];
+      var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15];
+
+      var oy = ci[4] * ax + ci[5] * ay + ci[6] * az + ci[7] * aw;
+      var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw;
+
+      return (ow !== 0) ? oy / ow : oy;
+    };
+
+    p.modelZ = function modelZ(x, y, z) {
+      var mv = modelView.array();
+      var ci = cameraInv.array();
+
+      var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3];
+      var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7];
+      var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11];
+      var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15];
+
+      var oz = ci[8] * ax + ci[9] * ay + ci[10] * az + ci[11] * aw;
+      var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw;
+
+      return (ow !== 0) ? oz / ow : oz;
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Material Properties
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.ambient = function ambient() {
+      // create an alias to shorten code
+      var a = arguments;
+
+      // either a shade of gray or a 'color' object.
+      if (p.use3DContext) {
+        curContext.useProgram(programObject3D);
+        uniformi(programObject3D, "usingMat", true);
+
+        if (a.length === 1) {
+          // color object was passed in
+          if (typeof a[0] === "string") {
+            var c = a[0].slice(5, -1).split(",");
+            uniformf(programObject3D, "mat_ambient", [c[0] / 255, c[1] / 255, c[2] / 255]);
+          }
+          // else a single number was passed in for gray shade
+          else {
+            uniformf(programObject3D, "mat_ambient", [a[0] / 255, a[0] / 255, a[0] / 255]);
+          }
+        }
+        // Otherwise three values were provided (r,g,b)
+        else {
+          uniformf(programObject3D, "mat_ambient", [a[0] / 255, a[1] / 255, a[2] / 255]);
+        }
+      }
+    };
+
+    p.emissive = function emissive() {
+      // create an alias to shorten code
+      var a = arguments;
+
+      if (p.use3DContext) {
+        curContext.useProgram(programObject3D);
+        uniformi(programObject3D, "usingMat", true);
+
+        // If only one argument was provided, the user either gave us a
+        // shade of gray or a 'color' object.
+        if (a.length === 1) {
+          // color object was passed in
+          if (typeof a[0] === "string") {
+            var c = a[0].slice(5, -1).split(",");
+            uniformf(programObject3D, "mat_emissive", [c[0] / 255, c[1] / 255, c[2] / 255]);
+          }
+          // else a regular number was passed in for gray shade
+          else {
+            uniformf(programObject3D, "mat_emissive", [a[0] / 255, a[0] / 255, a[0] / 255]);
+          }
+        }
+        // Otherwise three values were provided (r,g,b)
+        else {
+          uniformf(programObject3D, "mat_emissive", [a[0] / 255, a[1] / 255, a[2] / 255]);
+        }
+      }
+    };
+
+    p.shininess = function shininess(shine) {
+      if (p.use3DContext) {
+        curContext.useProgram(programObject3D);
+        uniformi(programObject3D, "usingMat", true);
+        uniformf(programObject3D, "shininess", shine);
+      }
+    };
+
+    /*
+      Documentation says the following calls are valid, but the
+      Processing throws exceptions:
+      specular(gray, alpha)
+      specular(v1, v2, v3, alpha)
+      So we don't support them either
+      <corban> I dont think this matters so much, let us let color handle it. alpha values are not sent anyways.
+    */
+    p.specular = function specular() {
+      var c = p.color.apply(this, arguments);
+
+      if (p.use3DContext) {
+        curContext.useProgram(programObject3D);
+        uniformi(programObject3D, "usingMat", true);
+        uniformf(programObject3D, "mat_specular", p.color.toGLArray(c).slice(0, 3));
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Coordinates
+    ////////////////////////////////////////////////////////////////////////////
+    p.screenX = function screenX( x, y, z ) {
+      var mv = modelView.array();
+      var pj = projection.array();
+
+      var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3];
+      var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7];
+      var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11];
+      var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15];
+
+      var ox = pj[ 0]*ax + pj[ 1]*ay + pj[ 2]*az + pj[ 3]*aw;
+      var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw;
+
+      if ( ow !== 0 ){
+        ox /= ow;
+      }
+      return p.width * ( 1 + ox ) / 2.0;
+    };
+
+    p.screenY = function screenY( x, y, z ) {
+      var mv = modelView.array();
+      var pj = projection.array();
+
+      var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3];
+      var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7];
+      var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11];
+      var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15];
+
+      var oy = pj[ 4]*ax + pj[ 5]*ay + pj[ 6]*az + pj[ 7]*aw;
+      var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw;
+
+      if ( ow !== 0 ){
+        oy /= ow;
+      }
+      return p.height * ( 1 + oy ) / 2.0;
+    };
+
+    p.screenZ = function screenZ( x, y, z ) {
+      var mv = modelView.array();
+      var pj = projection.array();
+
+      var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3];
+      var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7];
+      var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11];
+      var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15];
+
+      var oz = pj[ 8]*ax + pj[ 9]*ay + pj[10]*az + pj[11]*aw;
+      var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw;
+
+      if ( ow !== 0 ) {
+        oz /= ow;
+      }
+      return ( oz + 1 ) / 2.0;
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Style functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.fill = function fill() {
+      var color = p.color(arguments[0], arguments[1], arguments[2], arguments[3]);
+      if(color === currentFillColor && doFill) {
+        return;
+      }
+      doFill = true;
+      currentFillColor = color;
+
+      if (p.use3DContext) {
+        fillStyle = p.color.toGLArray(color);
+      } else {
+        isFillDirty = true;
+      }
+    };
+
+    function executeContextFill() {
+      if(doFill) {
+        if(isFillDirty) {
+          curContext.fillStyle = p.color.toString(currentFillColor);
+          isFillDirty = false;
+        }
+        curContext.fill();
+      }
+    }
+
+    p.noFill = function noFill() {
+      doFill = false;
+    };
+
+    p.stroke = function stroke() {
+      var color = p.color(arguments[0], arguments[1], arguments[2], arguments[3]);
+      if(color === currentStrokeColor && doStroke) {
+        return;
+      }
+      doStroke = true;
+      currentStrokeColor = color;
+
+      if (p.use3DContext) {
+        strokeStyle = p.color.toGLArray(color);
+      } else {
+        isStrokeDirty = true;
+      }
+    };
+
+    function executeContextStroke() {
+      if(doStroke) {
+        if(isStrokeDirty) {
+          curContext.strokeStyle = p.color.toString(currentStrokeColor);
+          isStrokeDirty = false;
+        }
+        curContext.stroke();
+      }
+    }
+
+    p.noStroke = function noStroke() {
+      doStroke = false;
+    };
+
+    p.strokeWeight = function strokeWeight(w) {
+      lineWidth = w;
+
+      if (p.use3DContext) {
+        curContext.useProgram(programObject2D);
+        uniformf(programObject2D, "pointSize", w);
+      } else {
+        curContext.lineWidth = w;
+      }
+    };
+
+    p.strokeCap = function strokeCap(value) {
+      curContext.lineCap = value;
+    };
+
+    p.strokeJoin = function strokeJoin(value) {
+      curContext.lineJoin = value;
+    };
+
+    p.smooth = function() {
+      curElement.style.setProperty("image-rendering", "optimizeQuality", "important");
+      if (!p.use3DContext && "mozImageSmoothingEnabled" in curContext) {
+        curContext.mozImageSmoothingEnabled = true;
+      }
+    };
+
+    p.noSmooth = function() {
+      curElement.style.setProperty("image-rendering", "optimizeSpeed", "important");
+      if (!p.use3DContext && "mozImageSmoothingEnabled" in curContext) {
+        curContext.mozImageSmoothingEnabled = false;
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Vector drawing functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    function colorBlendWithAlpha(c1, c2, k) {
+        var f = 0|(k * ((c2 & PConstants.ALPHA_MASK) >>> 24));
+        return (Math.min(((c1 & PConstants.ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
+                p.mix(c1 & PConstants.RED_MASK, c2 & PConstants.RED_MASK, f) & PConstants.RED_MASK |
+                p.mix(c1 & PConstants.GREEN_MASK, c2 & PConstants.GREEN_MASK, f) & PConstants.GREEN_MASK |
+                p.mix(c1 & PConstants.BLUE_MASK, c2 & PConstants.BLUE_MASK, f));
+    }
+
+    p.point = function point(x, y, z) {
+      if (p.use3DContext) {
+        var model = new PMatrix3D();
+
+        // move point to position
+        model.translate(x, y, z || 0);
+        model.transpose();
+
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.transpose();
+
+        var proj = new PMatrix3D();
+        proj.set(projection);
+        proj.transpose();
+
+        curContext.useProgram(programObject2D);
+        uniformMatrix(programObject2D, "model", false, model.array());
+        uniformMatrix(programObject2D, "view", false, view.array());
+        uniformMatrix(programObject2D, "projection", false, proj.array());
+
+        if (lineWidth > 0 && doStroke) {
+          // this will be replaced with the new bit shifting color code
+          uniformf(programObject2D, "color", strokeStyle);
+          uniformi(programObject2D, "picktype", 0);
+
+          vertexAttribPointer(programObject2D, "Vertex", 3, pointBuffer);
+          disableVertexAttribPointer(programObject2D, "aTextureCoord");
+
+          curContext.drawArrays(curContext.POINTS, 0, 1);
+        }
+      } else {
+        if (doStroke) {
+          // TODO if strokeWeight > 1, do circle
+
+          if (curSketch.options.crispLines) {
+            var alphaOfPointWeight = Math.PI / 4;  // TODO dependency of strokeWeight
+            var c = p.get(x, y);
+            p.set(x, y, colorBlendWithAlpha(c, currentStrokeColor, alphaOfPointWeight));
+          } else {
+            if (lineWidth > 1){
+              curContext.fillStyle = p.color.toString(currentStrokeColor);
+              isFillDirty = true;
+              curContext.beginPath();
+              curContext.arc(x, y, lineWidth / 2, 0, PConstants.TWO_PI, false);
+              curContext.fill();
+              curContext.closePath();
+            } else {
+              curContext.fillStyle = p.color.toString(currentStrokeColor);
+              curContext.fillRect(Math.round(x), Math.round(y), 1, 1);
+              isFillDirty = true;
+            }
+          }
+        }
+      }
+    };
+
+    p.beginShape = function beginShape(type) {
+      curShape = type;
+      curShapeCount = 0;
+      curvePoints = [];
+      //textureImage = null;
+      vertArray = [];
+      if(p.use3DContext)
+      {
+        //normalMode = NORMAL_MODE_AUTO;
+      }
+    };
+
+    p.vertex = function vertex() {
+      var vert = [];
+
+      if (firstVert) { firstVert = false; }
+
+      if (arguments.length === 4) { //x, y, u, v
+        vert[0] = arguments[0];
+        vert[1] = arguments[1];
+        vert[2] = 0;
+        vert[3] = arguments[2];
+        vert[4] = arguments[3];
+      } else { // x, y, z, u, v
+        vert[0] = arguments[0];
+        vert[1] = arguments[1];
+        vert[2] = arguments[2] || 0;
+        vert[3] = arguments[3] || 0;
+        vert[4] = arguments[4] || 0;
+      }
+
+      vert["isVert"] =  true;
+
+      if (p.use3DContext) {
+        // fill rgba
+        vert[5] = fillStyle[0];
+        vert[6] = fillStyle[1];
+        vert[7] = fillStyle[2];
+        vert[8] = fillStyle[3];
+        // stroke rgba
+        vert[9] = strokeStyle[0];
+        vert[10] = strokeStyle[1];
+        vert[11] = strokeStyle[2];
+        vert[12] = strokeStyle[3];
+        //normals
+        vert[13] = normalX;
+        vert[14] = normalY;
+        vert[15] = normalZ;
+      } else {
+        // fill and stroke color
+        vert[5] = currentFillColor;
+        vert[6] = currentStrokeColor;
+      }
+
+      vertArray.push(vert);
+    };
+
+    /*
+      Draw 3D points created from calls to vertex:
+
+      beginShape(POINT);
+      vertex(x, y, 0);
+      ...
+      endShape();
+    */
+    var point3D = function point3D(vArray, cArray){
+      var view = new PMatrix3D();
+      view.scale(1, -1, 1);
+      view.apply(modelView.array());
+      view.transpose();
+
+      var proj = new PMatrix3D();
+      proj.set(projection);
+      proj.transpose();
+
+      curContext.useProgram(programObjectUnlitShape);
+      uniformMatrix(programObjectUnlitShape, "uView", false, view.array());
+      uniformMatrix(programObjectUnlitShape, "uProjection", false, proj.array());
+
+      vertexAttribPointer(programObjectUnlitShape, "aVertex", 3, pointBuffer);
+      curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW);
+
+      vertexAttribPointer(programObjectUnlitShape, "aColor", 4, fillColorBuffer);
+      curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW);
+
+      curContext.drawArrays(curContext.POINTS, 0, vArray.length/3);
+    };
+
+    /*
+      Draw 3D lines created from calls to beginShape/vertex/endShape
+      LINES, LINE_LOOP, etc.
+    */
+    var line3D = function line3D(vArray, mode, cArray){
+      var ctxMode;
+      if (mode === "LINES"){
+        ctxMode = curContext.LINES;
+      }
+      else if(mode === "LINE_LOOP"){
+        ctxMode = curContext.LINE_LOOP;
+      }
+      else{
+        ctxMode = curContext.LINE_STRIP;
+      }
+
+      var view = new PMatrix3D();
+      view.scale(1, -1, 1);
+      view.apply(modelView.array());
+      view.transpose();
+
+      var proj = new PMatrix3D();
+      proj.set(projection);
+      proj.transpose();
+
+      curContext.useProgram(programObjectUnlitShape);
+      uniformMatrix(programObjectUnlitShape, "uView", false, view.array());
+      uniformMatrix(programObjectUnlitShape, "uProjection", false, proj.array());
+
+      vertexAttribPointer(programObjectUnlitShape, "aVertex", 3, lineBuffer);
+      curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW);
+
+      vertexAttribPointer(programObjectUnlitShape, "aColor", 4, strokeColorBuffer);
+      curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW);
+
+      curContext.lineWidth(lineWidth);
+
+      curContext.drawArrays(ctxMode, 0, vArray.length/3);
+    };
+
+    var fill3D = function fill3D(vArray, mode, cArray, tArray){
+      var ctxMode;
+      if(mode === "TRIANGLES"){
+        ctxMode = curContext.TRIANGLES;
+      }
+      else if(mode === "TRIANGLE_FAN"){
+        ctxMode = curContext.TRIANGLE_FAN;
+      }
+      else{
+        ctxMode = curContext.TRIANGLE_STRIP;
+      }
+
+      var view = new PMatrix3D();
+      view.scale(1, -1, 1);
+      view.apply(modelView.array());
+      view.transpose();
+
+      var proj = new PMatrix3D();
+      proj.set(projection);
+      proj.transpose();
+
+      curContext.useProgram( programObject3D );
+      uniformMatrix( programObject3D, "model", false,  [1,0,0,0,  0,1,0,0,   0,0,1,0,   0,0,0,1] );
+      uniformMatrix( programObject3D, "view", false, view.array() );
+      uniformMatrix( programObject3D, "projection", false, proj.array() );
+
+      curContext.enable( curContext.POLYGON_OFFSET_FILL );
+      curContext.polygonOffset( 1, 1 );
+
+      uniformf(programObject3D, "color", [-1,0,0,0]);
+
+      vertexAttribPointer(programObject3D, "Vertex", 3, fillBuffer);
+      curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW);
+
+      vertexAttribPointer(programObject3D, "aColor", 4, fillColorBuffer);
+      curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW);
+
+      // No support for lights....yet
+      disableVertexAttribPointer(programObject3D, "Normal");
+
+      var i;
+
+      if(usingTexture){
+        if(curTextureMode === PConstants.IMAGE){
+          for(i = 0; i < tArray.length; i += 2){
+            tArray[i] = tArray[i]/curTexture.width;
+            tArray[i+1] /= curTexture.height;
+          }
+        }
+
+        // hack to handle when users specifies values
+        // greater than 1.0 for texture coords.
+        for(i = 0; i < tArray.length; i += 2){
+          if( tArray[i+0] > 1.0 ){ tArray[i+0] -= (tArray[i+0] - 1.0);}
+          if( tArray[i+1] > 1.0 ){ tArray[i+1] -= (tArray[i+1] - 1.0);}
+        }
+
+        uniformi(programObject3D, "usingTexture", usingTexture);
+        vertexAttribPointer(programObject3D, "aTexture", 2, shapeTexVBO);
+        curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(tArray), curContext.STREAM_DRAW);
+      }
+
+      curContext.drawArrays( ctxMode, 0, vArray.length/3 );
+      curContext.disable( curContext.POLYGON_OFFSET_FILL );
+    };
+
+    p.endShape = function endShape(mode){
+      var closeShape = mode === PConstants.CLOSE;
+      var lineVertArray = [];
+      var fillVertArray = [];
+      var colorVertArray = [];
+      var strokeVertArray = [];
+      var texVertArray = [];
+
+      firstVert = true;
+      var i, j, k;
+      var last = vertArray.length - 1;
+
+      for (i = 0; i < vertArray.length; i++) {
+        for (j = 0; j < 3; j++) {
+          fillVertArray.push(vertArray[i][j]);
+        }
+      }
+
+      // 5,6,7,8
+      // R,G,B,A
+      for (i = 0; i < vertArray.length; i++) {
+        for (j = 5; j < 9; j++) {
+          colorVertArray.push(vertArray[i][j]);
+        }
+      }
+
+      // 9,10,11,12
+      // R, G, B, A
+      for (i = 0; i < vertArray.length; i++) {
+        for (j = 9; j < 13; j++) {
+          strokeVertArray.push(vertArray[i][j]);
+        }
+      }
+
+      for (i = 0; i < vertArray.length; i++) {
+        texVertArray.push(vertArray[i][3]);
+        texVertArray.push(vertArray[i][4]);
+      }
+
+      if (closeShape) {
+        fillVertArray.push(vertArray[0][0]);
+        fillVertArray.push(vertArray[0][1]);
+        fillVertArray.push(vertArray[0][2]);
+
+        for (i = 5; i < 9; i++) {
+          colorVertArray.push(vertArray[0][i]);
+        }
+
+       for (i = 9; i < 13; i++) {
+          strokeVertArray.push(vertArray[0][i]);
+        }
+
+        texVertArray.push(vertArray[0][3]);
+        texVertArray.push(vertArray[0][4]);
+      }
+
+      if (isCurve && curShape === PConstants.POLYGON || isCurve && curShape === undef) {
+        if (p.use3DContext) {
+          lineVertArray = fillVertArray;
+          if (doStroke) {
+            line3D(lineVertArray, null, strokeVertArray);
+          }
+          if (doFill) {
+            fill3D(fillVertArray, null, colorVertArray); // fill isn't working in 3d curveVertex
+          }
+        } else {
+          if (vertArray.length > 3) {
+            var b = [],
+                s = 1 - curTightness;
+            curContext.beginPath();
+            curContext.moveTo(vertArray[1][0], vertArray[1][1]);
+              /*
+              * Matrix to convert from Catmull-Rom to cubic Bezier
+              * where t = curTightness
+              * |0         1          0         0       |
+              * |(t-1)/6   1          (1-t)/6   0       |
+              * |0         (1-t)/6    1         (t-1)/6 |
+              * |0         0          0         0       |
+              */
+            for (i = 1; (i+2) < vertArray.length; i++) {
+              b[0] = [vertArray[i][0], vertArray[i][1]];
+              b[1] = [vertArray[i][0] + (s * vertArray[i+1][0] - s * vertArray[i-1][0]) / 6,
+                     vertArray[i][1] + (s * vertArray[i+1][1] - s * vertArray[i-1][1]) / 6];
+              b[2] = [vertArray[i+1][0] + (s * vertArray[i][0] - s * vertArray[i+2][0]) / 6,
+                     vertArray[i+1][1] + (s * vertArray[i][1] - s * vertArray[i+2][1]) / 6];
+              b[3] = [vertArray[i+1][0], vertArray[i+1][1]];
+              curContext.bezierCurveTo(b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]);
+            }
+            // close the shape
+            if (closeShape) {
+              curContext.lineTo(vertArray[0][0], vertArray[0][1]);
+            }
+            executeContextFill();
+            executeContextStroke();
+            curContext.closePath();
+          }
+        }
+      } else if (isBezier && curShape === PConstants.POLYGON || isBezier && curShape === undef) {
+        if (p.use3DContext) {
+          lineVertArray = fillVertArray;
+          lineVertArray.splice(lineVertArray.length - 3);
+          strokeVertArray.splice(strokeVertArray.length - 4);
+          if (doStroke) {
+            line3D(lineVertArray, null, strokeVertArray);
+          }
+          if (doFill) {
+            fill3D(fillVertArray, "TRIANGLES", colorVertArray);
+          }
+
+          // TODO: Fill not properly working yet, will fix later
+          /*fillVertArray = [];
+          colorVertArray = [];
+          tempArray.reverse();
+          for(i = 0; (i+1) < 10; i++){
+            for(j = 0; j < 3; j++){
+              fillVertArray.push(tempArray[i][j]);
+            }
+            for(j = 5; j < 9; j++){
+              colorVertArray.push(tempArray[i][j]);
+            }
+            for(j = 0; j < 3; j++){
+              fillVertArray.push(vertArray[i][j]);
+            }
+            for(j = 5; j < 9; j++){
+              colorVertArray.push(vertArray[i][j]);
+            }
+            for(j = 0; j < 3; j++){
+              fillVertArray.push(vertArray[i+1][j]);
+            }
+            for(j = 5; j < 9; j++){
+              colorVertArray.push(vertArray[i][j]);
+            }
+          }
+
+          strokeVertArray = [];
+          for(i = 0; i < tempArray.length/3; i++){
+            strokeVertArray.push(255);
+            strokeVertArray.push(0);
+            strokeVertArray.push(0);
+            strokeVertArray.push(255);
+          }
+          point3D(tempArray, strokeVertArray);*/
+        } else {
+          curContext.beginPath();
+          for (i = 0; i < vertArray.length; i++) {
+            if (vertArray[i]["isVert"] === true) { //if it is a vertex move to the position
+              if (vertArray[i]["moveTo"] === true) {
+                curContext.moveTo(vertArray[i][0], vertArray[i][1]);
+              } else if (vertArray[i]["moveTo"] === false){
+                curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+              } else {
+                curContext.moveTo(vertArray[i][0], vertArray[i][1]);
+              }
+            } else { //otherwise continue drawing bezier
+              curContext.bezierCurveTo(vertArray[i][0], vertArray[i][1], vertArray[i][2], vertArray[i][3], vertArray[i][4], vertArray[i][5]);
+            }
+          }
+          // close the shape
+          if (closeShape) {
+            curContext.lineTo(vertArray[0][0], vertArray[0][1]);
+          }
+          executeContextFill();
+          executeContextStroke();
+          curContext.closePath();
+        }
+      } else {
+        if (p.use3DContext) { // 3D context
+          if (curShape === PConstants.POINTS) {
+            for (i = 0; i < vertArray.length; i++) {
+              for (j = 0; j < 3; j++) {
+                lineVertArray.push(vertArray[i][j]);
+              }
+            }
+            point3D(lineVertArray, strokeVertArray);
+          } else if (curShape === PConstants.LINES) {
+            for (i = 0; i < vertArray.length; i++) {
+              for (j = 0; j < 3; j++) {
+                lineVertArray.push(vertArray[i][j]);
+              }
+            }
+            for (i = 0; i < vertArray.length; i++) {
+              for (j = 5; j < 9; j++) {
+                colorVertArray.push(vertArray[i][j]);
+              }
+            }
+            line3D(lineVertArray, "LINES", strokeVertArray);
+          } else if (curShape === PConstants.TRIANGLES) {
+            if (vertArray.length > 2) {
+              for (i = 0; (i+2) < vertArray.length; i+=3) {
+                fillVertArray = [];
+                texVertArray = [];
+                lineVertArray = [];
+                colorVertArray = [];
+                strokeVertArray = [];
+                for (j = 0; j < 3; j++) {
+                  for (k = 0; k < 3; k++) {
+                    lineVertArray.push(vertArray[i+j][k]);
+                    fillVertArray.push(vertArray[i+j][k]);
+                  }
+                }
+                for (j = 0; j < 3; j++) {
+                  for (k = 3; k < 5; k++) {
+                    texVertArray.push(vertArray[i+j][k]);
+                  }
+                }
+                for (j = 0; j < 3; j++) {
+                  for (k = 5; k < 9; k++) {
+                    colorVertArray.push(vertArray[i+j][k]);
+                    strokeVertArray.push(vertArray[i+j][k+4]);
+                  }
+                }
+                if (doStroke) {
+                  line3D(lineVertArray, "LINE_LOOP", strokeVertArray );
+                }
+                if (doFill || usingTexture) {
+                  fill3D(fillVertArray, "TRIANGLES", colorVertArray, texVertArray);
+                }
+              }
+            }
+          } else if (curShape === PConstants.TRIANGLE_STRIP) {
+            if (vertArray.length > 2) {
+              for (i = 0; (i+2) < vertArray.length; i++) {
+                lineVertArray = [];
+                fillVertArray = [];
+                strokeVertArray = [];
+                colorVertArray = [];
+                texVertArray = [];
+                for (j = 0; j < 3; j++) {
+                  for (k = 0; k < 3; k++) {
+                    lineVertArray.push(vertArray[i+j][k]);
+                    fillVertArray.push(vertArray[i+j][k]);
+                  }
+                }
+                for (j = 0; j < 3; j++) {
+                  for (k = 3; k < 5; k++) {
+                    texVertArray.push(vertArray[i+j][k]);
+                  }
+                }
+                for (j = 0; j < 3; j++) {
+                  for (k = 5; k < 9; k++) {
+                    strokeVertArray.push(vertArray[i+j][k+4]);
+                    colorVertArray.push(vertArray[i+j][k]);
+                  }
+                }
+
+                if (doFill || usingTexture) {
+                  fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray);
+                }
+                if (doStroke) {
+                  line3D(lineVertArray, "LINE_LOOP", strokeVertArray);
+                }
+              }
+            }
+          } else if (curShape === PConstants.TRIANGLE_FAN) {
+            if (vertArray.length > 2) {
+              for (i = 0; i < 3; i++) {
+                for (j = 0; j < 3; j++) {
+                  lineVertArray.push(vertArray[i][j]);
+                }
+              }
+              for (i = 0; i < 3; i++) {
+                for (j = 9; j < 13; j++) {
+                  strokeVertArray.push(vertArray[i][j]);
+                }
+              }
+              if (doStroke) {
+                line3D(lineVertArray, "LINE_LOOP", strokeVertArray);
+              }
+
+              for (i = 2; (i+1) < vertArray.length; i++) {
+                lineVertArray = [];
+                strokeVertArray = [];
+                lineVertArray.push(vertArray[0][0]);
+                lineVertArray.push(vertArray[0][1]);
+                lineVertArray.push(vertArray[0][2]);
+
+                strokeVertArray.push(vertArray[0][9]);
+                strokeVertArray.push(vertArray[0][10]);
+                strokeVertArray.push(vertArray[0][11]);
+                strokeVertArray.push(vertArray[0][12]);
+
+                for (j = 0; j < 2; j++) {
+                  for (k = 0; k < 3; k++) {
+                    lineVertArray.push(vertArray[i+j][k]);
+                  }
+                }
+                for (j = 0; j < 2; j++) {
+                  for (k = 9; k < 13; k++) {
+                    strokeVertArray.push(vertArray[i+j][k]);
+                  }
+                }
+                if (doStroke) {
+                  line3D(lineVertArray, "LINE_STRIP",strokeVertArray);
+                }
+              }
+              if (doFill || usingTexture) {
+                fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray);
+              }
+            }
+          } else if (curShape === PConstants.QUADS) {
+            for (i = 0; (i + 3) < vertArray.length; i+=4) {
+              lineVertArray = [];
+              for (j = 0; j < 4; j++) {
+                for (k = 0; k < 3; k++) {
+                  lineVertArray.push(vertArray[i+j][k]);
+                }
+              }
+              if (doStroke) {
+                line3D(lineVertArray, "LINE_LOOP",strokeVertArray);
+              }
+
+              if (doFill) {
+                fillVertArray = [];
+                colorVertArray = [];
+                texVertArray = [];
+                for (j = 0; j < 3; j++) {
+                  fillVertArray.push(vertArray[i][j]);
+                }
+                for (j = 5; j < 9; j++) {
+                  colorVertArray.push(vertArray[i][j]);
+                }
+
+                for (j = 0; j < 3; j++) {
+                  fillVertArray.push(vertArray[i+1][j]);
+                }
+                for (j = 5; j < 9; j++) {
+                  colorVertArray.push(vertArray[i+1][j]);
+                }
+
+                for (j = 0; j < 3; j++) {
+                  fillVertArray.push(vertArray[i+3][j]);
+                }
+                for (j = 5; j < 9; j++) {
+                  colorVertArray.push(vertArray[i+3][j]);
+                }
+
+                for (j = 0; j < 3; j++) {
+                  fillVertArray.push(vertArray[i+2][j]);
+                }
+                for (j = 5; j < 9; j++) {
+                  colorVertArray.push(vertArray[i+2][j]);
+                }
+
+                if (usingTexture) {
+                  texVertArray.push(vertArray[i+0][3]);
+                  texVertArray.push(vertArray[i+0][4]);
+                  texVertArray.push(vertArray[i+1][3]);
+                  texVertArray.push(vertArray[i+1][4]);
+                  texVertArray.push(vertArray[i+3][3]);
+                  texVertArray.push(vertArray[i+3][4]);
+                  texVertArray.push(vertArray[i+2][3]);
+                  texVertArray.push(vertArray[i+2][4]);
+                }
+
+                fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray);
+              }
+            }
+          } else if (curShape === PConstants.QUAD_STRIP) {
+            var tempArray = [];
+            if (vertArray.length > 3) {
+              for (i = 0; i < 2; i++) {
+                for (j = 0; j < 3; j++) {
+                  lineVertArray.push(vertArray[i][j]);
+                }
+              }
+
+              for (i = 0; i < 2; i++) {
+                for (j = 9; j < 13; j++) {
+                  strokeVertArray.push(vertArray[i][j]);
+                }
+              }
+
+              line3D(lineVertArray, "LINE_STRIP", strokeVertArray);
+              if (vertArray.length > 4 && vertArray.length % 2 > 0) {
+                tempArray = fillVertArray.splice(fillVertArray.length - 3);
+                vertArray.pop();
+              }
+              for (i = 0; (i+3) < vertArray.length; i+=2) {
+                lineVertArray = [];
+                strokeVertArray = [];
+                for (j = 0; j < 3; j++) {
+                  lineVertArray.push(vertArray[i+1][j]);
+                }
+                for (j = 0; j < 3; j++) {
+                  lineVertArray.push(vertArray[i+3][j]);
+                }
+                for (j = 0; j < 3; j++) {
+                  lineVertArray.push(vertArray[i+2][j]);
+                }
+                for (j = 0; j < 3; j++) {
+                  lineVertArray.push(vertArray[i+0][j]);
+                }
+                for (j = 9; j < 13; j++) {
+                  strokeVertArray.push(vertArray[i+1][j]);
+                }
+                for (j = 9; j < 13; j++) {
+                  strokeVertArray.push(vertArray[i+3][j]);
+                }
+                for (j = 9; j < 13; j++) {
+                  strokeVertArray.push(vertArray[i+2][j]);
+                }
+                for (j = 9; j < 13; j++) {
+                  strokeVertArray.push(vertArray[i+0][j]);
+                }
+                if (doStroke) {
+                  line3D(lineVertArray, "LINE_STRIP", strokeVertArray);
+                }
+              }
+
+              if (doFill || usingTexture) {
+                fill3D(fillVertArray, "TRIANGLE_LIST", colorVertArray, texVertArray);
+              }
+            }
+          }
+          // If the user didn't specify a type (LINES, TRIANGLES, etc)
+          else {
+            // If only one vertex was specified, it must be a point
+            if (vertArray.length === 1) {
+              for (j = 0; j < 3; j++) {
+                lineVertArray.push(vertArray[0][j]);
+              }
+              for (j = 9; j < 13; j++) {
+                strokeVertArray.push(vertArray[0][j]);
+              }
+              point3D(lineVertArray,strokeVertArray);
+            } else {
+              for (i = 0; i < vertArray.length; i++) {
+                for (j = 0; j < 3; j++) {
+                  lineVertArray.push(vertArray[i][j]);
+                }
+                for (j = 5; j < 9; j++) {
+                  strokeVertArray.push(vertArray[i][j]);
+                }
+              }
+              if (closeShape) {
+                line3D(lineVertArray, "LINE_LOOP", strokeVertArray);
+              } else {
+                line3D(lineVertArray, "LINE_STRIP", strokeVertArray);
+              }
+
+              // fill is ignored if textures are used
+              if (doFill || usingTexture) {
+                fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray);
+              }
+            }
+          }
+          // everytime beginShape is followed by a call to
+          // texture(), texturing it turned back on. We do this to
+          // figure out if the shape should be textured or filled
+          // with a color.
+          usingTexture = false;
+          curContext.useProgram(programObject3D);
+          uniformi(programObject3D, "usingTexture", usingTexture);
+        }
+
+        // 2D context
+        else {
+          if (curShape === PConstants.POINTS) {
+            for (i = 0; i < vertArray.length; i++) {
+              if (doStroke) {
+                p.stroke(vertArray[i][6]);
+              }
+              p.point(vertArray[i][0], vertArray[i][1]);
+            }
+          } else if (curShape === PConstants.LINES) {
+            for (i = 0; (i + 1) < vertArray.length; i+=2) {
+              if (doStroke) {
+                p.stroke(vertArray[i+1][6]);
+              }
+              p.line(vertArray[i][0], vertArray[i][1], vertArray[i+1][0], vertArray[i+1][1]);
+            }
+          } else if (curShape === PConstants.TRIANGLES) {
+            for (i = 0; (i + 2) < vertArray.length; i+=3) {
+              curContext.beginPath();
+              curContext.moveTo(vertArray[i][0], vertArray[i][1]);
+              curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]);
+              curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]);
+              curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+
+              if (doFill) {
+                p.fill(vertArray[i+2][5]);
+                executeContextFill();
+              }
+              if (doStroke) {
+                p.stroke(vertArray[i+2][6]);
+                executeContextStroke();
+              }
+
+              curContext.closePath();
+            }
+          } else if (curShape === PConstants.TRIANGLE_STRIP) {
+            for (i = 0; (i+1) < vertArray.length; i++) {
+              curContext.beginPath();
+              curContext.moveTo(vertArray[i+1][0], vertArray[i+1][1]);
+              curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+
+              if (doStroke) {
+                p.stroke(vertArray[i+1][6]);
+              }
+              if (doFill) {
+                p.fill(vertArray[i+1][5]);
+              }
+
+              if (i + 2 < vertArray.length) {
+                curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]);
+                if (doStroke) {
+                  p.stroke(vertArray[i+2][6]);
+                }
+                if (doFill) {
+                  p.fill(vertArray[i+2][5]);
+                }
+              }
+              executeContextFill();
+              executeContextStroke();
+              curContext.closePath();
+            }
+          } else if (curShape === PConstants.TRIANGLE_FAN) {
+            if (vertArray.length > 2) {
+              curContext.beginPath();
+              curContext.moveTo(vertArray[0][0], vertArray[0][1]);
+              curContext.lineTo(vertArray[1][0], vertArray[1][1]);
+              curContext.lineTo(vertArray[2][0], vertArray[2][1]);
+
+              if (doFill) {
+                p.fill(vertArray[2][5]);
+                executeContextFill();
+              }
+              if (doStroke) {
+                p.stroke(vertArray[2][6]);
+                executeContextStroke();
+              }
+
+              curContext.closePath();
+              for (i = 3; i < vertArray.length; i++) {
+                curContext.beginPath();
+                curContext.moveTo(vertArray[0][0], vertArray[0][1]);
+                curContext.lineTo(vertArray[i-1][0], vertArray[i-1][1]);
+                curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+
+                if (doFill) {
+                  p.fill(vertArray[i][5]);
+                  executeContextFill();
+                }
+                if (doStroke) {
+                  p.stroke(vertArray[i][6]);
+                  executeContextStroke();
+                }
+
+                curContext.closePath();
+              }
+            }
+          } else if (curShape === PConstants.QUADS) {
+            for (i = 0; (i + 3) < vertArray.length; i+=4) {
+              curContext.beginPath();
+              curContext.moveTo(vertArray[i][0], vertArray[i][1]);
+              for (j = 1; j < 4; j++) {
+                curContext.lineTo(vertArray[i+j][0], vertArray[i+j][1]);
+              }
+              curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+
+              if (doFill) {
+                p.fill(vertArray[i+3][5]);
+                executeContextFill();
+              }
+              if (doStroke) {
+                p.stroke(vertArray[i+3][6]);
+                executeContextStroke();
+              }
+
+              curContext.closePath();
+            }
+          } else if (curShape === PConstants.QUAD_STRIP) {
+            if (vertArray.length > 3) {
+              for (i = 0; (i+1) < vertArray.length; i+=2) {
+                curContext.beginPath();
+                if (i+3 < vertArray.length) {
+                  curContext.moveTo(vertArray[i+2][0], vertArray[i+2][1]);
+                  curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+                  curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]);
+                  curContext.lineTo(vertArray[i+3][0], vertArray[i+3][1]);
+
+                  if (doFill) {
+                    p.fill(vertArray[i+3][5]);
+                  }
+                  if (doStroke) {
+                    p.stroke(vertArray[i+3][6]);
+                  }
+                } else {
+                  curContext.moveTo(vertArray[i][0], vertArray[i][1]);
+                  curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]);
+                }
+                executeContextFill();
+                executeContextStroke();
+                curContext.closePath();
+              }
+            }
+          } else {
+            curContext.beginPath();
+            curContext.moveTo(vertArray[0][0], vertArray[0][1]);
+            for (i = 1; i < vertArray.length; i++) {
+              if (vertArray[i]["isVert"] === true ) { //if it is a vertex move to the position
+                if (vertArray[i]["moveTo"] === true) {
+                  curContext.moveTo(vertArray[i][0], vertArray[i][1]);
+                } else if (vertArray[i]["moveTo"] === false){
+                  curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+                } else {
+                  curContext.lineTo(vertArray[i][0], vertArray[i][1]);
+                }
+              }
+            }
+            if (closeShape) {
+              curContext.lineTo(vertArray[0][0], vertArray[0][1]);
+            }
+            executeContextFill();
+            executeContextStroke();
+            curContext.closePath();
+          }
+        }
+      }
+      isCurve = false;
+      isBezier = false;
+      curveVertArray = [];
+      curveVertCount = 0;
+    };
+
+    //used by both curveDetail and bezierDetail
+    var splineForward = function(segments, matrix) {
+      var f = 1.0 / segments;
+      var ff = f * f;
+      var fff = ff * f;
+
+      matrix.set(0, 0, 0, 1, fff, ff, f, 0, 6 * fff, 2 * ff, 0, 0, 6 * fff, 0, 0, 0);
+    };
+
+    //internal curveInit
+    //used by curveDetail, curveTightness
+    var curveInit = function() {
+      // allocate only if/when used to save startup time
+      if (!curveDrawMatrix) {
+        curveBasisMatrix = new PMatrix3D();
+        curveDrawMatrix = new PMatrix3D();
+        curveInited = true;
+      }
+
+      var s = curTightness;
+      curveBasisMatrix.set(((s - 1) / 2).toFixed(2), ((s + 3) / 2).toFixed(2),
+                           ((-3 - s) / 2).toFixed(2), ((1 - s) / 2).toFixed(2),
+                           (1 - s), ((-5 - s) / 2).toFixed(2), (s + 2), ((s - 1) / 2).toFixed(2),
+                           ((s - 1) / 2).toFixed(2), 0, ((1 - s) / 2).toFixed(2), 0, 0, 1, 0, 0);
+
+      splineForward(curveDet, curveDrawMatrix);
+
+      if (!bezierBasisInverse) {
+        //bezierBasisInverse = bezierBasisMatrix.get();
+        //bezierBasisInverse.invert();
+        curveToBezierMatrix = new PMatrix3D();
+      }
+
+      // TODO only needed for PGraphicsJava2D? if so, move it there
+      // actually, it's generally useful for other renderers, so keep it
+      // or hide the implementation elsewhere.
+      curveToBezierMatrix.set(curveBasisMatrix);
+      curveToBezierMatrix.preApply(bezierBasisInverse);
+
+      // multiply the basis and forward diff matrices together
+      // saves much time since this needn't be done for each curve
+      curveDrawMatrix.apply(curveBasisMatrix);
+    };
+
+    p.bezierVertex = function bezierVertex() {
+      isBezier = true;
+      var vert = [];
+      if (firstVert) {
+        throw ("vertex() must be used at least once before calling bezierVertex()");
+      } else {
+        if (arguments.length === 9) {
+          if (p.use3DContext) {
+            if (bezierDrawMatrix === undef) {
+              bezierDrawMatrix = new PMatrix3D();
+            }
+            // setup matrix for forward differencing to speed up drawing
+            var lastPoint = vertArray.length - 1;
+            splineForward( bezDetail, bezierDrawMatrix );
+            bezierDrawMatrix.apply( bezierBasisMatrix );
+            var draw = bezierDrawMatrix.array();
+            var x1 = vertArray[lastPoint][0],
+                y1 = vertArray[lastPoint][1],
+                z1 = vertArray[lastPoint][2];
+            var xplot1 = draw[4] * x1 + draw[5] * arguments[0] + draw[6] * arguments[3] + draw[7] * arguments[6];
+            var xplot2 = draw[8] * x1 + draw[9] * arguments[0] + draw[10]* arguments[3] + draw[11]* arguments[6];
+            var xplot3 = draw[12]* x1 + draw[13]* arguments[0] + draw[14]* arguments[3] + draw[15]* arguments[6];
+
+            var yplot1 = draw[4] * y1 + draw[5] * arguments[1] + draw[6] * arguments[4] + draw[7] * arguments[7];
+            var yplot2 = draw[8] * y1 + draw[9] * arguments[1] + draw[10]* arguments[4] + draw[11]* arguments[7];
+            var yplot3 = draw[12]* y1 + draw[13]* arguments[1] + draw[14]* arguments[4] + draw[15]* arguments[7];
+
+            var zplot1 = draw[4] * z1 + draw[5] * arguments[2] + draw[6] * arguments[5] + draw[7] * arguments[8];
+            var zplot2 = draw[8] * z1 + draw[9] * arguments[2] + draw[10]* arguments[5] + draw[11]* arguments[8];
+            var zplot3 = draw[12]* z1 + draw[13]* arguments[2] + draw[14]* arguments[5] + draw[15]* arguments[8];
+            for (var j = 0; j < bezDetail; j++) {
+              x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3;
+              y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3;
+              z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3;
+              p.vertex(x1, y1, z1);
+            }
+            p.vertex(arguments[6], arguments[7], arguments[8]);
+          }
+        } else {
+          for (var i = 0; i < arguments.length; i++) {
+            vert[i] = arguments[i];
+          }
+          vertArray.push(vert);
+          vertArray[vertArray.length -1]["isVert"] = false;
+        }
+      }
+    };
+
+    // texImage2D function changed http://www.khronos.org/webgl/public-mailing-list/archives/1007/msg00034.html
+    // This method tries the new argument pattern first and falls back to the old version
+    var executeTexImage2D = function () {
+      var canvas2d = document.createElement('canvas');
+
+      try { // new way.
+        curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, canvas2d);
+        executeTexImage2D = function(texture) {
+          curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, texture);
+        };
+      } catch (e) {
+        executeTexImage2D = function(texture) {
+          curContext.texImage2D(curContext.TEXTURE_2D, 0, texture, false);
+        };
+      }
+
+      executeTexImage2D.apply(this, arguments);
+    };
+
+    p.texture = function(pimage) {
+      if (pimage.localName === "canvas") {
+        curContext.bindTexture(curContext.TEXTURE_2D, canTex);
+        executeTexImage2D(pimage);
+        curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR);
+        curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR);
+        curContext.generateMipmap(curContext.TEXTURE_2D);
+      } else if (!pimage.__texture) {
+        var texture = curContext.createTexture();
+        pimage.__texture = texture;
+
+        var cvs = document.createElement('canvas');
+        cvs.width = pimage.width;
+        cvs.height = pimage.height;
+        var ctx = cvs.getContext('2d');
+        var textureImage = ctx.createImageData(cvs.width, cvs.height);
+
+        var imgData = pimage.toImageData();
+
+        for (var i = 0; i < cvs.width; i += 1) {
+          for (var j = 0; j < cvs.height; j += 1) {
+          var index = (j * cvs.width + i) * 4;
+            textureImage.data[index + 0] = imgData.data[index + 0];
+            textureImage.data[index + 1] = imgData.data[index + 1];
+            textureImage.data[index + 2] = imgData.data[index + 2];
+            textureImage.data[index + 3] = 255;
+          }
+        }
+
+        ctx.putImageData(textureImage, 0, 0);
+        pimage.__cvs = cvs;
+
+        curContext.bindTexture(curContext.TEXTURE_2D, pimage.__texture);
+        curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR_MIPMAP_LINEAR);
+        curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR);
+        curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_T, curContext.CLAMP_TO_EDGE);
+        curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_S, curContext.CLAMP_TO_EDGE);
+        executeTexImage2D(pimage.__cvs);
+        curContext.generateMipmap(curContext.TEXTURE_2D);
+      } else {
+        curContext.bindTexture(curContext.TEXTURE_2D, pimage.__texture);
+      }
+
+      curTexture.width = pimage.width;
+      curTexture.height = pimage.height;
+      usingTexture = true;
+      curContext.useProgram(programObject3D);
+      uniformi(programObject3D, "usingTexture", usingTexture);
+    };
+
+    p.textureMode = function(mode){
+      curTextureMode = mode;
+    };
+
+    var curveVertexSegment = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) {
+      var x0 = x2;
+      var y0 = y2;
+      var z0 = z2;
+
+      var draw = curveDrawMatrix.array();
+
+      var xplot1 = draw[4] * x1 + draw[5] * x2 + draw[6] * x3 + draw[7] * x4;
+      var xplot2 = draw[8] * x1 + draw[9] * x2 + draw[10] * x3 + draw[11] * x4;
+      var xplot3 = draw[12] * x1 + draw[13] * x2 + draw[14] * x3 + draw[15] * x4;
+
+      var yplot1 = draw[4] * y1 + draw[5] * y2 + draw[6] * y3 + draw[7] * y4;
+      var yplot2 = draw[8] * y1 + draw[9] * y2 + draw[10] * y3 + draw[11] * y4;
+      var yplot3 = draw[12] * y1 + draw[13] * y2 + draw[14] * y3 + draw[15] * y4;
+
+      var zplot1 = draw[4] * z1 + draw[5] * z2 + draw[6] * z3 + draw[7] * z4;
+      var zplot2 = draw[8] * z1 + draw[9] * z2 + draw[10] * z3 + draw[11] * z4;
+      var zplot3 = draw[12] * z1 + draw[13] * z2 + draw[14] * z3 + draw[15] * z4;
+
+      p.vertex(x0, y0, z0);
+      for (var j = 0; j < curveDet; j++) {
+        x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3;
+        y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3;
+        z0 += zplot1; zplot1 += zplot2; zplot2 += zplot3;
+        p.vertex(x0, y0, z0);
+      }
+    };
+
+    p.curveVertex = function(x, y, z) {
+      isCurve = true;
+      if(p.use3DContext){
+        if (!curveInited){
+          curveInit();
+        }
+        var vert = [];
+        vert[0] = x;
+        vert[1] = y;
+        vert[2] = z;
+        curveVertArray.push(vert);
+        curveVertCount++;
+
+        if (curveVertCount > 3){
+          curveVertexSegment( curveVertArray[curveVertCount-4][0],
+                              curveVertArray[curveVertCount-4][1],
+                              curveVertArray[curveVertCount-4][2],
+                              curveVertArray[curveVertCount-3][0],
+                              curveVertArray[curveVertCount-3][1],
+                              curveVertArray[curveVertCount-3][2],
+                              curveVertArray[curveVertCount-2][0],
+                              curveVertArray[curveVertCount-2][1],
+                              curveVertArray[curveVertCount-2][2],
+                              curveVertArray[curveVertCount-1][0],
+                              curveVertArray[curveVertCount-1][1],
+                              curveVertArray[curveVertCount-1][2] );
+        }
+      }
+      else{
+        p.vertex(x, y, z);
+      }
+    };
+
+    p.curve = function curve() {
+      if (arguments.length === 8) // curve(x1, y1, x2, y2, x3, y3, x4, y4)
+      {
+        p.beginShape();
+        p.curveVertex(arguments[0], arguments[1]);
+        p.curveVertex(arguments[2], arguments[3]);
+        p.curveVertex(arguments[4], arguments[5]);
+        p.curveVertex(arguments[6], arguments[7]);
+        p.endShape();
+      } else { // curve( x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
+        if (p.use3DContext) {
+          p.beginShape();
+          p.curveVertex(arguments[0], arguments[1], arguments[2]);
+          p.curveVertex(arguments[3], arguments[4], arguments[5]);
+          p.curveVertex(arguments[6], arguments[7], arguments[8]);
+          p.curveVertex(arguments[9], arguments[10], arguments[11]);
+          p.endShape();
+        }
+      }
+    };
+
+    p.curveTightness = function(tightness) {
+      curTightness = tightness;
+    };
+
+    p.curveDetail = function curveDetail( detail ) {
+      curveDet = detail;
+      curveInit();
+    };
+
+    p.rectMode = function rectMode(aRectMode) {
+      curRectMode = aRectMode;
+    };
+
+    p.imageMode = function(mode) {
+      switch (mode) {
+      case PConstants.CORNER:
+        imageModeConvert = imageModeCorner;
+        break;
+      case PConstants.CORNERS:
+        imageModeConvert = imageModeCorners;
+        break;
+      case PConstants.CENTER:
+        imageModeConvert = imageModeCenter;
+        break;
+      default:
+        throw "Invalid imageMode";
+      }
+    };
+
+    p.ellipseMode = function ellipseMode(aEllipseMode) {
+      curEllipseMode = aEllipseMode;
+    };
+
+    p.arc = function arc(x, y, width, height, start, stop) {
+      if (width <= 0) {
+        return;
+      }
+
+      if (curEllipseMode === PConstants.CORNER) {
+        x += width / 2;
+        y += height / 2;
+      }
+
+      curContext.moveTo(x, y);
+      curContext.beginPath();
+      curContext.arc(x, y, curEllipseMode === PConstants.CENTER_RADIUS ? width : width / 2, start, stop, false);
+
+      executeContextStroke();
+      curContext.lineTo(x, y);
+
+      executeContextFill();
+      curContext.closePath();
+    };
+
+    p.line = function line() {
+      var x1, y1, z1, x2, y2, z2;
+
+      if (p.use3DContext) {
+        if (arguments.length === 6) {
+          x1 = arguments[0];
+          y1 = arguments[1];
+          z1 = arguments[2];
+          x2 = arguments[3];
+          y2 = arguments[4];
+          z2 = arguments[5];
+        } else if (arguments.length === 4) {
+          x1 = arguments[0];
+          y1 = arguments[1];
+          z1 = 0;
+          x2 = arguments[2];
+          y2 = arguments[3];
+          z2 = 0;
+        }
+
+        var lineVerts = [x1, y1, z1, x2, y2, z2];
+
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.transpose();
+
+        var proj = new PMatrix3D();
+        proj.set(projection);
+        proj.transpose();
+
+        if (lineWidth > 0 && doStroke) {
+          curContext.useProgram(programObject2D);
+
+          uniformMatrix(programObject2D, "model", false, [1,0,0,0,  0,1,0,0,  0,0,1,0,  0,0,0,1]);
+          uniformMatrix(programObject2D, "view", false, view.array());
+          uniformMatrix(programObject2D, "projection", false, proj.array());
+
+          uniformf(programObject2D, "color", strokeStyle);
+          uniformi(programObject2D, "picktype", 0);
+
+          curContext.lineWidth(lineWidth);
+
+          vertexAttribPointer(programObject2D, "Vertex", 3, lineBuffer);
+          disableVertexAttribPointer(programObject2D, "aTextureCoord");
+
+          curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(lineVerts), curContext.STREAM_DRAW);
+          curContext.drawArrays(curContext.LINES, 0, 2);
+        }
+      } else {
+        x1 = arguments[0];
+        y1 = arguments[1];
+        x2 = arguments[2];
+        y2 = arguments[3];
+
+        // if line is parallel to axis and lineWidth is less than 1px, trying to do it "crisp"
+        if ((x1 === x2 || y1 === y2) && lineWidth <= 1.0 && doStroke && curSketch.options.crispLines) {
+          var temp;
+          if(x1 === x2) {
+            if(y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
+            for(var y=y1;y<=y2;++y) {
+              p.set(x1, y, currentStrokeColor);
+            }
+          } else {
+            if(x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
+            for(var x=x1;x<=x2;++x) {
+              p.set(x, y1, currentStrokeColor);
+            }
+          }
+          return;
+        }
+
+        if (doStroke) {
+          curContext.beginPath();
+          curContext.moveTo(x1 || 0, y1 || 0);
+          curContext.lineTo(x2 || 0, y2 || 0);
+          executeContextStroke();
+          curContext.closePath();
+        }
+      }
+    };
+
+    p.bezier = function bezier() {
+      if( arguments.length === 8 && !p.use3DContext ){
+          p.beginShape();
+          p.vertex( arguments[0], arguments[1] );
+          p.bezierVertex( arguments[2], arguments[3],
+                          arguments[4], arguments[5],
+                          arguments[6], arguments[7] );
+          p.endShape();
+      }
+      else if( arguments.length === 12 && p.use3DContext ){
+          p.beginShape();
+          p.vertex( arguments[0], arguments[1], arguments[2] );
+          p.bezierVertex( arguments[3], arguments[4], arguments[5],
+                          arguments[6], arguments[7], arguments[8],
+                          arguments[9], arguments[10], arguments[11] );
+          p.endShape();
+      }
+      else {
+        throw("Please use the proper parameters!");
+      }
+    };
+    p.bezierDetail = function bezierDetail( detail ){
+      bezDetail = detail;
+    };
+
+    p.bezierPoint = function bezierPoint(a, b, c, d, t) {
+      return (1 - t) * (1 - t) * (1 - t) * a + 3 * (1 - t) * (1 - t) * t * b + 3 * (1 - t) * t * t * c + t * t * t * d;
+    };
+
+    p.bezierTangent = function bezierTangent(a, b, c, d, t) {
+      return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
+    };
+
+    p.curvePoint = function curvePoint(a, b, c, d, t) {
+      return 0.5 * ((2 * b) + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t * t + (-a + 3 * b - 3 * c + d) * t * t * t);
+    };
+
+    p.curveTangent = function curveTangent(a, b, c, d, t) {
+      return 0.5 * ((-a + c) + 2 * (2 * a - 5 * b + 4 * c - d) * t + 3 * (-a + 3 * b - 3 * c + d) * t * t);
+    };
+
+    p.triangle = function triangle(x1, y1, x2, y2, x3, y3) {
+      p.beginShape(PConstants.TRIANGLES);
+      p.vertex(x1, y1, 0);
+      p.vertex(x2, y2, 0);
+      p.vertex(x3, y3, 0);
+      p.endShape();
+    };
+
+    p.quad = function quad(x1, y1, x2, y2, x3, y3, x4, y4) {
+      p.beginShape(PConstants.QUADS);
+      p.vertex(x1, y1, 0);
+      p.vertex(x2, y2, 0);
+      p.vertex(x3, y3, 0);
+      p.vertex(x4, y4, 0);
+      p.endShape();
+    };
+
+    p.rect = function rect(x, y, width, height) {
+      if (p.use3DContext) {
+        // Modeling transformation
+        var model = new PMatrix3D();
+        model.translate(x, y, 0);
+        model.scale(width, height, 1);
+        model.transpose();
+
+        // viewing transformation needs to have Y flipped
+        // becuase that's what Processing does.
+        var view = new PMatrix3D();
+        view.scale(1, -1, 1);
+        view.apply(modelView.array());
+        view.transpose();
+
+        var proj = new PMatrix3D();
+        proj.set(projection);
+        proj.transpose();
+
+        if (lineWidth > 0 && doStroke) {
+          curContext.useProgram(programObject2D);
+          uniformMatrix(programObject2D, "model", false, model.array());
+          uniformMatrix(programObject2D, "view", false, view.array());
+          uniformMatrix(programObject2D, "projection", false, proj.array());
+
+          uniformf(programObject2D, "color", strokeStyle);
+          uniformi(programObject2D, "picktype", 0);
+
+          vertexAttribPointer(programObject2D, "Vertex", 3, rectBuffer);
+          disableVertexAttribPointer(programObject2D, "aTextureCoord");
+
+          curContext.lineWidth(lineWidth);
+          curContext.drawArrays(curContext.LINE_LOOP, 0, rectVerts.length / 3);
+        }
+
+        if (doFill) {
+          curContext.useProgram(programObject3D);
+          uniformMatrix(programObject3D, "model", false, model.array());
+          uniformMatrix(programObject3D, "view", false, view.array());
+          uniformMatrix(programObject3D, "projection", false, proj.array());
+
+          // fix stitching problems. (lines get occluded by triangles
+          // since they share the same depth values). This is not entirely
+          // working, but it's a start for drawing the outline. So
+          // developers can start playing around with styles.
+          curContext.enable(curContext.POLYGON_OFFSET_FILL);
+          curContext.polygonOffset(1, 1);
+
+          uniformf(programObject3D, "color", fillStyle);
+
+          var v = new PMatrix3D();
+          v.set(view);
+
+          var m = new PMatrix3D();
+          m.set(model);
+
+          v.mult(m);
+
+          var normalMatrix = new PMatrix3D();
+          normalMatrix.set(v);
+          normalMatrix.invert();
+          normalMatrix.transpose();
+
+          uniformMatrix(programObject3D, "normalTransform", false, normalMatrix.array());
+
+          vertexAttribPointer(programObject3D, "Vertex", 3, rectBuffer);
+          vertexAttribPointer(programObject3D, "Normal", 3, rectNormBuffer);
+
+          curContext.drawArrays(curContext.TRIANGLE_FAN, 0, rectVerts.length / 3);
+          curContext.disable(curContext.POLYGON_OFFSET_FILL);
+        }
+      }
+      else{
+        if (!width && !height) {
+          return;
+        }
+
+        // if only stroke is enabled, do it "crisp"
+        if (doStroke && !doFill && lineWidth <= 1.0 && curSketch.options.crispLines) {
+          var i, x2 = x + width - 1, y2 = y + height - 1;
+          for(i=0;i<width;++i) {
+            p.set(x + i, y, currentStrokeColor);
+            p.set(x + i, y2, currentStrokeColor);
+          }
+          for(i=0;i<height;++i) {
+            p.set(x, y + i, currentStrokeColor);
+            p.set(x2, y + i, currentStrokeColor);
+          }
+          return;
+        }
+
+        curContext.beginPath();
+
+        var offsetStart = 0;
+        var offsetEnd = 0;
+
+        if (curRectMode === PConstants.CORNERS) {
+          width -= x;
+          height -= y;
+        }
+
+        if (curRectMode === PConstants.RADIUS) {
+          width *= 2;
+          height *= 2;
+        }
+
+        if (curRectMode === PConstants.CENTER || curRectMode === PConstants.RADIUS) {
+          x -= width / 2;
+          y -= height / 2;
+        }
+
+        curContext.rect(
+        Math.round(x) - offsetStart, Math.round(y) - offsetStart, Math.round(width) + offsetEnd, Math.round(height) + offsetEnd);
+
+        executeContextFill();
+        executeContextStroke();
+
+        curContext.closePath();
+      }
+    };
+
+    p.ellipse = function ellipse(x, y, width, height) {
+      x = x || 0;
+      y = y || 0;
+
+      if (width <= 0 && height <= 0) {
+        return;
+      }
+
+      if (curEllipseMode === PConstants.RADIUS) {
+        width *= 2;
+        height *= 2;
+      }
+
+      if (curEllipseMode === PConstants.CORNERS) {
+        width = width - x;
+        height = height - y;
+      }
+
+      if (curEllipseMode === PConstants.CORNER || curEllipseMode === PConstants.CORNERS) {
+        x += width / 2;
+        y += height / 2;
+      }
+
+      var offsetStart = 0;
+
+      // Shortcut for drawing a 2D circle
+      if ((!p.use3DContext) && (width === height)) {
+        curContext.beginPath();
+        curContext.arc(x - offsetStart, y - offsetStart, width / 2, 0, PConstants.TWO_PI, false);
+        executeContextFill();
+        executeContextStroke();
+        curContext.closePath();
+      }
+      else {
+        var w = width / 2,
+          h = height / 2,
+          C = 0.5522847498307933;
+        var c_x = C * w,
+          c_y = C * h;
+
+        if(!p.use3DContext){
+          // TODO: Audit
+          p.beginShape();
+          p.vertex(x + w, y);
+          p.bezierVertex(x + w, y - c_y, x + c_x, y - h, x, y - h);
+          p.bezierVertex(x - c_x, y - h, x - w, y - c_y, x - w, y);
+          p.bezierVertex(x - w, y + c_y, x - c_x, y + h, x, y + h);
+          p.bezierVertex(x + c_x, y + h, x + w, y + c_y, x + w, y);
+          p.endShape();
+        }
+        else{
+          p.beginShape();
+          p.vertex(x + w, y);
+          p.bezierVertex(x + w, y - c_y, 0, x + c_x, y - h, 0, x, y - h, 0);
+          p.bezierVertex(x - c_x, y - h, 0, x - w, y - c_y, 0, x - w, y, 0);
+          p.bezierVertex(x - w, y + c_y, 0, x - c_x, y + h, 0, x, y + h, 0);
+          p.bezierVertex(x + c_x, y + h, 0, x + w, y + c_y, 0, x + w, y, 0);
+          p.endShape();
+
+          //temporary workaround to not working fills for bezier -- will fix later
+          var xAv = 0, yAv = 0, i, j;
+          for(i = 0; i < vertArray.length; i++){
+            xAv += vertArray[i][0];
+            yAv += vertArray[i][1];
+          }
+          xAv /= vertArray.length;
+          yAv /= vertArray.length;
+          var vert = [],
+              fillVertArray = [],
+              colorVertArray = [];
+          vert[0] = xAv;
+          vert[1] = yAv;
+          vert[2] = 0;
+          vert[3] = 0;
+          vert[4] = 0;
+          vert[5] = fillStyle[0];
+          vert[6] = fillStyle[1];
+          vert[7] = fillStyle[2];
+          vert[8] = fillStyle[3];
+          vert[9] = strokeStyle[0];
+          vert[10] = strokeStyle[1];
+          vert[11] = strokeStyle[2];
+          vert[12] = strokeStyle[3];
+          vert[13] = normalX;
+          vert[14] = normalY;
+          vert[15] = normalZ;
+          vertArray.unshift(vert);
+          for(i = 0; i < vertArray.length; i++){
+            for(j = 0; j < 3; j++){
+              fillVertArray.push(vertArray[i][j]);
+            }
+            for(j = 5; j < 9; j++){
+              colorVertArray.push(vertArray[i][j]);
+            }
+          }
+          fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray);
+        }
+      }
+    };
+
+    p.normal = function normal(nx, ny, nz) {
+      if (arguments.length !== 3 || !(typeof nx === "number" && typeof ny === "number" && typeof nz === "number")) {
+        throw "normal() requires three numeric arguments.";
+      }
+
+      normalX = nx;
+      normalY = ny;
+      normalZ = nz;
+
+      if (curShape !== 0) {
+        if (normalMode === PConstants.NORMAL_MODE_AUTO) {
+          normalMode = PConstants.NORMAL_MODE_SHAPE;
+        } else if (normalMode === PConstants.NORMAL_MODE_SHAPE) {
+          normalMode = PConstants.NORMAL_MODE_VERTEX;
+        }
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Raster drawing functions
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.save = function save(file, img) {
+      // file is unused at the moment
+      // may implement this differently in later release
+      if (img !== undef) {
+        return window.open(img.toDataURL(),"_blank");
+      } else {
+        return window.open(p.externals.canvas.toDataURL(),"_blank");
+      }
+    };
+
+    var utilityContext2d = document.createElement("canvas").getContext("2d");
+
+    var canvasDataCache = [undef, undef, undef]; // we need three for now
+
+    function getCanvasData(obj, w, h) {
+      var canvasData = canvasDataCache.shift();
+
+      if (canvasData === undef) {
+        canvasData = {};
+        canvasData.canvas = document.createElement("canvas");
+        canvasData.context = canvasData.canvas.getContext('2d');
+      }
+
+      canvasDataCache.push(canvasData);
+
+      var canvas = canvasData.canvas, context = canvasData.context,
+          width = w || obj.width, height = h || obj.height;
+
+      canvas.width = width;
+      canvas.height = height;
+
+      if (!obj) {
+        context.clearRect(0, 0, width, height);
+      } else if ("data" in obj) { // ImageData
+        context.putImageData(obj, 0, 0);
+      } else {
+        context.clearRect(0, 0, width, height);
+        context.drawImage(obj, 0, 0, width, height);
+      }
+      return canvasData;
+    }
+
+    var PImage = function PImage(aWidth, aHeight, aFormat) {
+      this.get = function(x, y, w, h) {
+        if (!arguments.length) {
+          return p.get(this);
+        } else if (arguments.length === 2) {
+          return p.get(x, y, this);
+        } else if (arguments.length === 4) {
+          return p.get(x, y, w, h, this);
+        }
+      };
+
+      this.set = function(x, y, c) {
+        p.set(x, y, c, this);
+      };
+
+      this.blend = function(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE) {
+        if (arguments.length === 9) {
+          p.blend(this, srcImg, x, y, width, height, dx, dy, dwidth, dheight, this);
+        } else if (arguments.length === 10) {
+          p.blend(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE, this);
+        }
+      };
+
+      this.copy = function(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight) {
+        if (arguments.length === 8) {
+          p.blend(this, srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, PConstants.REPLACE, this);
+        } else if (arguments.length === 9) {
+          p.blend(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight, PConstants.REPLACE, this);
+        }
+      };
+
+      this.filter = function(mode, param) {
+        if (arguments.length === 2) {
+          p.filter(mode, param, this);
+        } else if (arguments.length === 1) {
+          // no param specified, send null to show its invalid
+          p.filter(mode, null, this);
+        }
+      };
+
+      this.save = function(file){
+        p.save(file,this);
+      };
+
+      this.resize = function(w, h) {
+        if (this.isRemote) { // Remote images cannot access imageData
+          throw "Image is loaded remotely. Cannot resize.";
+        } else {
+          if (this.width !== 0 || this.height !== 0) {
+            // make aspect ratio if w or h is 0
+            if (w === 0 && h !== 0) {
+              w = this.width / this.height * h;
+            } else if (h === 0 && w !== 0) {
+              h = w / (this.width / this.height);
+            }
+            // put 'this.imageData' into a new canvas
+            var canvas = getCanvasData(this.imageData).canvas;
+            // pull imageData object out of canvas into ImageData object
+            var imageData = getCanvasData(canvas, w, h).context.getImageData(0, 0, w, h);
+            // set this as new pimage
+            this.fromImageData(imageData);
+          }
+        }
+      };
+
+      this.mask = function(mask) {
+        this.__mask = undef;
+
+        if (mask instanceof PImage) {
+          if (mask.width === this.width && mask.height === this.height) {
+            this.__mask = mask;
+          } else {
+            throw "mask must have the same dimensions as PImage.";
+          }
+        } else if (typeof mask === "object" && mask.constructor === Array) { // this is a pixel array
+          // mask pixel array needs to be the same length as this.pixels
+          // how do we update this for 0.9 this.imageData holding pixels ^^
+          // mask.constructor ? and this.pixels.length = this.imageData.data.length instead ?
+          if (this.pixels.length === mask.length) {
+            this.__mask = mask;
+          } else {
+            throw "mask array must be the same length as PImage pixels array.";
+          }
+        }
+      };
+
+      // handle the sketch code for pixels[] and pixels.length
+      // parser code converts pixels[] to getPixels()
+      // or setPixels(), .length becomes getLength()
+      this.pixels = {
+        getLength: (function(aImg) {
+          if (aImg.isRemote) { // Remote images cannot access imageData
+            throw "Image is loaded remotely. Cannot get length.";
+          } else {
+            return function() {
+              return aImg.imageData.data.length ? aImg.imageData.data.length/4 : 0;
+            };
+          }
+        }(this)),
+        getPixel: (function(aImg) {
+          if (aImg.isRemote) { // Remote images cannot access imageData
+            throw "Image is loaded remotely. Cannot get pixels.";
+          } else {
+            return function(i) {
+              var offset = i*4;
+              return p.color.toInt(aImg.imageData.data[offset], aImg.imageData.data[offset+1],
+                                   aImg.imageData.data[offset+2], aImg.imageData.data[offset+3]);
+            };
+          }
+        }(this)),
+        setPixel: (function(aImg) {
+          if (aImg.isRemote) { // Remote images cannot access imageData
+            throw "Image is loaded remotely. Cannot set pixel.";
+          } else {
+            return function(i,c) {
+              var offset = i*4;
+              aImg.imageData.data[offset+0] = (c & PConstants.RED_MASK) >>> 16;
+              aImg.imageData.data[offset+1] = (c & PConstants.GREEN_MASK) >>> 8;
+              aImg.imageData.data[offset+2] = (c & PConstants.BLUE_MASK);
+              aImg.imageData.data[offset+3] = (c & PConstants.ALPHA_MASK) >>> 24;
+            };
+          }
+        }(this)),
+        set: function(arr) {
+          if (this.isRemote) { // Remote images cannot access imageData
+            throw "Image is loaded remotely. Cannot set pixels.";
+          } else {
+            for (var i = 0, aL = arr.length; i < aL; i++) {
+              this.setPixel(i, arr[i]);
+            }
+          }
+        }
+      };
+
+      // These are intentionally left blank for PImages, we work live with pixels and draw as necessary
+      this.loadPixels = function() {};
+
+      this.updatePixels = function() {};
+
+      this.toImageData = function() {
+        if (this.isRemote) { // Remote images cannot access imageData, send source image instead
+          return this.sourceImg;
+        } else {
+          var canvasData = getCanvasData(this.imageData);
+          return canvasData.context.getImageData(0, 0, this.width, this.height);
+        }
+      };
+
+      this.toDataURL = function() {
+        if (this.isRemote) { // Remote images cannot access imageData
+          throw "Image is loaded remotely. Cannot create dataURI.";
+        } else {
+          var canvasData = getCanvasData(this.imageData);
+          return canvasData.canvas.toDataURL();
+        }
+      };
+
+      this.fromImageData = function(canvasImg) {
+        this.width = canvasImg.width;
+        this.height = canvasImg.height;
+        this.imageData = canvasImg;
+        // changed for 0.9
+        this.format = PConstants.ARGB;
+      };
+
+      this.fromHTMLImageData = function(htmlImg) {
+        // convert an <img> to a PImage
+        var canvasData = getCanvasData(htmlImg);
+        try {
+          var imageData = canvasData.context.getImageData(0, 0, htmlImg.width, htmlImg.height);
+          this.fromImageData(imageData);
+        } catch(e) {
+          if (htmlImg.width && htmlImg.height) {
+            this.isRemote = true;
+            this.width = htmlImg.width;
+            this.height = htmlImg.height;
+          }
+        }
+        this.sourceImg = htmlImg;
+      };
+
+      if (arguments.length === 1) {
+        // convert an <img> to a PImage
+        this.fromHTMLImageData(arguments[0]);
+      } else if (arguments.length === 2 || arguments.length === 3) {
+        this.width = aWidth || 1;
+        this.height = aHeight || 1;
+        this.imageData = utilityContext2d.createImageData(this.width, this.height);
+        this.format = (aFormat === PConstants.ARGB || aFormat === PConstants.ALPHA) ? aFormat : PConstants.RGB;
+      } else {
+        this.width = 0;
+        this.height = 0;
+        this.imageData = utilityContext2d.createImageData(1, 1);
+        this.format = PConstants.ARGB;
+      }
+    };
+
+    p.PImage = PImage;
+
+    p.createImage = function createImage(w, h, mode) {
+      return new PImage(w,h,mode);
+    };
+
+    // Loads an image for display. Type is an extension. Callback is fired on load.
+    p.loadImage = function loadImage(file, type, callback) {
+      // if type is specified add it with a . to file to make the filename
+      if (type) {
+        file = file + "." + type;
+      }
+      // if image is in the preloader cache return a new PImage
+      if (curSketch.imageCache.images[file]) {
+        return new PImage(curSketch.imageCache.images[file]);
+      }
+      // else aysnc load it
+      else {
+        var pimg = new PImage(0, 0, PConstants.ARGB);
+        var img = document.createElement('img');
+
+        pimg.sourceImg = img;
+
+        img.onload = (function(aImage, aPImage, aCallback) {
+          var image = aImage;
+          var pimg = aPImage;
+          var callback = aCallback;
+          return function() {
+            // change the <img> object into a PImage now that its loaded
+            pimg.fromHTMLImageData(image);
+            pimg.loaded = true;
+            if (callback) {
+              callback();
+            }
+          };
+        }(img, pimg, callback));
+
+        img.src = file; // needs to be called after the img.onload function is declared or it wont work in opera
+        return pimg;
+      }
+    };
+
+    // async loading of large images, same functionality as loadImage above
+    p.requestImage = p.loadImage;
+
+    function get$0() {
+      //return a PImage of curContext
+      var c = new PImage(p.width, p.height, PConstants.RGB);
+      c.fromImageData(curContext.getImageData(0, 0, p.width, p.height));
+      return c;
+    }
+    function get$2(x,y) {
+      var data;
+      // return the color at x,y (int) of curContext
+      // create a PImage object of size 1x1 and return the int of the pixels array element 0
+      if (x < p.width && x >= 0 && y >= 0 && y < p.height) {
+        if(isContextReplaced) {
+          var offset = ((0|x) + p.width * (0|y))*4;
+          data = p.imageData.data;
+          return p.color.toInt(data[offset], data[offset+1],
+                           data[offset+2], data[offset+3]);
+        }
+        // x,y is inside canvas space
+        data = curContext.getImageData(0|x, 0|y, 1, 1).data;
+        // changed for 0.9
+        return p.color.toInt(data[0], data[1], data[2], data[3]);
+      } else {
+        // x,y is outside image return transparent black
+        return 0;
+      }
+    }
+    function get$3(x,y,img) {
+      if (img.isRemote) { // Remote images cannot access imageData
+        throw "Image is loaded remotely. Cannot get x,y.";
+      } else {
+        // PImage.get(x,y) was called, return the color (int) at x,y of img
+        // changed in 0.9
+        var offset = y * img.width * 4 + (x * 4);
+        return p.color.toInt(img.imageData.data[offset],
+                           img.imageData.data[offset + 1],
+                           img.imageData.data[offset + 2],
+                           img.imageData.data[offset + 3]);
+      }
+    }
+    function get$4(x, y, w, h) {
+      // return a PImage of w and h from cood x,y of curContext
+      var c = new PImage(w, h, PConstants.RGB);
+      c.fromImageData(curContext.getImageData(x, y, w, h));
+      return c;
+    }
+    function get$5(x, y, w, h, img) {
+      if (img.isRemote) { // Remote images cannot access imageData
+        throw "Image is loaded remotely. Cannot get x,y,w,h.";
+      } else {
+        // PImage.get(x,y,w,h) was called, return x,y,w,h PImage of img
+        // changed for 0.9, offset start point needs to be *4
+        var start = y * img.width * 4 + (x*4);
+        var end = (y + h) * img.width * 4 + ((x + w) * 4);
+        var c = new PImage(w, h, PConstants.RGB);
+        for (var i = start, j = 0; i < end; i++, j++) {
+          // changed in 0.9
+          c.imageData.data[j] = img.imageData.data[i];
+          if ((j+1) % (w*4) === 0) {
+            //completed one line, increment i by offset
+            i += (img.width - w) * 4;
+          }
+        }
+        return c;
+      }
+    }
+
+    // Gets a single pixel or block of pixels from the current Canvas Context or a PImage
+    p.get = function get(x, y, w, h, img) {
+      // for 0 2 and 4 arguments use curContext, otherwise PImage.get was called
+      if (arguments.length === 2) {
+        return get$2(x, y);
+      } else if (arguments.length === 0) {
+        return get$0();
+      } else if (arguments.length === 5) {
+        return get$5(x, y, w, h, img);
+      } else if (arguments.length === 4) {
+        return get$4(x, y, w, h);
+      } else if (arguments.length === 3) {
+        return get$3(x, y, w);
+      } else if (arguments.length === 1) {
+        // PImage.get() was called, return the PImage
+        return x;
+      }
+    };
+
+    // Creates a new Processing instance and passes it back for... processing
+    p.createGraphics = function createGraphics(w, h, render) {
+      var canvas = document.createElement("canvas");
+      var pg = new Processing(canvas);
+      pg.size(w, h, render);
+      pg.canvas = canvas;
+      //Processing.addInstance(pg); // TODO: this function does not exist in this scope
+      return pg;
+    };
+
+    // pixels caching
+    function resetContext() {
+      if(isContextReplaced) {
+        curContext = originalContext;
+        isContextReplaced = false;
+
+        p.updatePixels();
+      }
+    }
+    function SetPixelContextWrapper() {
+      function wrapFunction(newContext, name) {
+        function wrapper() {
+          resetContext();
+          curContext[name].apply(curContext, arguments);
+        }
+        newContext[name] = wrapper;
+      }
+      function wrapProperty(newContext, name) {
+        function getter() {
+          resetContext();
+          return curContext[name];
+        }
+        function setter(value) {
+          resetContext();
+          curContext[name] = value;
+        }
+        p.defineProperty(newContext, name, { get: getter, set: setter });
+      }
+      for(var n in curContext) {
+        if(typeof curContext[n] === 'function') {
+          wrapFunction(this, n);
+        } else {
+          wrapProperty(this, n);
+        }
+      }
+    }
+    function replaceContext() {
+      if(isContextReplaced) {
+        return;
+      }
+      p.loadPixels();
+      if(proxyContext === null) {
+        originalContext = curContext;
+        proxyContext = new SetPixelContextWrapper();
+      }
+      isContextReplaced = true;
+      curContext = proxyContext;
+      setPixelsCached = 0;
+    }
+
+    function set$3(x, y, c) {
+      if (x < p.width && x >= 0 && y >= 0 && y < p.height) {
+        replaceContext();
+        p.pixels.setPixel((0|x)+p.width*(0|y), c);
+        if(++setPixelsCached > maxPixelsCached) {
+          resetContext();
+        }
+      }
+    }
+    function set$4(x, y, obj, img) {
+      if (img.isRemote) { // Remote images cannot access imageData
+        throw "Image is loaded remotely. Cannot set x,y.";
+      } else {
+        var c = p.color.toArray(obj);
+        var offset = y * img.width * 4 + (x*4);
+        var data = img.imageData.data;
+        data[offset] = c[0];
+        data[offset+1] = c[1];
+        data[offset+2] = c[2];
+        data[offset+3] = c[3];
+      }
+    }
+    // Paints a pixel array into the canvas
+    p.set = function set(x, y, obj, img) {
+      var color, oldFill;
+      if (arguments.length === 3) {
+        // called p.set(), was it with a color or a img ?
+        if (typeof obj === "number") {
+          set$3(x, y, obj);
+        } else if (obj instanceof PImage) {
+          p.image(obj, x, y);
+        }
+      } else if (arguments.length === 4) {
+        // PImage.set(x,y,c) was called, set coordinate x,y color to c of img
+        set$4(x, y, obj, img);
+      }
+    };
+    p.imageData = {};
+
+    // handle the sketch code for pixels[]
+    // parser code converts pixels[] to getPixels()
+    // or setPixels(), .length becomes getLength()
+    p.pixels = {
+      getLength: function() { return p.imageData.data.length ? p.imageData.data.length/4 : 0; },
+      getPixel: function(i) {
+        var offset = i*4;
+        return (p.imageData.data[offset+3] << 24) & 0xff000000 |
+               (p.imageData.data[offset+0] << 16) & 0x00ff0000 |
+               (p.imageData.data[offset+1] << 8) & 0x0000ff00 |
+               p.imageData.data[offset+2] & 0x000000ff;
+      },
+      setPixel: function(i,c) {
+        var offset = i*4;
+        p.imageData.data[offset+0] = (c & 0x00ff0000) >>> 16; // RED_MASK
+        p.imageData.data[offset+1] = (c & 0x0000ff00) >>> 8;  // GREEN_MASK
+        p.imageData.data[offset+2] = (c & 0x000000ff);        // BLUE_MASK
+        p.imageData.data[offset+3] = (c & 0xff000000) >>> 24; // ALPHA_MASK
+      },
+      set: function(arr) {
+        for (var i = 0, aL = arr.length; i < aL; i++) {
+          this.setPixel(i, arr[i]);
+        }
+      }
+    };
+
+    // Gets a 1-Dimensional pixel array from Canvas
+    p.loadPixels = function() {
+      // changed in 0.9
+      p.imageData = curContext.getImageData(0, 0, p.width, p.height);
+    };
+
+    // Draws a 1-Dimensional pixel array to Canvas
+    p.updatePixels = function() {
+      // changed in 0.9
+      if (p.imageData) {
+        curContext.putImageData(p.imageData, 0, 0);
+      }
+    };
+
+    p.hint = function hint(which) {
+      if (which === PConstants.DISABLE_DEPTH_TEST) {
+         curContext.disable(curContext.DEPTH_TEST);
+         curContext.depthMask(false);
+         curContext.clear(curContext.DEPTH_BUFFER_BIT);
+      }
+      else if (which === PConstants.ENABLE_DEPTH_TEST) {
+         curContext.enable(curContext.DEPTH_TEST);
+         curContext.depthMask(true);
+      }
+    };
+
+    // Draw an image or a color to the background
+    p.background = function background() {
+      var color, a, img;
+      // background params are either a color or a PImage
+      if (typeof arguments[0] === 'number') {
+        color = p.color.apply(this, arguments);
+
+        // override alpha value, processing ignores the alpha for background color
+        if (!curSketch.options.isTransparent) {
+          color = color | PConstants.ALPHA_MASK;
+        }
+      } else if (arguments.length === 1 && arguments[0] instanceof PImage) {
+        img = arguments[0];
+
+        if (!img.pixels || img.width !== p.width || img.height !== p.height) {
+          throw "Background image must be the same dimensions as the canvas.";
+        }
+      } else {
+        throw "Incorrect background parameters.";
+      }
+
+      if (p.use3DContext) {
+        if (color !== undef) {
+          var c = p.color.toGLArray(color);
+          refreshBackground = function() {
+            curContext.clearColor(c[0], c[1], c[2], c[3]);
+            curContext.clear(curContext.COLOR_BUFFER_BIT | curContext.DEPTH_BUFFER_BIT);
+          };
+        } else {
+          // Handle image background for 3d context. not done yet.
+          refreshBackground = function() {};
+        }
+      } else { // 2d context
+        if (color !== undef) {
+          refreshBackground = function() {
+            if (curSketch.options.isTransparent) {
+              curContext.clearRect(0,0, p.width, p.height);
+            }
+            curContext.fillStyle = p.color.toString(color);
+            curContext.fillRect(0, 0, p.width, p.height);
+            isFillDirty = true;
+          };
+        } else {
+          refreshBackground = function() {
+            p.image(img, 0, 0);
+          };
+        }
+      }
+      refreshBackground();
+    };
+
+    // Draws an image to the Canvas
+    p.image = function image(img, x, y, w, h) {
+      if (img.width > 0) {
+        var wid = w || img.width;
+        var hgt = h || img.height;
+        if (p.use3DContext) {
+          p.beginShape(p.QUADS);
+          p.texture(img.externals.canvas);
+          p.vertex(x, y, 0, 0, 0);
+          p.vertex(x, y+hgt, 0, 0, hgt);
+          p.vertex(x+wid, y+hgt, 0, wid, hgt);
+          p.vertex(x+wid, y, 0, wid, 0);
+          p.endShape();
+        } else {
+          var bounds = imageModeConvert(x || 0, y || 0, w || img.width, h || img.height, arguments.length < 4);
+          var obj = img.toImageData();
+
+          if (img.__mask) {
+            var j, size;
+            if (img.__mask instanceof PImage) {
+              var objMask = img.__mask.toImageData();
+              for (j = 2, size = img.width * img.height * 4; j < size; j += 4) {
+                // using it as an alpha channel
+                obj.data[j + 1] = objMask.data[j];
+                // but only the blue color channel
+              }
+            } else {
+              for (j = 0, size = img.__mask.length; j < size; ++j) {
+                obj.data[(j << 2) + 3] = img.__mask[j];
+              }
+            }
+          }
+
+          // draw the image
+          curTint(obj);
+
+          curContext.drawImage(getCanvasData(obj).canvas, 0, 0, img.width, img.height, bounds.x, bounds.y, bounds.w, bounds.h);
+        }
+      }
+    };
+
+    // Clears a rectangle in the Canvas element or the whole Canvas
+    p.clear = function clear(x, y, width, height) {
+      if (arguments.length === 0) {
+        curContext.clearRect(0, 0, p.width, p.height);
+      } else {
+        curContext.clearRect(x, y, width, height);
+      }
+    };
+
+    p.tint = function tint() {
+      var tintColor = p.color.apply(this, arguments);
+      var r = p.red(tintColor) / colorModeX;
+      var g = p.green(tintColor) / colorModeY;
+      var b = p.blue(tintColor) / colorModeZ;
+      var a = p.alpha(tintColor) / colorModeA;
+
+      curTint = function(obj) {
+        var data = obj.data,
+            length = 4 * obj.width * obj.height;
+        for (var i = 0; i < length;) {
+          data[i++] *= r;
+          data[i++] *= g;
+          data[i++] *= b;
+          data[i++] *= a;
+        }
+      };
+    };
+
+    p.noTint = function noTint() {
+      curTint = function() {};
+    };
+
+    p.copy = function copy(src, sx, sy, sw, sh, dx, dy, dw, dh) {
+      if (arguments.length === 8) {
+        // shift everything, and introduce p
+        dh = dw;
+        dw = dy;
+        dy = dx;
+        dx = sh;
+        sh = sw;
+        sw = sy;
+        sy = sx;
+        sx = src;
+        src = p;
+      }
+      p.blend(src, sx, sy, sw, sh, dx, dy, dw, dh, PConstants.REPLACE);
+    };
+
+    p.blend = function blend(src, sx, sy, sw, sh, dx, dy, dw, dh, mode, pimgdest) {
+      if (arguments.length === 9) {
+        // shift everything, and introduce p
+        mode = dh;
+        dh = dw;
+        dw = dy;
+        dy = dx;
+        dx = sh;
+        sh = sw;
+        sw = sy;
+        sy = sx;
+        sx = src;
+        src = p;
+      }
+
+      var sx2 = sx + sw;
+      var sy2 = sy + sh;
+      var dx2 = dx + dw;
+      var dy2 = dy + dh;
+      var dest;
+      if (src.isRemote) { // Remote images cannot access imageData
+        throw "Image is loaded remotely. Cannot blend image.";
+      } else {
+        // check if pimgdest is there and pixels, if so this was a call from pimg.blend
+        if (arguments.length === 10 || arguments.length === 9) {
+          p.loadPixels();
+          dest = p;
+        } else if (arguments.length === 11 && pimgdest && pimgdest.imageData) {
+          dest = pimgdest;
+        }
+        if (src === p) {
+          if (p.intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) {
+            p.blit_resize(p.get(sx, sy, sx2 - sx, sy2 - sy), 0, 0, sx2 - sx - 1, sy2 - sy - 1,
+                          dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode);
+          } else {
+            // same as below, except skip the loadPixels() because it'd be redundant
+            p.blit_resize(src, sx, sy, sx2, sy2, dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode);
+          }
+        } else {
+          src.loadPixels();
+          p.blit_resize(src, sx, sy, sx2, sy2, dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode);
+        }
+        if (arguments.length === 10) {
+          p.updatePixels();
+        }
+      }
+    };
+
+    // helper function for filter()
+    var buildBlurKernel = function buildBlurKernel(r) {
+      var radius = p.floor(r * 3.5), i, radiusi;
+      radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248);
+      if (p.shared.blurRadius !== radius) {
+        p.shared.blurRadius = radius;
+        p.shared.blurKernelSize = 1 + (p.shared.blurRadius<<1);
+        p.shared.blurKernel = new Array(p.shared.blurKernelSize);
+        // init blurKernel
+        for (i = 0; i < p.shared.blurKernelSize; i++) {
+          p.shared.blurKernel[i] = 0;
+        }
+
+        for (i = 1, radiusi = radius - 1; i < radius; i++) {
+          p.shared.blurKernel[radius+i] = p.shared.blurKernel[radiusi] = radiusi * radiusi;
+        }
+        p.shared.blurKernel[radius] = radius * radius;
+      }
+    };
+
+    var blurARGB = function blurARGB(r, aImg) {
+      var sum, cr, cg, cb, ca, c, m;
+      var read, ri, ym, ymi, bk0;
+      var wh = aImg.pixels.getLength();
+      var r2 = new Array(wh);
+      var g2 = new Array(wh);
+      var b2 = new Array(wh);
+      var a2 = new Array(wh);
+      var yi = 0;
+      var x, y, i;
+
+      buildBlurKernel(r);
+
+      for (y = 0; y < aImg.height; y++) {
+        for (x = 0; x < aImg.width; x++) {
+          cb = cg = cr = ca = sum = 0;
+          read = x - p.shared.blurRadius;
+          if (read<0) {
+            bk0 = -read;
+            read = 0;
+          } else {
+            if (read >= aImg.width) {
+              break;
+            }
+            bk0=0;
+          }
+          for (i = bk0; i < p.shared.blurKernelSize; i++) {
+            if (read >= aImg.width) {
+              break;
+            }
+            c = aImg.pixels.getPixel(read + yi);
+            m = p.shared.blurKernel[i];
+            ca += m * ((c & PConstants.ALPHA_MASK) >>> 24);
+            cr += m * ((c & PConstants.RED_MASK) >> 16);
+            cg += m * ((c & PConstants.GREEN_MASK) >> 8);
+            cb += m * (c & PConstants.BLUE_MASK);
+            sum += m;
+            read++;
+          }
+          ri = yi + x;
+          a2[ri] = ca / sum;
+          r2[ri] = cr / sum;
+          g2[ri] = cg / sum;
+          b2[ri] = cb / sum;
+        }
+        yi += aImg.width;
+      }
+
+      yi = 0;
+      ym = -p.shared.blurRadius;
+      ymi = ym*aImg.width;
+
+      for (y = 0; y < aImg.height; y++) {
+        for (x = 0; x < aImg.width; x++) {
+          cb = cg = cr = ca = sum = 0;
+          if (ym<0) {
+            bk0 = ri = -ym;
+            read = x;
+          } else {
+            if (ym >= aImg.height) {
+              break;
+            }
+            bk0 = 0;
+            ri = ym;
+            read = x + ymi;
+          }
+          for (i = bk0; i < p.shared.blurKernelSize; i++) {
+            if (ri >= aImg.height) {
+              break;
+            }
+            m = p.shared.blurKernel[i];
+            ca += m * a2[read];
+            cr += m * r2[read];
+            cg += m * g2[read];
+            cb += m * b2[read];
+            sum += m;
+            ri++;
+            read += aImg.width;
+          }
+          aImg.pixels.setPixel(x+yi, ((ca/sum)<<24 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum)));
+        }
+        yi += aImg.width;
+        ymi += aImg.width;
+        ym++;
+      }
+    };
+
+    // helper funtion for ERODE and DILATE modes of filter()
+    var dilate = function dilate(isInverted, aImg) {
+      var currIdx = 0;
+      var maxIdx = aImg.pixels.getLength();
+      var out = new Array(maxIdx);
+      var currRowIdx, maxRowIdx, colOrig, colOut, currLum;
+      var idxRight, idxLeft, idxUp, idxDown,
+          colRight, colLeft, colUp, colDown,
+          lumRight, lumLeft, lumUp, lumDown;
+
+      if (!isInverted) {
+        // erosion (grow light areas)
+        while (currIdx<maxIdx) {
+          currRowIdx = currIdx;
+          maxRowIdx = currIdx + aImg.width;
+          while (currIdx < maxRowIdx) {
+            colOrig = colOut = aImg.pixels.getPixel(currIdx);
+            idxLeft = currIdx - 1;
+            idxRight = currIdx + 1;
+            idxUp = currIdx - aImg.width;
+            idxDown = currIdx + aImg.width;
+            if (idxLeft < currRowIdx) {
+              idxLeft = currIdx;
+            }
+            if (idxRight >= maxRowIdx) {
+              idxRight = currIdx;
+            }
+            if (idxUp < 0) {
+              idxUp = 0;
+            }
+            if (idxDown >= maxIdx) {
+              idxDown = currIdx;
+            }
+            colUp = aImg.pixels.getPixel(idxUp);
+            colLeft = aImg.pixels.getPixel(idxLeft);
+            colDown = aImg.pixels.getPixel(idxDown);
+            colRight = aImg.pixels.getPixel(idxRight);
+
+            // compute luminance
+            currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff);
+            lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
+            lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
+            lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
+            lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
+
+            if (lumLeft > currLum) {
+              colOut = colLeft;
+              currLum = lumLeft;
+            }
+            if (lumRight > currLum) {
+              colOut = colRight;
+              currLum = lumRight;
+            }
+            if (lumUp > currLum) {
+              colOut = colUp;
+              currLum = lumUp;
+            }
+            if (lumDown > currLum) {
+              colOut = colDown;
+              currLum = lumDown;
+            }
+            out[currIdx++] = colOut;
+          }
+        }
+      } else {
+        // dilate (grow dark areas)
+        while (currIdx < maxIdx) {
+          currRowIdx = currIdx;
+          maxRowIdx = currIdx + aImg.width;
+          while (currIdx < maxRowIdx) {
+            colOrig = colOut = aImg.pixels.getPixel(currIdx);
+            idxLeft = currIdx - 1;
+            idxRight = currIdx + 1;
+            idxUp = currIdx - aImg.width;
+            idxDown = currIdx + aImg.width;
+            if (idxLeft < currRowIdx) {
+              idxLeft = currIdx;
+            }
+            if (idxRight >= maxRowIdx) {
+              idxRight = currIdx;
+            }
+            if (idxUp < 0) {
+              idxUp = 0;
+            }
+            if (idxDown >= maxIdx) {
+              idxDown = currIdx;
+            }
+            colUp = aImg.pixels.getPixel(idxUp);
+            colLeft = aImg.pixels.getPixel(idxLeft);
+            colDown = aImg.pixels.getPixel(idxDown);
+            colRight = aImg.pixels.getPixel(idxRight);
+
+            // compute luminance
+            currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff);
+            lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
+            lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
+            lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
+            lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
+
+            if (lumLeft < currLum) {
+              colOut = colLeft;
+              currLum = lumLeft;
+            }
+            if (lumRight < currLum) {
+              colOut = colRight;
+              currLum = lumRight;
+            }
+            if (lumUp < currLum) {
+              colOut = colUp;
+              currLum = lumUp;
+            }
+            if (lumDown < currLum) {
+              colOut = colDown;
+              currLum = lumDown;
+            }
+            out[currIdx++]=colOut;
+          }
+        }
+      }
+      aImg.pixels.set(out);
+      //p.arraycopy(out,0,pixels,0,maxIdx);
+    };
+
+    p.filter = function filter(kind, param, aImg){
+      var img, col, lum, i;
+
+      if (arguments.length === 3) {
+        aImg.loadPixels();
+        img = aImg;
+      } else {
+        p.loadPixels();
+        img = p;
+      }
+
+      if (param === undef) {
+        param = null;
+      }
+      if (img.isRemote) { // Remote images cannot access imageData
+        throw "Image is loaded remotely. Cannot filter image.";
+      } else {
+        // begin filter process
+        var imglen = img.pixels.getLength();
+        switch (kind) {
+          case PConstants.BLUR:
+            var radius = param || 1; // if no param specified, use 1 (default for p5)
+            blurARGB(radius, img);
+            break;
+
+          case PConstants.GRAY:
+            if (img.format === PConstants.ALPHA) { //trouble
+              // for an alpha image, convert it to an opaque grayscale
+              for (i = 0; i < imglen; i++) {
+                col = 255 - img.pixels.getPixel(i);
+                img.pixels.setPixel(i,(0xff000000 | (col << 16) | (col << 8) | col));
+              }
+              img.format = PConstants.RGB; //trouble
+            } else {
+              for (i = 0; i < imglen; i++) {
+                col = img.pixels.getPixel(i);
+                lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8;
+                img.pixels.setPixel(i,((col & PConstants.ALPHA_MASK) | lum<<16 | lum<<8 | lum));
+              }
+            }
+            break;
+
+          case PConstants.INVERT:
+            for (i = 0; i < imglen; i++) {
+              img.pixels.setPixel(i, (img.pixels.getPixel(i) ^ 0xffffff));
+            }
+            break;
+
+          case PConstants.POSTERIZE:
+            if (param === null) {
+              throw "Use filter(POSTERIZE, int levels) instead of filter(POSTERIZE)";
+            }
+            var levels = p.floor(param);
+            if ((levels < 2) || (levels > 255)) {
+              throw "Levels must be between 2 and 255 for filter(POSTERIZE, levels)";
+            }
+            var levels1 = levels - 1;
+            for (i = 0; i < imglen; i++) {
+              var rlevel = (img.pixels.getPixel(i) >> 16) & 0xff;
+              var glevel = (img.pixels.getPixel(i) >> 8) & 0xff;
+              var blevel = img.pixels.getPixel(i) & 0xff;
+              rlevel = (((rlevel * levels) >> 8) * 255) / levels1;
+              glevel = (((glevel * levels) >> 8) * 255) / levels1;
+              blevel = (((blevel * levels) >> 8) * 255) / levels1;
+              img.pixels.setPixel(i, ((0xff000000 & img.pixels.getPixel(i)) | (rlevel << 16) | (glevel << 8) | blevel));
+            }
+            break;
+
+          case PConstants.OPAQUE:
+            for (i = 0; i < imglen; i++) {
+              img.pixels.setPixel(i, (img.pixels.getPixel(i) | 0xff000000));
+            }
+            img.format = PConstants.RGB; //trouble
+            break;
+
+          case PConstants.THRESHOLD:
+            if (param === null) {
+              param = 0.5;
+            }
+            if ((param < 0) || (param > 1)) {
+              throw "Level must be between 0 and 1 for filter(THRESHOLD, level)";
+            }
+            var thresh = p.floor(param * 255);
+            for (i = 0; i < imglen; i++) {
+              var max = p.max((img.pixels.getPixel(i) & PConstants.RED_MASK) >> 16, p.max((img.pixels.getPixel(i) & PConstants.GREEN_MASK) >> 8, (img.pixels.getPixel(i) & PConstants.BLUE_MASK)));
+              img.pixels.setPixel(i, ((img.pixels.getPixel(i) & PConstants.ALPHA_MASK) | ((max < thresh) ? 0x000000 : 0xffffff)));
+            }
+            break;
+
+          case PConstants.ERODE:
+            dilate(true, img);
+            break;
+
+          case PConstants.DILATE:
+            dilate(false, img);
+            break;
+        }
+        img.updatePixels();
+      }
+    };
+
+
+    // shared variables for blit_resize(), filter_new_scanline(), filter_bilinear(), filter()
+    // change this in the future to not be exposed to p
+    p.shared = {
+      fracU: 0,
+      ifU: 0,
+      fracV: 0,
+      ifV: 0,
+      u1: 0,
+      u2: 0,
+      v1: 0,
+      v2: 0,
+      sX: 0,
+      sY: 0,
+      iw: 0,
+      iw1: 0,
+      ih1: 0,
+      ul: 0,
+      ll: 0,
+      ur: 0,
+      lr: 0,
+      cUL: 0,
+      cLL: 0,
+      cUR: 0,
+      cLR: 0,
+      srcXOffset: 0,
+      srcYOffset: 0,
+      r: 0,
+      g: 0,
+      b: 0,
+      a: 0,
+      srcBuffer: null,
+      blurRadius: 0,
+      blurKernelSize: 0,
+      blurKernel: null
+    };
+
+    p.intersect = function intersect(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2) {
+      var sw = sx2 - sx1 + 1;
+      var sh = sy2 - sy1 + 1;
+      var dw = dx2 - dx1 + 1;
+      var dh = dy2 - dy1 + 1;
+      if (dx1 < sx1) {
+        dw += dx1 - sx1;
+        if (dw > sw) {
+          dw = sw;
+        }
+      } else {
+        var w = sw + sx1 - dx1;
+        if (dw > w) {
+          dw = w;
+        }
+      }
+      if (dy1 < sy1) {
+        dh += dy1 - sy1;
+        if (dh > sh) {
+          dh = sh;
+        }
+      } else {
+        var h = sh + sy1 - dy1;
+        if (dh > h) {
+          dh = h;
+        }
+      }
+      return ! (dw <= 0 || dh <= 0);
+    };
+
+    p.filter_new_scanline = function filter_new_scanline() {
+      p.shared.sX = p.shared.srcXOffset;
+      p.shared.fracV = p.shared.srcYOffset & PConstants.PREC_MAXVAL;
+      p.shared.ifV = PConstants.PREC_MAXVAL - p.shared.fracV;
+      p.shared.v1 = (p.shared.srcYOffset >> PConstants.PRECISIONB) * p.shared.iw;
+      p.shared.v2 = Math.min((p.shared.srcYOffset >> PConstants.PRECISIONB) + 1, p.shared.ih1) * p.shared.iw;
+    };
+
+    p.filter_bilinear = function filter_bilinear() {
+      p.shared.fracU = p.shared.sX & PConstants.PREC_MAXVAL;
+      p.shared.ifU = PConstants.PREC_MAXVAL - p.shared.fracU;
+      p.shared.ul = (p.shared.ifU * p.shared.ifV) >> PConstants.PRECISIONB;
+      p.shared.ll = (p.shared.ifU * p.shared.fracV) >> PConstants.PRECISIONB;
+      p.shared.ur = (p.shared.fracU * p.shared.ifV) >> PConstants.PRECISIONB;
+      p.shared.lr = (p.shared.fracU * p.shared.fracV) >> PConstants.PRECISIONB;
+      p.shared.u1 = (p.shared.sX >> PConstants.PRECISIONB);
+      p.shared.u2 = Math.min(p.shared.u1 + 1, p.shared.iw1);
+      // get color values of the 4 neighbouring texels
+      // changed for 0.9
+      var cULoffset = (p.shared.v1 + p.shared.u1) * 4;
+      var cURoffset = (p.shared.v1 + p.shared.u2) * 4;
+      var cLLoffset = (p.shared.v2 + p.shared.u1) * 4;
+      var cLRoffset = (p.shared.v2 + p.shared.u2) * 4;
+      p.shared.cUL = p.color.toInt(p.shared.srcBuffer[cULoffset], p.shared.srcBuffer[cULoffset+1],
+                     p.shared.srcBuffer[cULoffset+2], p.shared.srcBuffer[cULoffset+3]);
+      p.shared.cUR = p.color.toInt(p.shared.srcBuffer[cURoffset], p.shared.srcBuffer[cURoffset+1],
+                     p.shared.srcBuffer[cURoffset+2], p.shared.srcBuffer[cURoffset+3]);
+      p.shared.cLL = p.color.toInt(p.shared.srcBuffer[cLLoffset], p.shared.srcBuffer[cLLoffset+1],
+                     p.shared.srcBuffer[cLLoffset+2], p.shared.srcBuffer[cLLoffset+3]);
+      p.shared.cLR = p.color.toInt(p.shared.srcBuffer[cLRoffset], p.shared.srcBuffer[cLRoffset+1],
+                     p.shared.srcBuffer[cLRoffset+2], p.shared.srcBuffer[cLRoffset+3]);
+      p.shared.r = ((p.shared.ul * ((p.shared.cUL & PConstants.RED_MASK) >> 16) + p.shared.ll *
+                   ((p.shared.cLL & PConstants.RED_MASK) >> 16) + p.shared.ur * ((p.shared.cUR & PConstants.RED_MASK) >> 16) +
+                   p.shared.lr * ((p.shared.cLR & PConstants.RED_MASK) >> 16)) << PConstants.PREC_RED_SHIFT) & PConstants.RED_MASK;
+      p.shared.g = ((p.shared.ul * (p.shared.cUL & PConstants.GREEN_MASK) + p.shared.ll * (p.shared.cLL & PConstants.GREEN_MASK) +
+                   p.shared.ur * (p.shared.cUR & PConstants.GREEN_MASK) + p.shared.lr *
+                   (p.shared.cLR & PConstants.GREEN_MASK)) >>> PConstants.PRECISIONB) & PConstants.GREEN_MASK;
+      p.shared.b = (p.shared.ul * (p.shared.cUL & PConstants.BLUE_MASK) + p.shared.ll * (p.shared.cLL & PConstants.BLUE_MASK) +
+                   p.shared.ur * (p.shared.cUR & PConstants.BLUE_MASK) + p.shared.lr * (p.shared.cLR & PConstants.BLUE_MASK)) >>> PConstants.PRECISIONB;
+      p.shared.a = ((p.shared.ul * ((p.shared.cUL & PConstants.ALPHA_MASK) >>> 24) + p.shared.ll *
+                   ((p.shared.cLL & PConstants.ALPHA_MASK) >>> 24) + p.shared.ur * ((p.shared.cUR & PConstants.ALPHA_MASK) >>> 24) +
+                   p.shared.lr * ((p.shared.cLR & PConstants.ALPHA_MASK) >>> 24)) << PConstants.PREC_ALPHA_SHIFT) & PConstants.ALPHA_MASK;
+      return p.shared.a | p.shared.r | p.shared.g | p.shared.b;
+    };
+
+    p.blit_resize = function blit_resize(img, srcX1, srcY1, srcX2, srcY2, destPixels,
+                                         screenW, screenH, destX1, destY1, destX2, destY2, mode) {
+      var x, y; // iterator vars
+      if (srcX1 < 0) {
+        srcX1 = 0;
+      }
+      if (srcY1 < 0) {
+        srcY1 = 0;
+      }
+      if (srcX2 >= img.width) {
+        srcX2 = img.width - 1;
+      }
+      if (srcY2 >= img.height) {
+        srcY2 = img.height - 1;
+      }
+      var srcW = srcX2 - srcX1;
+      var srcH = srcY2 - srcY1;
+      var destW = destX2 - destX1;
+      var destH = destY2 - destY1;
+      var smooth = true; // may as well go with the smoothing these days
+      if (!smooth) {
+        srcW++;
+        srcH++;
+      }
+      if (destW <= 0 || destH <= 0 || srcW <= 0 || srcH <= 0 || destX1 >= screenW ||
+          destY1 >= screenH || srcX1 >= img.width || srcY1 >= img.height) {
+        return;
+      }
+      var dx = Math.floor(srcW / destW * PConstants.PRECISIONF);
+      var dy = Math.floor(srcH / destH * PConstants.PRECISIONF);
+      p.shared.srcXOffset = Math.floor(destX1 < 0 ? -destX1 * dx : srcX1 * PConstants.PRECISIONF);
+      p.shared.srcYOffset = Math.floor(destY1 < 0 ? -destY1 * dy : srcY1 * PConstants.PRECISIONF);
+      if (destX1 < 0) {
+        destW += destX1;
+        destX1 = 0;
+      }
+      if (destY1 < 0) {
+        destH += destY1;
+        destY1 = 0;
+      }
+      destW = Math.min(destW, screenW - destX1);
+      destH = Math.min(destH, screenH - destY1);
+      // changed in 0.9, TODO
+      var destOffset = destY1 * screenW + destX1;
+      var destColor;
+      p.shared.srcBuffer = img.imageData.data;
+      if (smooth) {
+        // use bilinear filtering
+        p.shared.iw = img.width;
+        p.shared.iw1 = img.width - 1;
+        p.shared.ih1 = img.height - 1;
+        switch (mode) {
+        case PConstants.BLEND:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.blend(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.blend(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.ADD:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.add(destColor, p.filter_bilinear()));
+              destColor = p.color.toArray(p.modes.add(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.add(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.SUBTRACT:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.subtract(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.subtract(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.LIGHTEST:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.lightest(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.lightest(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.DARKEST:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.darkest(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.darkest(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.REPLACE:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.filter_bilinear());
+              //destPixels[destOffset + x] = p.filter_bilinear();
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.DIFFERENCE:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.difference(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.difference(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.EXCLUSION:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.exclusion(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.exclusion(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.MULTIPLY:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.multiply(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.multiply(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.SCREEN:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.screen(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.screen(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.OVERLAY:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.overlay(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.overlay(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.HARD_LIGHT:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.hard_light(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.hard_light(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.SOFT_LIGHT:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.soft_light(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.soft_light(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.DODGE:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.dodge(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.dodge(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        case PConstants.BURN:
+          for (y = 0; y < destH; y++) {
+            p.filter_new_scanline();
+            for (x = 0; x < destW; x++) {
+              // changed for 0.9
+              destColor = p.color.toInt(destPixels[(destOffset + x) * 4],
+                                        destPixels[((destOffset + x) * 4) + 1],
+                                        destPixels[((destOffset + x) * 4) + 2],
+                                        destPixels[((destOffset + x) * 4) + 3]);
+              destColor = p.color.toArray(p.modes.burn(destColor, p.filter_bilinear()));
+              //destPixels[destOffset + x] = p.modes.burn(destPixels[destOffset + x], p.filter_bilinear());
+              destPixels[(destOffset + x) * 4] = destColor[0];
+              destPixels[(destOffset + x) * 4 + 1] = destColor[1];
+              destPixels[(destOffset + x) * 4 + 2] = destColor[2];
+              destPixels[(destOffset + x) * 4 + 3] = destColor[3];
+              p.shared.sX += dx;
+            }
+            destOffset += screenW;
+            p.shared.srcYOffset += dy;
+          }
+          break;
+        }
+      }
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Font handling
+    ////////////////////////////////////////////////////////////////////////////
+
+    // Loads a font from an SVG or Canvas API
+    p.loadFont = function loadFont(name) {
+      if (name.indexOf(".svg") === -1) {
+        return {
+          name: "\"" + name + "\", sans-serif",
+          origName: name,
+          width: function(str) {
+            if ("measureText" in curContext) {
+              return curContext.measureText(typeof str === "number" ? String.fromCharCode(str) : str).width / curTextSize;
+            } else if ("mozMeasureText" in curContext) {
+              return curContext.mozMeasureText(typeof str === "number" ? String.fromCharCode(str) : str) / curTextSize;
+            } else {
+              return 0;
+            }
+          }
+        };
+      } else {
+        // If the font is a glyph, calculate by SVG table
+        var font = p.loadGlyphs(name);
+
+        return {
+          name: name,
+          glyph: true,
+          units_per_em: font.units_per_em,
+          horiz_adv_x: 1 / font.units_per_em * font.horiz_adv_x,
+          ascent: font.ascent,
+          descent: font.descent,
+          width: function(str) {
+            var width = 0;
+            var len = str.length;
+            for (var i = 0; i < len; i++) {
+              try {
+                width += parseFloat(p.glyphLook(p.glyphTable[name], str[i]).horiz_adv_x);
+              }
+              catch(e) {
+                Processing.debug(e);
+              }
+            }
+            return width / p.glyphTable[name].units_per_em;
+          }
+        };
+      }
+    };
+
+    p.createFont = function(name, size, smooth, charset) {
+      if (arguments.length === 2) {
+        p.textSize(size);
+        return p.loadFont(name);
+      } else if (arguments.length === 3) {
+        // smooth: true for an antialiased font, false for aliased
+        p.textSize(size);
+        return p.loadFont(name);
+      } else if (arguments.length === 4) {
+        // charset: char array containing characters to be generated
+        p.textSize(size);
+        return p.loadFont(name);
+      } else {
+        throw("incorrent number of parameters for createFont");
+      }
+    };
+
+    // Sets a 'current font' for use
+    p.textFont = function textFont(name, size) {
+      curTextFont = name;
+      p.textSize(size);
+    };
+
+    // Sets the font size
+    p.textSize = function textSize(size) {
+      if (size) {
+        curTextSize = size;
+      }
+    };
+
+    p.textAlign = function textAlign() {
+      if(arguments.length === 1) {
+        horizontalTextAlignment = arguments[0];
+      } else if(arguments.length === 2) {
+        horizontalTextAlignment = arguments[0];
+        verticalTextAlignment = arguments[1];
+      }
+    };
+
+    p.textWidth = function textWidth(str) {
+      if (p.use3DContext) {
+        if (textcanvas === undef) {
+          textcanvas = document.createElement("canvas");
+        }
+        var oldContext = curContext;
+        curContext = textcanvas.getContext("2d");
+        curContext.font = curContext.mozTextStyle = curTextSize + "px " + curTextFont.name;
+        if ("fillText" in curContext) {
+          textcanvas.width = curContext.measureText(str).width;
+        } else if ("mozDrawText" in curContext) {
+          textcanvas.width = curContext.mozMeasureText(str);
+        }
+        curContext = oldContext;
+        return textcanvas.width;
+      } else {
+        curContext.font = curTextSize + "px " + curTextFont.name;
+        if ("fillText" in curContext) {
+          return curContext.measureText(str).width;
+        } else if ("mozDrawText" in curContext) {
+          return curContext.mozMeasureText(str);
+        }
+      }
+    };
+
+    p.textAscent = (function() {
+      var oldTextSize = undef,
+          oldTextFont = undef,
+          ascent      = undef,
+          graphics    = undef;
+      return function textAscent() {
+        // if text size or font has changed, recalculate ascent value
+        if (oldTextFont !== curTextFont || oldTextSize !== curTextSize) {
+          // store current size and font
+          oldTextFont = curTextFont;
+          oldTextSize = curTextSize;
+
+          var found       = false,
+              character   = "k",
+              colour      = p.color(0),
+              top         = 0,
+              bottom      = curTextSize,
+              yLoc        = curTextSize/2;
+
+          // setup off screen image to write and measure text from
+          if (graphics !== undef) {
+            graphics.size(curTextSize, curTextSize);
+          } else {
+            graphics = p.createGraphics(curTextSize, curTextSize);
+          }
+          graphics.background(0);
+          graphics.fill(255);
+          graphics.textFont(curTextFont, curTextSize);
+          graphics.text(character, 0, curTextSize);
+
+          // binary search for highest pixel
+          while(yLoc !== bottom) {
+            for (var xLoc = 0; xLoc < curTextSize; xLoc++) {
+              if (graphics.get(xLoc, yLoc) !== colour) {
+                found = true;
+                xLoc = curTextSize;
+              }
+            }
+            if (found) {
+              // y--
+              bottom = yLoc;
+              found = false;
+            } else {
+              // y++
+              top = yLoc;
+            }
+            yLoc = Math.ceil((bottom + top)/2);
+          }
+          ascent = ((curTextSize-1) - yLoc) + 1;
+          return ascent;
+        } else { // text size and font have not changed since last time
+          return ascent;
+        }
+      };
+    }());
+
+    p.textDescent = (function() {
+      var oldTextSize = undef,
+          oldTextFont = undef,
+          descent     = undef,
+          graphics    = undef;
+      return function textDescent() {
+        // if text size or font has changed, recalculate descent value
+        if (oldTextFont !== curTextFont || oldTextSize !== curTextSize) {
+          // store current size and font
+          oldTextFont = curTextFont;
+          oldTextSize = curTextSize;
+
+          var found       = false,
+              character   = "p",
+              colour      = p.color(0),
+              top         = 0,
+              bottom      = curTextSize,
+              yLoc        = curTextSize/2;
+
+          // setup off screen image to write and measure text from
+          if (graphics !== undef) {
+            graphics.size(curTextSize, curTextSize);
+          } else {
+            graphics = p.createGraphics(curTextSize, curTextSize);
+          }
+          graphics.background(0);
+          graphics.fill(255);
+          graphics.textFont(curTextFont, curTextSize);
+          graphics.text(character, 0, 0);
+
+          // binary search for lowest pixel
+          while(yLoc !== bottom) {
+            for (var xLoc = 0; xLoc < curTextSize; xLoc++) {
+              if (graphics.get(xLoc, yLoc) !== colour) {
+                found = true;
+                xLoc = curTextSize;
+              }
+            }
+            if (found) {
+              // y++
+              top = yLoc;
+              found = false;
+            } else {
+              // y--
+              bottom = yLoc;
+            }
+            yLoc = Math.ceil((bottom + top)/2);
+          }
+          descent = yLoc + 1;
+          return descent;
+        } else { // text size and font have not changed since last time
+          return descent;
+        }
+      };
+    }());
+
+    // A lookup table for characters that can not be referenced by Object
+    p.glyphLook = function glyphLook(font, chr) {
+      try {
+        switch (chr) {
+        case "1":
+          return font.one;
+        case "2":
+          return font.two;
+        case "3":
+          return font.three;
+        case "4":
+          return font.four;
+        case "5":
+          return font.five;
+        case "6":
+          return font.six;
+        case "7":
+          return font.seven;
+        case "8":
+          return font.eight;
+        case "9":
+          return font.nine;
+        case "0":
+          return font.zero;
+        case " ":
+          return font.space;
+        case "$":
+          return font.dollar;
+        case "!":
+          return font.exclam;
+        case '"':
+          return font.quotedbl;
+        case "#":
+          return font.numbersign;
+        case "%":
+          return font.percent;
+        case "&":
+          return font.ampersand;
+        case "'":
+          return font.quotesingle;
+        case "(":
+          return font.parenleft;
+        case ")":
+          return font.parenright;
+        case "*":
+          return font.asterisk;
+        case "+":
+          return font.plus;
+        case ",":
+          return font.comma;
+        case "-":
+          return font.hyphen;
+        case ".":
+          return font.period;
+        case "/":
+          return font.slash;
+        case "_":
+          return font.underscore;
+        case ":":
+          return font.colon;
+        case ";":
+          return font.semicolon;
+        case "<":
+          return font.less;
+        case "=":
+          return font.equal;
+        case ">":
+          return font.greater;
+        case "?":
+          return font.question;
+        case "@":
+          return font.at;
+        case "[":
+          return font.bracketleft;
+        case "\\":
+          return font.backslash;
+        case "]":
+          return font.bracketright;
+        case "^":
+          return font.asciicircum;
+        case "`":
+          return font.grave;
+        case "{":
+          return font.braceleft;
+        case "|":
+          return font.bar;
+        case "}":
+          return font.braceright;
+        case "~":
+          return font.asciitilde;
+          // If the character is not 'special', access it by object reference
+        default:
+          return font[chr];
+        }
+      } catch(e) {
+        Processing.debug(e);
+      }
+    };
+
+    function toP5String(obj) {
+      if(obj instanceof String) {
+        return obj;
+      } else if(typeof obj === 'number') {
+        // check if an int
+        if(obj === (0 | obj)) {
+          return obj.toString();
+        } else {
+          return p.nf(obj, 0, 3);
+        }
+      } else if(obj === null || obj === undef) {
+        return "";
+      } else {
+        return obj.toString();
+      }
+    }
+
+    // Print some text to the Canvas
+    function text$line(str, x, y, z, align) {
+      var textWidth = 0, xOffset = 0;
+      // If the font is a standard Canvas font...
+      if (!curTextFont.glyph) {
+        if (str && ("fillText" in curContext || "mozDrawText" in curContext)) {
+          curContext.font = curContext.mozTextStyle = curTextSize + "px " + curTextFont.name;
+
+          if (isFillDirty) {
+            curContext.fillStyle = p.color.toString(currentFillColor);
+            isFillDirty = false;
+          }
+
+          // horizontal offset/alignment
+          if(align === PConstants.RIGHT || align === PConstants.CENTER) {
+            if ("fillText" in curContext) {
+              textWidth = curContext.measureText(str).width;
+            } else if ("mozDrawText" in curContext) {
+              textWidth = curContext.mozMeasureText(str);
+            }
+
+            if(align === PConstants.RIGHT) {
+              xOffset = -textWidth;
+            } else { // if(align === PConstants.CENTER)
+              xOffset = -textWidth/2;
+            }
+          }
+
+          if ("fillText" in curContext) {
+            curContext.fillText(str, x+xOffset, y);
+          } else if ("mozDrawText" in curContext) {
+            saveContext();
+            curContext.translate(x+xOffset, y);
+            curContext.mozDrawText(str);
+            restoreContext();
+          }
+        }
+      } else {
+        // If the font is a Batik SVG font...
+        var font = p.glyphTable[curTextFont.name];
+        saveContext();
+        curContext.translate(x, y + curTextSize);
+
+        // horizontal offset/alignment
+        if(align === PConstants.RIGHT || align === PConstants.CENTER) {
+          textWidth = font.width(str);
+
+          if(align === PConstants.RIGHT) {
+            xOffset = -textWidth;
+          } else { // if(align === PConstants.CENTER)
+            xOffset = -textWidth/2;
+          }
+        }
+
+        var upem   = font.units_per_em,
+          newScale = 1 / upem * curTextSize;
+
+        curContext.scale(newScale, newScale);
+
+        for (var i=0, len=str.length; i < len; i++) {
+          // Test character against glyph table
+          try {
+            p.glyphLook(font, str[i]).draw();
+          } catch(e) {
+            Processing.debug(e);
+          }
+        }
+        restoreContext();
+      }
+    }
+
+    function text$line$3d(str, x, y, z, align) {
+      // handle case for 3d text
+      if (textcanvas === undef) {
+        textcanvas = document.createElement("canvas");
+      }
+      var oldContext = curContext;
+      curContext = textcanvas.getContext("2d");
+      curContext.font = curContext.mozTextStyle = curTextSize + "px " + curTextFont.name;
+      var textWidth = 0;
+      if ("fillText" in curContext) {
+        textWidth = curContext.measureText(str).width;
+      } else if ("mozDrawText" in curContext) {
+        textWidth = curContext.mozMeasureText(str);
+      }
+      textcanvas.width = textWidth;
+      textcanvas.height = curTextSize;
+      curContext = textcanvas.getContext("2d"); // refreshes curContext
+      curContext.font = curContext.mozTextStyle = curTextSize + "px " + curTextFont.name;
+      curContext.textBaseline="top";
+
+      // paint on 2D canvas
+      text$line(str,0,0,0,PConstants.LEFT);
+
+      // use it as a texture
+      var aspect = textcanvas.width/textcanvas.height;
+      curContext = oldContext;
+
+      executeTexImage2D(textcanvas);
+      curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR);
+      curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR_MIPMAP_LINEAR);
+      curContext.generateMipmap(curContext.TEXTURE_2D);
+
+      // horizontal offset/alignment
+      var xOffset = 0;
+      if (align === PConstants.RIGHT) {
+        xOffset = -textWidth;
+      } else if(align === PConstants.CENTER) {
+        xOffset = -textWidth/2;
+      }
+      var model = new PMatrix3D();
+      var scalefactor = curTextSize * 0.5;
+      model.translate(x+xOffset-scalefactor/2, y-scalefactor, z);
+      model.scale(-aspect*scalefactor, -scalefactor, scalefactor);
+      model.translate(-1, -1, -1);
+      model.transpose();
+
+      var view = new PMatrix3D();
+      view.scale(1, -1, 1);
+      view.apply(modelView.array());
+      view.transpose();
+
+      var proj = new PMatrix3D();
+      proj.set(projection);
+      proj.transpose();
+
+      curContext.useProgram(programObject2D);
+      vertexAttribPointer(programObject2D, "Vertex", 3, textBuffer);
+      vertexAttribPointer(programObject2D, "aTextureCoord", 2, textureBuffer);
+      uniformi(programObject2D, "uSampler", [0]);
+      uniformi(programObject2D, "picktype", 1);
+      uniformMatrix(programObject2D, "model", false,  model.array());
+      uniformMatrix(programObject2D, "view", false, view.array());
+      uniformMatrix(programObject2D, "projection", false, proj.array());
+      uniformf(programObject2D, "color", fillStyle);
+      curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer);
+      curContext.drawElements(curContext.TRIANGLES, 6, curContext.UNSIGNED_SHORT, 0);
+    }
+
+    function text$4(str, x, y, z) {
+      var lineFunction = p.use3DContext ?  text$line$3d : text$line;
+      var lines, linesCount;
+      if(str.indexOf('\n') < 0) {
+        lines = [str];
+        linesCount = 1;
+      } else {
+        lines = str.split(/\r?\n/g);
+        linesCount = lines.length;
+      }
+      // handle text line-by-line
+
+      var yOffset;
+      if(verticalTextAlignment === PConstants.TOP) {
+        yOffset = (1-baselineOffset) * curTextSize;
+      } else if(verticalTextAlignment === PConstants.CENTER) {
+        yOffset = (1-baselineOffset - linesCount/2) * curTextSize;
+      } else if(verticalTextAlignment === PConstants.BOTTOM) {
+        yOffset = (1-baselineOffset - linesCount) * curTextSize;
+      } else { //  if(verticalTextAlignment === PConstants.BASELINE) {
+        yOffset = (1 - linesCount) * curTextSize;
+      }
+      for(var i=0;i<linesCount;++i) {
+        var line = lines[i];
+        lineFunction(line, x, y + yOffset, z, horizontalTextAlignment);
+        yOffset += curTextSize;
+      }
+    }
+
+    function text$6(str, x, y, width, height, z) {
+      if (str.length === 0) { // is empty string
+        return;
+      }
+      if(curTextSize > height) { // is text height larger than box
+        return;
+      }
+
+      var spaceMark = -1;
+      var start = 0;
+      var lineWidth = 0;
+      var textboxWidth = width;
+
+      var yOffset = 0;
+
+      curContext.font = curTextSize + "px " + curTextFont.name;
+
+      var drawCommands = [];
+      var hadSpaceBefore = false;
+      for (var j=0, len=str.length; j < len; j++) {
+        var currentChar = str[j];
+        var letterWidth;
+
+        if ("fillText" in curContext) {
+          letterWidth = curContext.measureText(currentChar).width;
+        } else if ("mozDrawText" in curContext) {
+          letterWidth = curContext.mozMeasureText(currentChar);
+        }
+
+        if (currentChar !== "\n" && (currentChar === " " || (hadSpaceBefore && str[j + 1] === " ") ||
+            lineWidth + 2 * letterWidth < textboxWidth)) { // check a line of text
+          if (currentChar === " ") {
+            spaceMark = j;
+          }
+          lineWidth += letterWidth;
+        } else { // draw a line of text
+          if (start === spaceMark + 1) { // in case a whole line without a space
+            spaceMark = j;
+          }
+
+          if (str[j] === "\n") {
+            drawCommands.push({text:str.substring(start, j), width: lineWidth, offset: yOffset});
+            start = j + 1;
+          } else {
+            drawCommands.push({text:str.substring(start, spaceMark + 1), width: lineWidth, offset: yOffset});
+            start = spaceMark + 1;
+          }
+          yOffset += curTextSize;
+
+          lineWidth = 0;
+          j = start - 1;
+        }
+        hadSpaceBefore = currentChar === " ";
+      } // for (var j=
+
+      if (start < len) { // draw the last line
+        drawCommands.push({text:str.substring(start), width: lineWidth, offset: yOffset});
+        yOffset += curTextSize;
+      }
+
+      // actual draw
+      var lineFunction = p.use3DContext ?  text$line$3d : text$line;
+      var xOffset = 0;
+      if (horizontalTextAlignment === PConstants.CENTER) {
+        xOffset = width / 2;
+      } else if (horizontalTextAlignment === PConstants.RIGHT) {
+        xOffset = width;
+      }
+
+      // offsets for alignment
+      var boxYOffset1 = (1-baselineOffset) * curTextSize, boxYOffset2 = 0;
+      if (verticalTextAlignment === PConstants.BOTTOM) {
+        boxYOffset2 = height-yOffset;
+      } else if (verticalTextAlignment === PConstants.CENTER) {
+        boxYOffset2 = (height-yOffset) / 2;
+      }
+
+      for (var il=0,ll=drawCommands.length; il<ll; ++il) {
+        var command = drawCommands[il];
+        if (command.offset + boxYOffset2 < 0) {
+          continue; // skip if not inside box yet
+        }
+        if (command.offset + boxYOffset2 + curTextSize > height) {
+          break; // stop if no enough space for one more line draw
+        }
+        lineFunction(command.text, x + xOffset, y + command.offset + boxYOffset1 + boxYOffset2, z, horizontalTextAlignment);
+      }
+    }
+
+    p.text = function text() {
+      if (tMode === PConstants.SCREEN) {  // TODO: 3D Screen not working yet due to 3D not working in textAscent
+        p.pushMatrix();
+        p.resetMatrix();
+        var asc = p.textAscent();
+        var des = p.textDescent();
+        var tWidth = p.textWidth(arguments[0]);
+        var tHeight = asc + des;
+        var font = p.loadFont(curTextFont.origName);
+        var hud = p.createGraphics(tWidth, tHeight);
+        hud.beginDraw();
+        hud.fill(currentFillColor);
+        hud.opaque = false;
+        hud.background(0, 0, 0, 0);
+        hud.textFont(font);
+        hud.textSize(curTextSize);
+        hud.text(arguments[0], 0, asc);
+        hud.endDraw();
+        if (arguments.length === 5 || arguments.length === 6) {
+          p.image(hud, arguments[1], arguments[2]-asc, arguments[3], arguments[4]);
+        } else {
+          p.image(hud, arguments[1], arguments[2]-asc);
+        }
+        p.popMatrix();
+      }
+      else if (tMode === PConstants.SHAPE) {
+        // TODO: requires beginRaw function
+      } else {
+        if (arguments.length === 3) { // for text( str, x, y)
+          text$4(toP5String(arguments[0]), arguments[1], arguments[2], 0);
+        } else if (arguments.length === 4) { // for text( str, x, y, z)
+          text$4(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3]);
+        } else if (arguments.length === 5) { // for text( str, x, y , width, height)
+          text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], 0);
+        } else if (arguments.length === 6) { // for text( stringdata, x, y , width, height, z)
+          text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
+        }
+      }
+    };
+
+    p.textMode = function textMode(mode){
+      tMode = mode;
+    };
+
+    // Load Batik SVG Fonts and parse to pre-def objects for quick rendering
+    p.loadGlyphs = function loadGlyph(url) {
+      var x, y, cx, cy, nx, ny, d, a, lastCom, lenC, horiz_adv_x, getXY = '[0-9\\-]+', path;
+
+      // Return arrays of SVG commands and coords
+      // get this to use p.matchAll() - will need to work around the lack of null return
+      var regex = function regex(needle, hay) {
+        var i = 0,
+          results = [],
+          latest, regexp = new RegExp(needle, "g");
+        latest = results[i] = regexp.exec(hay);
+        while (latest) {
+          i++;
+          latest = results[i] = regexp.exec(hay);
+        }
+        return results;
+      };
+
+      var buildPath = function buildPath(d) {
+        var c = regex("[A-Za-z][0-9\\- ]+|Z", d);
+
+        // Begin storing path object
+        path = "var path={draw:function(){saveContext();curContext.beginPath();";
+
+        x = 0;
+        y = 0;
+        cx = 0;
+        cy = 0;
+        nx = 0;
+        ny = 0;
+        d = 0;
+        a = 0;
+        lastCom = "";
+        lenC = c.length - 1;
+
+        // Loop through SVG commands translating to canvas eqivs functions in path object
+        for (var j = 0; j < lenC; j++) {
+          var com = c[j][0], xy = regex(getXY, com);
+
+          switch (com[0]) {
+            case "M":
+              //curContext.moveTo(x,-y);
+              x = parseFloat(xy[0][0]);
+              y = parseFloat(xy[1][0]);
+              path += "curContext.moveTo(" + x + "," + (-y) + ");";
+              break;
+
+            case "L":
+              //curContext.lineTo(x,-y);
+              x = parseFloat(xy[0][0]);
+              y = parseFloat(xy[1][0]);
+              path += "curContext.lineTo(" + x + "," + (-y) + ");";
+              break;
+
+            case "H":
+              //curContext.lineTo(x,-y)
+              x = parseFloat(xy[0][0]);
+              path += "curContext.lineTo(" + x + "," + (-y) + ");";
+              break;
+
+            case "V":
+              //curContext.lineTo(x,-y);
+              y = parseFloat(xy[0][0]);
+              path += "curContext.lineTo(" + x + "," + (-y) + ");";
+              break;
+
+            case "T":
+              //curContext.quadraticCurveTo(cx,-cy,nx,-ny);
+              nx = parseFloat(xy[0][0]);
+              ny = parseFloat(xy[1][0]);
+
+              if (lastCom === "Q" || lastCom === "T") {
+                d = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(cy - y, 2));
+                a = Math.PI + Math.atan2(cx - x, cy - y);
+                cx = x + (Math.sin(a) * (d));
+                cy = y + (Math.cos(a) * (d));
+              } else {
+                cx = x;
+                cy = y;
+              }
+
+              path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");";
+              x = nx;
+              y = ny;
+              break;
+
+            case "Q":
+              //curContext.quadraticCurveTo(cx,-cy,nx,-ny);
+              cx = parseFloat(xy[0][0]);
+              cy = parseFloat(xy[1][0]);
+              nx = parseFloat(xy[2][0]);
+              ny = parseFloat(xy[3][0]);
+              path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");";
+              x = nx;
+              y = ny;
+              break;
+
+            case "Z":
+              //curContext.closePath();
+              path += "curContext.closePath();";
+              break;
+          }
+          lastCom = com[0];
+        }
+
+        path += "executeContextFill();executeContextStroke();";
+        path += "restoreContext();";
+        path += "curContext.translate(" + horiz_adv_x + ",0);";
+        path += "}}";
+
+        return path;
+      };
+
+      // Parse SVG font-file into block of Canvas commands
+      var parseSVGFont = function parseSVGFontse(svg) {
+        // Store font attributes
+        var font = svg.getElementsByTagName("font");
+        p.glyphTable[url].horiz_adv_x = font[0].getAttribute("horiz-adv-x");
+
+        var font_face = svg.getElementsByTagName("font-face")[0];
+        p.glyphTable[url].units_per_em = parseFloat(font_face.getAttribute("units-per-em"));
+        p.glyphTable[url].ascent = parseFloat(font_face.getAttribute("ascent"));
+        p.glyphTable[url].descent = parseFloat(font_face.getAttribute("descent"));
+
+        var glyph = svg.getElementsByTagName("glyph"),
+          len = glyph.length;
+
+        // Loop through each glyph in the SVG
+        for (var i = 0; i < len; i++) {
+          // Store attributes for this glyph
+          var unicode = glyph[i].getAttribute("unicode");
+          var name = glyph[i].getAttribute("glyph-name");
+          horiz_adv_x = glyph[i].getAttribute("horiz-adv-x");
+          if (horiz_adv_x === null) {
+            horiz_adv_x = p.glyphTable[url].horiz_adv_x;
+          }
+          d = glyph[i].getAttribute("d");
+          // Split path commands in glpyh
+          if (d !== undef) {
+            path = buildPath(d);
+            eval(path);
+            // Store glyph data to table object
+            p.glyphTable[url][name] = {
+              name: name,
+              unicode: unicode,
+              horiz_adv_x: horiz_adv_x,
+              draw: path.draw
+            };
+          }
+        } // finished adding glyphs to table
+      };
+
+      // Load and parse Batik SVG font as XML into a Processing Glyph object
+      var loadXML = function loadXML() {
+        var xmlDoc;
+
+        try {
+          xmlDoc = document.implementation.createDocument("", "", null);
+        }
+        catch(e_fx_op) {
+          Processing.debug(e_fx_op.message);
+          return;
+        }
+
+        try {
+          xmlDoc.async = false;
+          xmlDoc.load(url);
+          parseSVGFont(xmlDoc.getElementsByTagName("svg")[0]);
+        }
+        catch(e_sf_ch) {
+          // Google Chrome, Safari etc.
+          Processing.debug(e_sf_ch);
+          try {
+            var xmlhttp = new window.XMLHttpRequest();
+            xmlhttp.open("GET", url, false);
+            xmlhttp.send(null);
+            parseSVGFont(xmlhttp.responseXML.documentElement);
+          }
+          catch(e) {
+            Processing.debug(e_sf_ch);
+          }
+        }
+      };
+
+      // Create a new object in glyphTable to store this font
+      p.glyphTable[url] = {};
+
+      // Begin loading the Batik SVG font...
+      loadXML(url);
+
+      // Return the loaded font for attribute grabbing
+      return p.glyphTable[url];
+    };
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Class methods
+    ////////////////////////////////////////////////////////////////////////////
+
+    p.extendClass = function extendClass(subClass, baseClass) {
+      function extendGetterSetter(propertyName) {
+        p.defineProperty(subClass, propertyName, {
+          get: function() {
+            return baseClass[propertyName];
+          },
+          set: function(v) {
+            baseClass[propertyName]=v;
+          }
+        });
+      }
+
+      for (var propertyName in baseClass) {
+        if (subClass[propertyName] === undef) {
+          if (typeof baseClass[propertyName] === 'function') {
+            subClass[propertyName] = baseClass[propertyName];
+          } else {
+            extendGetterSetter(propertyName);
+          }
+        }
+      }
+    };
+
+    p.addMethod = function addMethod(object, name, fn, superAccessor) {
+      if (object[name]) {
+        var args = fn.length,
+          oldfn = object[name];
+
+        object[name] = function() {
+          if (arguments.length === args) {
+            return fn.apply(this, arguments);
+          } else {
+            return oldfn.apply(this, arguments);
+          }
+        };
+      } else {
+        object[name] = fn;
+      }
+    };
+
+    //////////////////////////////////////////////////////////////////////////
+    // Event handling
+    //////////////////////////////////////////////////////////////////////////
+
+    function attach(elem, type, fn) {
+      if (elem.addEventListener) {
+        elem.addEventListener(type, fn, false);
+      } else {
+        elem.attachEvent("on" + type, fn);
+      }
+      eventHandlers.push([elem, type, fn]);
+    }
+
+    attach(curElement, "mousemove", function(e) {
+      var element = curElement, offsetX = 0, offsetY = 0;
+
+      p.pmouseX = p.mouseX;
+      p.pmouseY = p.mouseY;
+
+      if (element.offsetParent) {
+        do {
+          offsetX += element.offsetLeft;
+          offsetY += element.offsetTop;
+        } while ((element = element.offsetParent));
+      }
+
+      // Add padding and border style widths to offset
+      offsetX += stylePaddingLeft;
+      offsetY += stylePaddingTop;
+
+      offsetX += styleBorderLeft;
+      offsetY += styleBorderTop;
+
+      // Dropping support for IE clientX and clientY, switching to pageX and pageY so we don't have to calculate scroll offset.
+      // Removed in ticket #184. See rev: 2f106d1c7017fed92d045ba918db47d28e5c16f4
+      p.mouseX = e.pageX - offsetX;
+      p.mouseY = e.pageY - offsetY;
+
+      if (typeof p.mouseMoved === "function" && !p.__mousePressed) {
+        p.mouseMoved();
+      }
+      if (typeof p.mouseDragged === "function" && p.__mousePressed) {
+        p.mouseDragged();
+        p.mouseDragging = true;
+      }
+    });
+
+    attach(curElement, "mouseout", function(e) {
+    });
+
+    attach(curElement, "mousedown", function(e) {
+      p.__mousePressed = true;
+      p.mouseDragging = false;
+      switch (e.which) {
+      case 1:
+        p.mouseButton = PConstants.LEFT;
+        break;
+      case 2:
+        p.mouseButton = PConstants.CENTER;
+        break;
+      case 3:
+        p.mouseButton = PConstants.RIGHT;
+        break;
+      }
+
+      if (typeof p.mousePressed === "function") {
+        p.mousePressed();
+      }
+    });
+
+    attach(curElement, "mouseup", function(e) {
+      p.__mousePressed = false;
+
+      if (typeof p.mouseClicked === "function" && !p.mouseDragging) {
+        p.mouseClicked();
+      }
+
+      if (typeof p.mouseReleased === "function") {
+        p.mouseReleased();
+      }
+    });
+
+    var mouseWheelHandler = function(e) {
+      var delta = 0;
+
+      if (e.wheelDelta) {
+        delta = e.wheelDelta / 120;
+        if (window.opera) {
+          delta = -delta;
+        }
+      } else if (e.detail) {
+        delta = -e.detail / 3;
+      }
+
+      p.mouseScroll = delta;
+
+      if (delta && typeof p.mouseScrolled === 'function') {
+        p.mouseScrolled();
+      }
+    };
+
+    // Support Gecko and non-Gecko scroll events
+    attach(document, 'DOMMouseScroll', mouseWheelHandler);
+    attach(document, 'mousewheel', mouseWheelHandler);
+
+    //////////////////////////////////////////////////////////////////////////
+    // Keyboard Events
+    //////////////////////////////////////////////////////////////////////////
+
+    function keyCodeMap(code, shift) {
+      // Letters
+      if (code >= 65 && code <= 90) { // A-Z
+        // Keys return ASCII for upcased letters.
+        // Convert to downcase if shiftKey is not pressed.
+        if (shift) {
+          return code;
+        }
+        else {
+          return code + 32;
+        }
+      }
+
+      // Numbers and their shift-symbols
+      else if (code >= 48 && code <= 57) { // 0-9
+        if (shift) {
+          switch (code) {
+          case 49:
+            return 33; // !
+          case 50:
+            return 64; // @
+          case 51:
+            return 35; // #
+          case 52:
+            return 36; // $
+          case 53:
+            return 37; // %
+          case 54:
+            return 94; // ^
+          case 55:
+            return 38; // &
+          case 56:
+            return 42; // *
+          case 57:
+            return 40; // (
+          case 48:
+            return 41; // )
+          }
+        }
+      }
+
+      // Coded keys
+      else if (codedKeys.indexOf(code) >= 0) { // SHIFT, CONTROL, ALT, LEFT, RIGHT, UP, DOWN
+        p.keyCode = code;
+        return PConstants.CODED;
+      }
+
+      // Symbols and their shift-symbols
+      else {
+        if (shift) {
+          switch (code) {
+          case 107:
+            return 43; // +
+          case 219:
+            return 123; // {
+          case 221:
+            return 125; // }
+          case 222:
+            return 34; // "
+          }
+        } else {
+          switch (code) {
+          case 188:
+            return 44; // ,
+          case 109:
+            return 45; // -
+          case 190:
+            return 46; // .
+          case 191:
+            return 47; // /
+          case 192:
+            return 96; // ~
+          case 219:
+            return 91; // [
+          case 220:
+            return 92; // \
+          case 221:
+            return 93; // ]
+          case 222:
+            return 39; // '
+          }
+        }
+      }
+      return code;
+    }
+
+    attach(document, "keydown", function(e) {
+      p.__keyPressed = true;
+      p.keyCode = null;
+      p.key = keyCodeMap(e.keyCode, e.shiftKey);
+
+      if (typeof p.keyPressed === "function") {
+        p.keyPressed();
+      }
+    });
+
+    attach(document, "keyup", function(e) {
+      p.keyCode = null;
+      p.key = keyCodeMap(e.keyCode, e.shiftKey);
+
+      //TODO: This needs to only be made false if all keys have been released.
+      p.__keyPressed = false;
+
+      if (typeof p.keyReleased === "function") {
+        p.keyReleased();
+      }
+    });
+
+    attach(document, "keypress", function (e) {
+      // In Firefox, e.keyCode is not currently set with keypress.
+      //
+      // keypress will always happen after a keydown, so p.keyCode and p.key
+      // should remain correct. Some browsers (chrome) refire keydown when
+      // key repeats happen, others (firefox) don't. Either way keyCode and
+      // key should remain correct.
+
+      if (p.keyTyped) {
+        p.keyTyped();
+      }
+    });
+
+    // Place-holder for debugging function
+    Processing.debug = function(e) {};
+
+    // Get the DOM element if string was passed
+    if (typeof curElement === "string") {
+      curElement = document.getElementById(curElement);
+    }
+
+    // Send aCode Processing syntax to be converted to JavaScript
+    if (aCode) {
+      if(aCode instanceof Processing.Sketch) {
+        // Use sketch as is
+        curSketch = aCode;
+      } else if(typeof aCode === "function") {
+        // Wrap function with default sketch parameters
+        curSketch = new Processing.Sketch(aCode);
+      } else {
+        // Compile the code
+        curSketch = Processing.compile(aCode);
+      }
+
+      // Expose internal field for diagnostics and testing
+      p.externals.sketch = curSketch;
+
+      p.use3DContext = curSketch.use3DContext;
+
+      if ("mozOpaque" in curElement) {
+        curElement.mozOpaque = !curSketch.options.isTransparent;
+      }
+
+      // Initialize the onfocus and onblur event handler externals
+      if (curSketch.options.pauseOnBlur) {
+        p.externals.onfocus = function() {
+          if (doLoop) {
+            p.loop();
+          }
+        };
+
+        p.externals.onblur = function() {
+          if (doLoop && loopStarted) {
+            p.noLoop();
+            doLoop = true; // make sure to keep this true after the noLoop call
+          }
+        };
+      }
+
+      if (!curSketch.use3DContext) {
+        // Setup default 2d canvas context.
+        curContext = curElement.getContext('2d');
+
+        // Externalize the default context
+        p.externals.context = curContext;
+
+        modelView = new PMatrix2D();
+
+        // Canvas has trouble rendering single pixel stuff on whole-pixel
+        // counts, so we slightly offset it (this is super lame).
+        curContext.translate(0.5, 0.5);
+
+        curContext.lineCap = 'round';
+
+        // Set default stroke and fill color
+        p.stroke(0);
+        p.fill(255);
+        p.noSmooth();
+        p.disableContextMenu();
+      }
+
+      // Step through the libraries that were attached at doc load...
+      for (var i in Processing.lib) {
+        if (Processing.lib.hasOwnProperty(i)) {
+          // Init the libraries in the context of this p_instance
+          Processing.lib[i].call(this);
+        }
+      }
+
+      var executeSketch = function(processing) {
+        // Don't start until all specified images and fonts in the cache are preloaded
+        if (!curSketch.imageCache.pending && curSketch.fonts.pending()) {
+          curSketch.attach(processing, PConstants);
+
+          // Run void setup()
+          if (processing.setup) {
+            processing.setup();
+          }
+
+          // some pixels can be cached, flushing
+          resetContext();
+
+          if (processing.draw) {
+            if (!doLoop) {
+              processing.redraw();
+            } else {
+              processing.loop();
+            }
+          }
+        } else {
+          window.setTimeout(function() { executeSketch(processing); }, 10);
+        }
+      };
+
+      // The parser adds custom methods to the processing context
+      // this renames p to processing so these methods will run
+      executeSketch(p);
+    } else {
+      // No executable sketch was specified
+      // or called via createGraphics
+      curSketch = new Processing.Sketch();
+      curSketch.options.isTransparent = true;
+    }
+
+  };
+
+  // Processing global methods and constants for the parser
+  function getGlobalMembers() {
+    var names = [ /* this code is generated by jsglobals.js */
+      "abs", "acos", "alpha", "ambient", "ambientLight", "append", "applyMatrix",
+      "arc", "arrayCopy", "ArrayList", "asin", "atan", "atan2", "background",
+      "beginCamera", "beginDraw", "beginShape", "bezier", "bezierDetail",
+      "bezierPoint", "bezierTangent", "bezierVertex", "binary", "blend",
+      "blendColor", "blit_resize", "blue", "boolean", "box", "breakShape",
+      "brightness", "byte", "camera", "ceil", "char", "Character", "clear",
+      "color", "colorMode", "concat", "console", "constrain", "copy", "cos",
+      "createFont", "createGraphics", "createImage", "cursor", "curve",
+      "curveDetail", "curvePoint", "curveTangent", "curveTightness",
+      "curveVertex", "day", "defaultColor", "degrees", "directionalLight",
+      "disableContextMenu", "dist", "draw", "ellipse", "ellipseMode", "emissive",
+      "enableContextMenu", "endCamera", "endDraw", "endShape", "exit", "exp",
+      "expand", "externals", "fill", "filter", "filter_bilinear",
+      "filter_new_scanline", "float", "floor", "focused", "frameCount",
+      "frameRate", "frustum", "get", "glyphLook", "glyphTable", "green",
+      "HashMap", "height", "hex", "hint", "hour", "hue", "image", "imageMode",
+      "Import", "int", "intersect", "join", "key", "keyCode", "keyPressed",
+      "keyReleased", "keyTyped", "lerp", "lerpColor", "lightFalloff", "lights",
+      "lightSpecular", "line", "link", "loadBytes", "loadFont", "loadGlyphs",
+      "loadImage", "loadPixels", "loadShape", "loadStrings", "log", "loop",
+      "mag", "map", "match", "matchAll", "max", "millis", "min", "minute", "mix",
+      "modelX", "modelY", "modelZ", "modes", "month", "mouseButton",
+      "mouseClicked", "mouseDragged", "mouseMoved", "mousePressed",
+      "mouseReleased", "mouseScroll", "mouseScrolled", "mouseX", "mouseY",
+      "name", "nf", "nfc", "nfp", "nfs", "noCursor", "noFill", "noise",
+      "noiseDetail", "noiseSeed", "noLights", "noLoop", "norm", "normal",
+      "noSmooth", "noStroke", "noTint", "ortho", "peg", "perspective", "PImage",
+      "pixels", "PMatrix2D", "PMatrix3D", "PMatrixStack", "pmouseX", "pmouseY",
+      "point", "pointLight", "popMatrix", "popStyle", "pow", "print",
+      "printCamera", "println", "printMatrix", "printProjection", "PShape",
+      "pushMatrix", "pushStyle", "PVector", "quad", "radians", "random",
+      "Random", "randomSeed", "rect", "rectMode", "red", "redraw",
+      "requestImage", "resetMatrix", "reverse", "rotate", "rotateX", "rotateY",
+      "rotateZ", "round", "saturation", "save", "saveStrings", "scale",
+      "screenX", "screenY", "screenZ", "second", "set", "setup", "shape",
+      "shapeMode", "shared", "shininess", "shorten", "sin", "size", "smooth",
+      "sort", "specular", "sphere", "sphereDetail", "splice", "split",
+      "splitTokens", "spotLight", "sq", "sqrt", "status", "str", "stroke",
+      "strokeCap", "strokeJoin", "strokeWeight", "subset", "tan", "text",
+      "textAlign", "textAscent", "textDescent", "textFont", "textMode",
+      "textSize", "texture", "textureMode", "textWidth", "tint", "translate",
+      "triangle", "trim", "unbinary", "unhex", "updatePixels", "use3DContext",
+      "vertex", "width", "XMLElement", "year", "__frameRate", "__keyPressed",
+      "__mousePressed"];
+
+    var members = {};
+    var i, l;
+    for (i = 0, l = names.length; i < l ; ++i) {
+      members[names[i]] = null;
+    }
+    for (var lib in Processing.lib) {
+      if (Processing.lib.hasOwnProperty(lib)) {
+        if (Processing.lib[lib].exports) {
+          var exportedNames = Processing.lib[lib].exports;
+          for (i = 0, l = exportedNames.length; i < l; ++i) {
+           members[exportedNames[i]] = null;
+          }
+        }
+      }
+    }
+    return members;
+  }
+
+  // Parser starts
+  function parseProcessing(code) {
+    var globalMembers = getGlobalMembers();
+
+    function splitToAtoms(code) {
+      var atoms = [];
+      var items = code.split(/([\{\[\(\)\]\}])/);
+      var result = items[0];
+
+      var stack = [];
+      for(var i=1; i < items.length; i += 2) {
+        var item = items[i];
+        if(item === '[' || item === '{' || item === '(') {
+          stack.push(result); result = item;
+        } else if(item === ']' || item === '}' || item === ')') {
+          var kind = item === '}' ? 'A' : item === ')' ? 'B' : 'C';
+          var index = atoms.length; atoms.push(result + item);
+          result = stack.pop() + '"' + kind + (index + 1) + '"';
+        }
+        result += items[i + 1];
+      }
+      atoms.unshift(result);
+      return atoms;
+    }
+
+    function injectStrings(code, strings) {
+      return code.replace(/'(\d+)'/g, function(all, index) {
+        var val = strings[index];
+        if(val.charAt(0) === "/") {
+          return val;
+        } else {
+          return (/^'((?:[^'\\\n])|(?:\\.[0-9A-Fa-f]*))'$/).test(val) ? "(new processing.Character(" + val + "))" : val;
+        }
+      });
+    }
+
+    function trimSpaces(string) {
+      var m1 = /^\s*/.exec(string), result;
+      if(m1[0].length === string.length) {
+        result = {left: m1[0], middle: "", right: ""};
+      } else {
+        var m2 = /\s*$/.exec(string);
+        result = {left: m1[0], middle: string.substring(m1[0].length, m2.index), right: m2[0]};
+      }
+      result.untrim = function(t) { return this.left + t + this.right; };
+      return result;
+    }
+
+    function trim(string) {
+      return string.replace(/^\s+/,'').replace(/\s+$/,'');
+    }
+
+    function appendToLookupTable(table, array) {
+      for(var i=0,l=array.length;i<l;++i) {
+        table[array[i]] = null;
+      }
+      return table;
+    }
+
+    function isLookupTableEmpty(table) {
+      for(var i in table) {
+        if(table.hasOwnProperty(i)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    function getAtomIndex(templ) { return templ.substring(2, templ.length - 1); }
+
+    var codeWoExtraCr = code.replace(/\r\n?|\n\r/g, "\n");
+
+    var strings = [];
+    var codeWoStrings = codeWoExtraCr.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g,
+    function(all, quoted, aposed, regexCtx, prefix, regex, singleComment, comment) {
+      var index;
+      if(quoted || aposed) { // replace strings
+        index = strings.length; strings.push(all);
+        return "'" + index + "'";
+      } else if(regexCtx) { // replace RegExps
+        index = strings.length; strings.push(regex);
+        return prefix + "'" + index + "'";
+      } else { // kill comments
+        return comment !== "" ? " " : "\n";
+      }
+    });
+
+    var atoms = splitToAtoms(codeWoStrings);
+    var replaceContext;
+    var declaredClasses = {}, currentClassId, classIdSeed = 0;
+
+    function addAtom(text, type) {
+      var lastIndex = atoms.length;
+      atoms.push(text);
+      return '"' + type + lastIndex + '"';
+    }
+
+    function generateClassId() {
+      return "class" + (++classIdSeed);
+    }
+
+    function appendClass(class_, classId, scopeId) {
+      class_.classId = classId;
+      class_.scopeId = scopeId;
+      declaredClasses[classId] = class_;
+    }
+
+    // function defined below
+    var transformClassBody, transformStatementsBlock, transformStatements, transformMain, transformExpression;
+
+    var classesRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)(class|interface)\s+([A-Za-z_$][\w$]*\b)(\s+extends\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)?(\s+implements\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?\s*("A\d+")/g;
+    var methodsRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:else|new|return|throw|function|public|private|protected)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+"|;)/g;
+    var fieldTest = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:else|new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*(?:"C\d+"\s*)*([=,]|$)/;
+    var cstrsRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+")/g;
+    var attrAndTypeRegex = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*/;
+    var functionsRegex = /\bfunction(?:\s+([A-Za-z_$][\w$]*))?\s*("B\d+")\s*("A\d+")/g;
+
+    function extractClassesAndMethods(code) {
+      var s = code;
+      s = s.replace(classesRegex, function(all) {
+        return addAtom(all, 'E');
+      });
+      s = s.replace(methodsRegex, function(all) {
+        return addAtom(all, 'D');
+      });
+      s = s.replace(functionsRegex, function(all) {
+        return addAtom(all, 'H');
+      });
+      return s;
+    }
+
+    function extractConstructors(code, className) {
+      var result = code.replace(cstrsRegex, function(all, attr, name, params, throws_, body) {
+        if(name !== className) {
+          return all;
+        } else {
+          return addAtom(all, 'G');
+        }
+      });
+      return result;
+    }
+
+    function AstParam(name) {
+      this.name = name;
+    }
+    AstParam.prototype.toString = function() {
+      return this.name;
+    };
+    function AstParams(params) {
+      this.params = params;
+    }
+    AstParams.prototype.getNames = function() {
+      var names = [];
+      for(var i=0,l=this.params.length;i<l;++i) {
+        names.push(this.params[i].name);
+      }
+      return names;
+    };
+    AstParams.prototype.toString = function() {
+      if(this.params.length === 0) {
+        return "()";
+      }
+      var result = "(";
+      for(var i=0,l=this.params.length;i<l;++i) {
+        result += this.params[i] + ", ";
+      }
+      return result.substring(0, result.length - 2) + ")";
+    };
+
+    function transformParams(params) {
+      var paramsWoPars = trim(params.substring(1, params.length - 1));
+      var result = [];
+      if(paramsWoPars !== "") {
+        var paramList = paramsWoPars.split(",");
+        for(var i=0; i < paramList.length; ++i) {
+          var param = /\b([A-Za-z_$][\w$]*\b)\s*("[ABC][\d]*")?$/.exec(paramList[i]);
+          result.push(new AstParam(param[1]));
+        }
+      }
+      return new AstParams(result);
+    }
+
+    function preExpressionTransform(expr) {
+      var s = expr;
+      // new type[] {...} --> {...}
+      s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"C\d+")+\s*("A\d+")/g, function(all, type, init) {
+        return init;
+      });
+      // new Runnable() {...} --> "F???"
+      s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"B\d+")\s*("A\d+")/g, function(all, type, init) {
+        return addAtom(all, 'F');
+      });
+      // function(...) { } --> "H???"
+      s = s.replace(functionsRegex, function(all) {
+        return addAtom(all, 'H');
+      });
+      // new type[?] --> new ArrayList(?)
+      s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*("C\d+"(?:\s*"C\d+")*)/g, function(all, type, index) {
+        var args = index.replace(/"C(\d+)"/g, function(all, j) { return atoms[j]; }).
+          replace(/\[\s*\]/g, "[0]").replace(/\s*\]\s*\[\s*/g, ", ");
+
+        var arrayInitializer = "(" + args.substring(1, args.length - 1) + ")";
+        return 'new ArrayList' + addAtom(arrayInitializer, 'B');
+      });
+      // .length() --> .length
+      s = s.replace(/(\.\s*length)\s*"B\d+"/g, "$1");
+      // #000000 --> 0x000000
+      s = s.replace(/#([0-9A-Fa-f]{6})\b/g, function(all, digits) {
+        return "0xFF" + digits;
+      });
+      // delete (type)???, (int)??? -> 0|???
+      s = s.replace(/"B(\d+)"(\s*(?:[\w$']|"B))/g, function(all, index, next) {
+        var atom = atoms[index];
+        if(!/^\(\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\s*(?:"C\d+"\s*)*\)$/.test(atom)) {
+          return all;
+        } else if(/^\(\s*int\s*\)$/.test(atom)) {
+          return "0|" + next;
+        } else {
+          var indexParts = atom.split(/"C(\d+)"/g);
+          if(indexParts.length > 1) {
+            // even items contains atom numbers, can check only first
+            if(! /^\[\s*\]$/.test(atoms[indexParts[1]])) {
+              return all; // fallback - not a cast
+            }
+          }
+          return "" + next;
+        }
+      });
+      // super() -> $superCstr(), super. -> $super.;
+      s = s.replace(/\bsuper(\s*"B\d+")/g, "$$superCstr$1").replace(/\bsuper(\s*\.)/g, "$$super$1");
+      // 3.0f -> 3.0
+      s = s.replace(/\b(\.?\d+)[fF]/g, "$1");
+      // Weird (?) parsing errors with %
+      s = s.replace(/([^\s])%([^=\s])/g, "$1 % $2");
+      // Since frameRate() and frameRate are different things,
+      // we need to differentiate them somehow. So when we parse
+      // the Processing.js source, replace frameRate so it isn't
+      // confused with frameRate(), as well as keyPressed and mousePressed
+      s = s.replace(/\b(frameRate|keyPressed|mousePressed)\b(?!\s*"B)/g, "__$1");
+      // "pixels" replacements:
+      //   pixels[i] = c => pixels.setPixel(i,c) | pixels[i] => pixels.getPixel(i)
+      //   pixels.length => pixels.getLength()
+      //   pixels = ar => pixels.set(ar) | pixels => pixels.toArray()
+      s = s.replace(/\bpixels\s*(("C(\d+)")|\.length)?(\s*=(?!=)([^,\]\)\}\?\:]+))?/g,
+        function(all, indexOrLength, index, atomIndex, equalsPart, rightSide) {
+          if(index) {
+            var atom = atoms[atomIndex];
+            if(equalsPart) {
+              return "pixels.setPixel" + addAtom("(" +atom.substring(1, atom.length - 1) +
+                "," + rightSide + ")", 'B');
+            } else {
+              return "pixels.getPixel" + addAtom("(" + atom.substring(1, atom.length - 1) +
+                ")", 'B');
+            }
+          } else if(indexOrLength) {
+            // length
+            return "pixels.getLength" + addAtom("()", 'B');
+          } else {
+            if(equalsPart) {
+              return "pixels.set" + addAtom("(" + rightSide + ")", 'B');
+            } else {
+              return "pixels.toArray" + addAtom("()", 'B');
+            }
+          }
+        });
+      // this() -> $constr()
+      s = s.replace(/\bthis(\s*"B\d+")/g, "$$constr$1");
+
+      return s;
+    }
+
+    function AstInlineClass(baseInterfaceName, body) {
+      this.baseInterfaceName = baseInterfaceName;
+      this.body = body;
+      body.owner = this;
+    }
+    AstInlineClass.prototype.toString = function() {
+      return "new (function() {\n" + this.body + "})";
+    };
+
+    function transformInlineClass(class_) {
+      var m = new RegExp(/\bnew\s*(Runnable)\s*"B\d+"\s*"A(\d+)"/).exec(class_);
+      if(m === null) {
+        return "null";
+      } else {
+        var oldClassId = currentClassId, newClassId = generateClassId();
+        currentClassId = newClassId;
+        // only Runnable supported
+        var inlineClass = new AstInlineClass("Runnable", transformClassBody(atoms[m[2]], m[1]));
+        appendClass(inlineClass, newClassId, oldClassId);
+
+        currentClassId = oldClassId;
+        return inlineClass;
+      }
+    }
+
+    function AstFunction(name, params, body) {
+      this.name = name;
+      this.params = params;
+      this.body = body;
+    }
+    AstFunction.prototype.toString = function() {
+      var oldContext = replaceContext;
+      // saving "this." and parameters
+      var names = appendToLookupTable({"this":null}, this.params.getNames());
+      replaceContext = function(name) {
+        return names.hasOwnProperty(name) ? name : oldContext(name);
+      };
+      var result = "function";
+      if(this.name) {
+        result += " " + this.name;
+      }
+      result += this.params + " " + this.body;
+      replaceContext = oldContext;
+      return result;
+    };
+
+    function transformFunction(class_) {
+      var m = new RegExp(/\b([A-Za-z_$][\w$]*)\s*"B(\d+)"\s*"A(\d+)"/).exec(class_);
+      return new AstFunction( m[1] !== "function" ? m[1] : null,
+        transformParams(atoms[m[2]]), transformStatementsBlock(atoms[m[3]]));
+    }
+
+    function AstInlineObject(members) {
+      this.members = members;
+    }
+    AstInlineObject.prototype.toString = function() {
+      var oldContext = replaceContext;
+      replaceContext = function(name) {
+          return name === "this"? name : oldContext(name); // saving "this."
+      };
+      var result = "";
+      for(var i=0,l=this.members.length;i<l;++i) {
+        if(this.members[i].label) {
+          result += this.members[i].label + ": ";
+        }
+        result += this.members[i].value.toString() + ", ";
+      }
+      replaceContext = oldContext;
+      return result.substring(0, result.length - 2);
+    };
+
+    function transformInlineObject(obj) {
+      var members = obj.split(',');
+      for(var i=0; i < members.length; ++i) {
+        var label = members[i].indexOf(':');
+        if(label < 0) {
+          members[i] = { value: transformExpression(members[i]) };
+        } else {
+          members[i] = { label: trim(members[i].substring(0, label)),
+            value: transformExpression( trim(members[i].substring(label + 1)) ) };
+        }
+      }
+      return new AstInlineObject(members);
+    }
+
+    function expandExpression(expr) {
+      if(expr.charAt(0) === '(' || expr.charAt(0) === '[') {
+        return expr.charAt(0) + expandExpression(expr.substring(1, expr.length - 1)) + expr.charAt(expr.length - 1);
+      } else if(expr.charAt(0) === '{') {
+        if(/^\{\s*(?:[A-Za-z_$][\w$]*|'\d+')\s*:/.test(expr)) {
+          return "{" + addAtom(expr.substring(1, expr.length - 1), 'I') + "}";
+        } else {
+          return "[" + expandExpression(expr.substring(1, expr.length - 1)) + "]";
+        }
+      } else {
+        var trimmed = trimSpaces(expr);
+        var result = preExpressionTransform(trimmed.middle);
+        result = result.replace(/"[ABC](\d+)"/g, function(all, index) {
+          return expandExpression(atoms[index]);
+        });
+        return trimmed.untrim(result);
+      }
+    }
+
+    function replaceContextInVars(expr) {
+      return expr.replace(/(\.\s*)?(\b[A-Za-z_$][\w$]*\b)/g,
+        function(all, memberAccessSign, identifier) {
+          if(memberAccessSign) {
+            return all;
+          } else {
+            return replaceContext(identifier);
+          }
+        });
+    }
+
+    function AstExpression(expr, transforms) {
+      this.expr = expr;
+      this.transforms = transforms;
+    }
+    AstExpression.prototype.toString = function() {
+      var transforms = this.transforms;
+      var expr = replaceContextInVars(this.expr);
+      return expr.replace(/"!(\d+)"/g, function(all, index) {
+        return transforms[index].toString();
+      });
+    };
+
+    transformExpression = function(expr) {
+      var transforms = [];
+      var s = expandExpression(expr);
+      s = s.replace(/"H(\d+)"/g, function(all, index) {
+        transforms.push(transformFunction(atoms[index]));
+        return '"!' + (transforms.length - 1) + '"';
+      });
+      s = s.replace(/"F(\d+)"/g, function(all, index) {
+        transforms.push(transformInlineClass(atoms[index]));
+        return '"!' + (transforms.length - 1) + '"';
+      });
+      s = s.replace(/"I(\d+)"/g, function(all, index) {
+        transforms.push(transformInlineObject(atoms[index]));
+        return '"!' + (transforms.length - 1) + '"';
+      });
+
+      return new AstExpression(s, transforms);
+    };
+
+    function AstVarDefinition(name, value, isDefault) {
+      this.name = name;
+      this.value = value;
+      this.isDefault = isDefault;
+    }
+    AstVarDefinition.prototype.toString = function() {
+      return this.name + ' = ' + this.value;
+    };
+
+    function transformVarDefinition(def, defaultTypeValue) {
+      var eqIndex = def.indexOf("=");
+      var name, value, isDefault;
+      if(eqIndex < 0) {
+        name = def;
+        value = defaultTypeValue;
+        isDefault = true;
+      } else {
+        name = def.substring(0, eqIndex);
+        value = transformExpression(def.substring(eqIndex + 1));
+        isDefault = false;
+      }
+      return new AstVarDefinition( trim(name.replace(/(\s*"C\d+")+/g, "")),
+        value, isDefault);
+    }
+
+    function getDefaultValueForType(type) {
+        if(type === "int" || type === "float") {
+          return "0";
+        } else if(type === "boolean") {
+          return "false";
+        } else if(type === "color") {
+          return "0x00000000";
+        } else {
+          return "null";
+        }
+    }
+
+    function AstVar(definitions, varType) {
+      this.definitions = definitions;
+      this.varType = varType;
+    }
+    AstVar.prototype.getNames = function() {
+      var names = [];
+      for(var i=0,l=this.definitions.length;i<l;++i) {
+        names.push(this.definitions[i].name);
+      }
+      return names;
+    };
+    AstVar.prototype.toString = function() {
+      return "var " + this.definitions.join(",");
+    };
+    function AstStatement(expression) {
+      this.expression = expression;
+    }
+    AstStatement.prototype.toString = function() {
+      return this.expression.toString();
+    };
+
+    function transformStatement(statement) {
+      if(fieldTest.test(statement)) {
+        var attrAndType = attrAndTypeRegex.exec(statement);
+        var definitions = statement.substring(attrAndType[0].length).split(",");
+        var defaultTypeValue = getDefaultValueForType(attrAndType[2]);
+        for(var i=0; i < definitions.length; ++i) {
+          definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue);
+        }
+        return new AstVar(definitions, attrAndType[2]);
+      } else {
+        return new AstStatement(transformExpression(statement));
+      }
+    }
+
+    function AstForExpression(initStatement, condition, step) {
+      this.initStatement = initStatement;
+      this.condition = condition;
+      this.step = step;
+    }
+    AstForExpression.prototype.toString = function() {
+      return "(" + this.initStatement + "; " + this.condition + "; " + this.step + ")";
+    };
+
+    function AstForInExpression(initStatement, container) {
+      this.initStatement = initStatement;
+      this.container = container;
+    }
+    AstForInExpression.prototype.toString = function() {
+      var init = this.initStatement.toString();
+      if(init.indexOf("=") >= 0) { // can be without var declaration
+        init = init.substring(0, init.indexOf("="));
+      }
+      return "(" + init + " in " + this.container + ")";
+    };
+
+    function transformForExpression(expr) {
+      var content;
+      if(/\bin\b/.test(expr)) {
+        content = expr.substring(1, expr.length - 1).split(/\bin\b/g);
+        return new AstForInExpression( transformStatement(trim(content[0])),
+          transformExpression(content[1]));
+      } else {
+        content = expr.substring(1, expr.length - 1).split(";");
+        return new AstForExpression( transformStatement(trim(content[0])),
+          transformExpression(content[1]), transformExpression(content[2]));
+      }
+    }
+
+    function AstInnerInterface(name) {
+      this.name = name;
+    }
+    AstInnerInterface.prototype.toString = function() {
+      return  "this." + this.name + " = function " + this.name + "() { "+
+        "throw 'This is an interface'; };";
+    };
+    function AstInnerClass(name, body) {
+      this.name = name;
+      this.body = body;
+      body.owner = this;
+    }
+    AstInnerClass.prototype.toString = function() {
+      return "this." + this.name + " = function " + this.name + "() {\n" +
+        this.body + "};";
+    };
+
+    function transformInnerClass(class_) {
+      var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body
+      classesRegex.lastIndex = 0;
+      var body = atoms[getAtomIndex(m[6])];
+      if(m[2] === "interface") {
+        return new AstInnerInterface(m[3]);
+      } else {
+        var oldClassId = currentClassId, newClassId = generateClassId();
+        currentClassId = newClassId;
+        var innerClass = new AstInnerClass(m[3], transformClassBody(body, m[3], m[4], m[5]));
+        appendClass(innerClass, newClassId, oldClassId);
+        currentClassId = oldClassId;
+        return innerClass;
+      }
+    }
+
+    function AstClassMethod(name, params, body) {
+      this.name = name;
+      this.params = params;
+      this.body = body;
+    }
+    AstClassMethod.prototype.toString = function(){
+      var thisReplacement = replaceContext("this");
+      var paramNames = appendToLookupTable({}, this.params.getNames());
+      var oldContext = replaceContext;
+      replaceContext = function(name) {
+        return paramNames.hasOwnProperty(name) ? name : oldContext(name);
+      };
+      var result = "processing.addMethod(" + thisReplacement + ", '" + this.name + "', function " + this.params + " " +
+        this.body +");";
+      replaceContext = oldContext;
+      return result;
+    };
+
+    function transformClassMethod(method) {
+      var m = methodsRegex.exec(method);
+      methodsRegex.lastIndex = 0;
+      return new AstClassMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]),
+        transformStatementsBlock(atoms[getAtomIndex(m[6])]) );
+    }
+
+    function AstClassField(definitions, fieldType, isStatic) {
+      this.definitions = definitions;
+      this.fieldType = fieldType;
+      this.isStatic = isStatic;
+    }
+    AstClassField.prototype.getNames = function() {
+      var names = [];
+      for(var i=0,l=this.definitions.length;i<l;++i) {
+        names.push(this.definitions[i].name);
+      }
+      return names;
+    };
+    AstClassField.prototype.toString = function() {
+      var thisPrefix = replaceContext("this") + ".";
+      if(this.isStatic) {
+        var className = this.owner.name;
+        var staticDeclarations = [];
+        for(var i=0,l=this.definitions.length;i<l;++i) {
+          var definition = this.definitions[i];
+          var name = definition.name, staticName = className + "." + name;
+          var declaration = "if(" + staticName + " === void(0)) {\n" +
+            " " + staticName + " = " + definition.value + "; }\n" +
+            "processing.defineProperty(" + replaceContext("this") + ", " +
+            "'" + name + "', { get: function(){return " + staticName + ";}, " +
+            "set: function(val){" + staticName + " = val;} });\n";
+          staticDeclarations.push(declaration);
+        }
+        return staticDeclarations.join("");
+      } else {
+        return thisPrefix + this.definitions.join("; " + thisPrefix);
+      }
+    };
+
+    function transformClassField(statement) {
+      var attrAndType = attrAndTypeRegex.exec(statement);
+      var isStatic = attrAndType[1].indexOf("static") >= 0;
+      var definitions = statement.substring(attrAndType[0].length).split(/,\s*/g);
+      var defaultTypeValue = getDefaultValueForType(attrAndType[2]);
+      for(var i=0; i < definitions.length; ++i) {
+        definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue);
+      }
+      return new AstClassField(definitions, attrAndType[2], isStatic);
+    }
+
+    function AstConstructor(params, body) {
+      this.params = params;
+      this.body = body;
+    }
+    AstConstructor.prototype.toString = function() {
+      var paramNames = appendToLookupTable({}, this.params.getNames());
+      var oldContext = replaceContext;
+      replaceContext = function(name) {
+        return paramNames.hasOwnProperty(name) ? name : oldContext(name);
+      };
+      var prefix = "function $constr_" + this.params.params.length + this.params.toString();
+      var body = this.body.toString();
+      if(!/\$(superCstr|constr)\b/.test(body)) {
+        body = "{\n$superCstr();\n" + body.substring(1);
+      }
+      replaceContext = oldContext;
+      return prefix + body + "\n";
+    };
+
+    function transformConstructor(cstr) {
+      var m = new RegExp(/"B(\d+)"\s*"A(\d+)"/).exec(cstr);
+      var params = transformParams(atoms[m[1]]);
+
+      return new AstConstructor(params, transformStatementsBlock(atoms[m[2]]));
+    }
+
+    function AstClassBody(name, baseClassName, functions, methods, fields, cstrs, innerClasses, misc) {
+      var i,l;
+      this.name = name;
+      this.baseClassName = baseClassName;
+      this.functions = functions;
+      this.methods = methods;
+      this.fields = fields;
+      this.cstrs = cstrs;
+      this.innerClasses = innerClasses;
+      this.misc = misc;
+      for(i=0,l=fields.length; i<l; ++i) {
+        fields[i].owner = this;
+      }
+    }
+    AstClassBody.prototype.getMembers = function() {
+      var members;
+      if(this.owner.base) {
+        members = this.owner.base.body.getMembers();
+      } else {
+        members = { fields: [], methods: [], innerClasses: [] };
+      }
+      var i, j, l, m;
+      for(i=0,l=this.fields.length;i<l;++i) {
+        members.fields = members.fields.concat(this.fields[i].getNames());
+      }
+      for(i=0,l=this.methods.length;i<l;++i) {
+        var method = this.methods[i];
+        members.methods.push(method.name);
+      }
+      for(i=0,l=this.innerClasses.length;i<l;++i) {
+        var innerClass = this.innerClasses[i];
+        members.innerClasses.push(innerClass.name);
+      }
+      return members;
+    };
+    AstClassBody.prototype.toString = function() {
+      function getScopeLevel(p) {
+        var i = 0;
+        while(p) {
+          ++i;
+          p=p.scope;
+        }
+        return i;
+      }
+
+      var scopeLevel = getScopeLevel(this.owner);
+
+      var selfId = "$this_" + scopeLevel;
+      var result = "var " + selfId + " = this;\n";
+
+      var members = this.getMembers();
+      var thisClassFields = appendToLookupTable({}, members.fields),
+        thisClassMethods = appendToLookupTable({}, members.methods),
+        thisClassInners = appendToLookupTable({}, members.innerClasses);
+
+      var oldContext = replaceContext;
+      replaceContext = function(name) {
+        if(name === "this") {
+          return selfId;
+        } else if(thisClassFields.hasOwnProperty(name) || thisClassInners.hasOwnProperty(name)) {
+          return selfId + "." + name;
+        } else if(thisClassMethods.hasOwnProperty(name)) {
+          return "this." + name;
+        }
+        return oldContext(name);
+      };
+
+      if(this.baseClassName) {
+        result += "var $super = {};\n";
+        result += "function $superCstr(){\n" +
+                        this.baseClassName + ".prototype.constructor.apply($super, arguments);\n" +
+                        "processing.extendClass(" + selfId + ", $super); }\n";
+      } else {
+        result += "function $superCstr() { }\n";
+      }
+
+      result += this.functions.join('\n') + '\n';
+      result += this.innerClasses.join('\n');
+
+      result += this.fields.join(";\n") + ";\n";
+      result += this.methods.join('\n') + '\n';
+      result += this.misc.tail;
+
+      result += this.cstrs.join('\n') + '\n';
+
+      result += "function $constr() {\n";
+      var cstrsIfs = [];
+      for(var i=0,l=this.cstrs.length;i<l;++i) {
+        var paramsLength = this.cstrs[i].params.params.length;
+        cstrsIfs.push("if(arguments.length === " + paramsLength + ") { " +
+          "$constr_" + paramsLength + ".apply(" + selfId + ", arguments); }");
+      }
+      if(cstrsIfs.length > 0) {
+        result += cstrsIfs.join(" else ") + " else ";
+      }
+      // ??? add check if length is 0, otherwise fail
+      result += "$superCstr(); }\n";
+      result += "$constr.apply(null, arguments);\n";
+
+      replaceContext = oldContext;
+      return result;
+    };
+
+    transformClassBody = function(body, name, baseName, impls) {
+      var declarations = body.substring(1, body.length - 1);
+      declarations = extractClassesAndMethods(declarations);
+      declarations = extractConstructors(declarations, name);
+      var methods = [], classes = [], cstrs = [], functions = [];
+      declarations = declarations.replace(/"([DEGH])(\d+)"/g, function(all, type, index) {
+        if(type === 'D') { methods.push(index); }
+        else if(type === 'E') { classes.push(index); }
+        else if(type === 'H') { functions.push(index); }
+        else { cstrs.push(index); }
+        return "";
+      });
+      var fields = declarations.split(';');
+      var baseClassName;
+      var i;
+
+      if(baseName !== undef) {
+        baseClassName = baseName.replace(/^\s*extends\s+([A-Za-z_$][\w$]*)\s*$/g, "$1");
+      }
+
+      for(i = 0; i < functions.length; ++i) {
+        functions[i] = transformFunction(atoms[functions[i]]);
+      }
+      for(i = 0; i < methods.length; ++i) {
+        methods[i] = transformClassMethod(atoms[methods[i]]);
+      }
+      for(i = 0; i < fields.length - 1; ++i) {
+        var field = trimSpaces(fields[i]);
+        fields[i] = transformClassField(field.middle);
+      }
+      var tail = fields.pop();
+      for(i = 0; i < cstrs.length; ++i) {
+        cstrs[i] = transformConstructor(atoms[cstrs[i]]);
+      }
+      for(i = 0; i < classes.length; ++i) {
+        classes[i] = transformInnerClass(atoms[classes[i]]);
+      }
+
+      return new AstClassBody(name, baseClassName, functions, methods, fields, cstrs,
+        classes, { tail: tail });
+    };
+
+    function AstInterface(name) {
+      this.name = name;
+    }
+    AstInterface.prototype.toString = function() {
+      return "function " + this.name + "() {  throw 'This is an interface'; }\n" +
+        "processing." + this.name + " = " + this.name + ";";
+    };
+    function AstClass(name, body) {
+      this.name = name;
+      this.body = body;
+      body.owner = this;
+    }
+    AstClass.prototype.toString = function() {
+      var staticVars = "";
+      for (var i = 0, l = this.body.fields.length; i < l; i++) {
+        if (this.body.fields[i].isStatic) {
+          for (var x = 0, xl = this.body.fields[i].definitions.length; x < xl; x++) {
+            staticVars += "var " + this.body.fields[i].definitions[x].name + " = " + this.body.name + "." + this.body.fields[i].definitions[x] + ";";
+          }
+        }
+      }
+      return "function " + this.name + "() {\n" + this.body + "}\n" +
+        staticVars + "\n" +
+        "processing." + this.name + " = " + this.name + ";";
+    };
+
+
+    function transformGlobalClass(class_) {
+      var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body
+      classesRegex.lastIndex = 0;
+      var body = atoms[getAtomIndex(m[6])];
+      if(m[2] === "interface") {
+        return new AstInterface(m[3]);
+      } else {
+        var oldClassId = currentClassId, newClassId = generateClassId();
+        currentClassId = newClassId;
+        var globalClass = new AstClass(m[3], transformClassBody(body, m[3], m[4], m[5]) );
+        appendClass(globalClass, newClassId, oldClassId);
+
+        currentClassId = oldClassId;
+        return globalClass;
+      }
+    }
+
+    function AstMethod(name, params, body) {
+      this.name = name;
+      this.params = params;
+      this.body = body;
+    }
+    AstMethod.prototype.toString = function(){
+      var paramNames = appendToLookupTable({}, this.params.getNames());
+      var oldContext = replaceContext;
+      replaceContext = function(name) {
+        return paramNames.hasOwnProperty(name) ? name : oldContext(name);
+      };
+      var result = "function " + this.name + this.params + " " + this.body + "\n" +
+        "processing." + this.name + " = " + this.name + ";";
+      replaceContext = oldContext;
+      return result;
+    };
+
+    function transformGlobalMethod(method) {
+      var m = methodsRegex.exec(method);
+      var result =
+      methodsRegex.lastIndex = 0;
+      return new AstMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]),
+        transformStatementsBlock(atoms[getAtomIndex(m[6])]));
+    }
+
+    function preStatementsTransform(statements) {
+      var s = statements;
+      s = s.replace(/\b(catch\s*"B\d+"\s*"A\d+")(\s*catch\s*"B\d+"\s*"A\d+")+/g, "$1");
+      return s;
+    }
+
+    function AstForStatement(argument, misc) {
+      this.argument = argument;
+      this.misc = misc;
+    }
+    AstForStatement.prototype.toString = function() {
+      return this.misc.prefix + this.argument.toString();
+    };
+    function AstCatchStatement(argument, misc) {
+      this.argument = argument;
+      this.misc = misc;
+    }
+    AstCatchStatement.prototype.toString = function() {
+      return this.misc.prefix + this.argument.toString();
+    };
+    function AstPrefixStatement(name, argument, misc) {
+      this.name = name;
+      this.argument = argument;
+      this.misc = misc;
+    }
+    AstPrefixStatement.prototype.toString = function() {
+      var result = this.misc.prefix;
+      if(this.argument !== undef) {
+        result += this.argument.toString();
+      }
+      return result;
+    };
+    function AstLabel(label) {
+      this.label = label;
+    }
+    AstLabel.prototype.toString = function() {
+      return this.label;
+    };
+
+    transformStatements = function(statements, transformMethod, transformClass) {
+      var nextStatement = new RegExp(/\b(catch|for|if|switch|while|with)\s*"B(\d+)"|\b(do|else|finally|return|throw|try|break|continue)\b|("[ADEH](\d+)")|\b((?:case\s[^:]+|[A-Za-z_$][\w$]*\s*):)|(;)/g);
+      var res = [];
+      statements = preStatementsTransform(statements);
+      var lastIndex = 0, m, space;
+      while((m = nextStatement.exec(statements)) !== null) {
+        if(m[1] !== undef) { // catch, for ...
+          var i = statements.lastIndexOf('"B', nextStatement.lastIndex);
+          var statementsPrefix = statements.substring(lastIndex, i);
+          if(m[1] === "for") {
+            res.push(new AstForStatement(transformForExpression(atoms[m[2]]),
+              { prefix: statementsPrefix }) );
+          } else if(m[1] === "catch") {
+            res.push(new AstCatchStatement(transformParams(atoms[m[2]]),
+              { prefix: statementsPrefix }) );
+          } else {
+            res.push(new AstPrefixStatement(m[1], transformExpression(atoms[m[2]]),
+              { prefix: statementsPrefix }) );
+          }
+        } else if(m[3] !== undef) { // do, else, ...
+            res.push(new AstPrefixStatement(m[3], undef,
+              { prefix: statements.substring(lastIndex, nextStatement.lastIndex) }) );
+        } else if(m[4] !== undef) { // block, class and methods
+          space = statements.substring(lastIndex, nextStatement.lastIndex - m[4].length);
+          if(trim(space).length !== 0) { continue; } // avoiding new type[] {} construct
+          res.push(space);
+          var kind = m[4].charAt(1), atomIndex = m[5];
+          if(kind === 'D') {
+            res.push(transformMethod(atoms[atomIndex]));
+          } else if(kind === 'E') {
+            res.push(transformClass(atoms[atomIndex]));
+          } else if(kind === 'H') {
+            res.push(transformFunction(atoms[atomIndex]));
+          } else {
+            res.push(transformStatementsBlock(atoms[atomIndex]));
+          }
+        } else if(m[6] !== undef) { // label
+          space = statements.substring(lastIndex, nextStatement.lastIndex - m[6].length);
+          if(trim(space).length !== 0) { continue; } // avoiding ?: construct
+          res.push(new AstLabel(statements.substring(lastIndex, nextStatement.lastIndex)) );
+        } else { // semicolon
+          var statement = trimSpaces(statements.substring(lastIndex, nextStatement.lastIndex - 1));
+          res.push(statement.left);
+          res.push(transformStatement(statement.middle));
+          res.push(statement.right + ";");
+        }
+        lastIndex = nextStatement.lastIndex;
+      }
+      var statementsTail = trimSpaces(statements.substring(lastIndex));
+      res.push(statementsTail.left);
+      if(statementsTail.middle !== "") {
+        res.push(transformStatement(statementsTail.middle));
+        res.push(";" + statementsTail.right);
+      }
+      return res;
+    };
+
+    function getLocalNames(statements) {
+      var localNames = [];
+      for(var i=0,l=statements.length;i<l;++i) {
+        var statement = statements[i];
+        if(statement instanceof AstVar) {
+          localNames = localNames.concat(statement.getNames());
+        } else if(statement instanceof AstForStatement &&
+          statement.argument.initStatement instanceof AstVar) {
+          localNames = localNames.concat(statement.argument.initStatement.getNames());
+        } else if(statement instanceof AstInnerInterface || statement instanceof AstInnerClass ||
+          statement instanceof AstInterface || statement instanceof AstClass ||
+          statement instanceof AstMethod || statement instanceof AstFunction) {
+          localNames.push(statement.name);
+        }
+      }
+      return appendToLookupTable({}, localNames);
+    }
+
+    function AstStatementsBlock(statements) {
+      this.statements = statements;
+    }
+    AstStatementsBlock.prototype.toString = function() {
+      var localNames = getLocalNames(this.statements);
+      var oldContext = replaceContext;
+
+      // replacing context only when necessary
+      if(!isLookupTableEmpty(localNames)) {
+        replaceContext = function(name) {
+          return localNames.hasOwnProperty(name) ? name : oldContext(name);
+        };
+      }
+
+      var result = "{\n" + this.statements.join('') + "\n}";
+      replaceContext = oldContext;
+      return result;
+    };
+
+    transformStatementsBlock = function(block) {
+      var content = trimSpaces(block.substring(1, block.length - 1));
+      return new AstStatementsBlock(transformStatements(content.middle));
+    };
+
+    function AstRoot(statements) {
+      this.statements = statements;
+    }
+    AstRoot.prototype.toString = function() {
+      var localNames = getLocalNames(this.statements);
+      replaceContext = function(name) {
+        if(localNames.hasOwnProperty(name)) {
+          return name;
+        } else if(globalMembers.hasOwnProperty(name)) {
+          return "processing." + name;
+        } else if(PConstants.hasOwnProperty(name)) {
+          return "$constants." + name;
+        }
+        return name;
+      };
+      var result = "// this code was autogenerated from PJS\n" +
+        "(function(processing, $constants) {\n" +
+        this.statements.join('') + "\n})";
+      replaceContext = null;
+      return result;
+    };
+
+    transformMain = function() {
+      var statements = extractClassesAndMethods(atoms[0]);
+      statements = statements.replace(/\bimport\s+[^;]+;/g, "");
+      return new AstRoot( transformStatements(statements,
+        transformGlobalMethod, transformGlobalClass) );
+    };
+
+    function generateMetadata(ast) {
+      var globalScope = {};
+      var id, class_;
+      for(id in declaredClasses) {
+        if(declaredClasses.hasOwnProperty(id)) {
+          class_ = declaredClasses[id];
+          var scopeId = class_.scopeId, name = class_.name;
+          if(scopeId) {
+            var scope = declaredClasses[scopeId];
+            class_.scope = scope;
+            if(scope.inScope === undef) {
+              scope.inScope = {};
+            }
+            scope.inScope[name] = class_;
+          } else {
+            globalScope[name] = class_;
+          }
+        }
+      }
+
+      function findInScopes(class_, name) {
+        var parts = name.split('.');
+        var currentScope = class_.scope, found;
+        while(currentScope) {
+          if(currentScope.hasOwnProperty(parts[0])) {
+            found = currentScope[parts[0]]; break;
+          }
+          currentScope = currentScope.scope;
+        }
+        if(found === undef) {
+          found = globalScope[parts[0]];
+        }
+        for(var i=1,l=parts.length;i<l && found;++i) {
+          found = found.inScope[parts[i]];
+        }
+        return found;
+      }
+
+      for(id in declaredClasses) {
+        if(declaredClasses.hasOwnProperty(id)) {
+          class_ = declaredClasses[id];
+          var baseClassName = class_.body.baseClassName;
+          if(baseClassName) {
+            class_.base = findInScopes(class_, baseClassName);
+          }
+        }
+      }
+    }
+
+    var transformed = transformMain();
+    generateMetadata(transformed);
+
+    // remove empty extra lines with space
+    var redendered = transformed.toString();
+    redendered = redendered.replace(/\s*\n(?:[\t ]*\n)+/g, "\n\n");
+
+    return injectStrings(redendered, strings);
+  }// Parser ends
+
+  function preprocessCode(aCode, sketch) {
+    // Parse out @pjs directive, if any.
+    var dm = new RegExp(/\/\*\s*@pjs\s+((?:[^\*]|\*+[^\*\/])*)\*\//g).exec(aCode);
+    if (dm && dm.length === 2) {
+      // masks contents of a JSON to be replaced later
+      // to protect the contents from further parsing
+      var jsonItems = [],
+          directives = dm.splice(1, 2)[0].replace(/\{([\s\S]*?)\}/g, (function() {
+            return function(all, item) {
+              jsonItems.push(item);
+              return "{" + (jsonItems.length-1) + "}";
+            };
+          }())).replace('\n', '').replace('\r', '').split(";");
+
+      // We'll L/RTrim, and also remove any surrounding double quotes (e.g., just take string contents)
+      var clean = function(s) {
+        return s.replace(/^\s*["']?/, '').replace(/["']?\s*$/, '');
+      };
+
+      for (var i = 0, dl = directives.length; i < dl; i++) {
+        var pair = directives[i].split('=');
+        if (pair && pair.length === 2) {
+          var key = clean(pair[0]),
+              value = clean(pair[1]),
+              list = [];
+          // A few directives require work beyond storying key/value pairings
+          if (key === "preload") {
+            list = value.split(',');
+            // All pre-loaded images will get put in imageCache, keyed on filename
+            for (var j = 0, jl = list.length; j < jl; j++) {
+              var imageName = clean(list[j]);
+              sketch.imageCache.add(imageName);
+            }
+          } else if (key === "transparent") {
+            sketch.options.isTransparent = value === "true";
+          // fonts can be declared as a string containing a url,
+          // or a JSON object, containing a font name, and a url
+          } else if (key === "font") {
+            list = value.split(",");
+            for (var x = 0, xl = list.length; x < xl; x++) {
+              var fontName = clean(list[x]),
+                  index = /^\{(\d*?)\}$/.exec(fontName);
+              // if index is not null, send JSON, otherwise, send string
+              sketch.fonts.add(index ? JSON.parse("{" + jsonItems[index[1]] + "}") : fontName);
+            }
+          } else if (key === "crisp") {
+            sketch.options.crispLines = value === "true";
+          } else if (key === "pauseOnBlur") {
+            sketch.options.pauseOnBlur = value === "true";
+          } else {
+            sketch.options[key] = value;
+          }
+        }
+      }
+    }
+
+    // Check if 3D context is invoked -- this is not the best way to do this.
+    var codeWoStrings = aCode.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g, "");
+    if (codeWoStrings.match(/\bsize\((?:.+),(?:.+),\s*(OPENGL|P3D)\s*\);/)) {
+      sketch.use3DContext = true;
+    }
+    return aCode;
+  }
+
+  // Parse/compiles Processing (Java-like) syntax to JavaScript syntax
+  Processing.compile = function(pdeCode) {
+    var sketch = new Processing.Sketch();
+    var code = preprocessCode(pdeCode, sketch);
+    var compiledPde = parseProcessing(code);
+    sketch.sourceCode = compiledPde;
+    return sketch;
+  };
+
+  Error.prototype.printStackTrace = function() {
+     return this.toString();
+  };
+
+  Processing.version = "0.9.7";
+
+  // Share lib space
+  Processing.lib = {};
+
+  // Store Processing instances
+  Processing.instances = [];
+  Processing.instanceIds = {};
+
+  Processing.removeInstance = function(id) {
+    Processing.instances.splice(Processing.instanceIds[id], 1);
+    delete Processing.instanceIds[id];
+  };
+
+  Processing.addInstance = function(processing) {
+    if (processing.externals.canvas.id === undef || !processing.externals.canvas.id.length) {
+      processing.externals.canvas.id = "__processing" + Processing.instances.length;
+    }
+    Processing.instanceIds[processing.externals.canvas.id] = Processing.instances.length;
+    Processing.instances.push(processing);
+  };
+
+  Processing.getInstanceById = function(name) {
+    return Processing.instances[Processing.instanceIds[name]];
+  };
+
+  Processing.Sketch = function(attachFunction) {
+    this.attachFunction = attachFunction; // can be optional
+    this.use3DContext = false;
+    this.options = {
+      isTransparent: false,
+      crispLines: false,
+      pauseOnBlur: false
+    };
+    this.imageCache = {
+      pending: 0,
+      images: {},
+      add: function(href) {
+        var img = new Image();
+        img.onload = (function(owner) {
+          return function() {
+            owner.pending--;
+          };
+        }(this));
+        this.pending++;
+        this.images[href] = img;
+        img.src = href;
+      }
+    };
+    this.fonts = {
+      // template element used to compare font sizes
+      template: (function() {
+        var element = document.createElement('p');
+        element.style.fontFamily = "serif";
+        element.style.fontSize = "72px";
+        element.style.visibility = "hidden";
+        element.innerHTML = "abcmmmmmmmmmmlll";
+        document.getElementsByTagName("body")[0].appendChild(element);
+        return element;
+      }()),
+      // number of attempts to load a font
+      attempt: 0,
+      // returns true is fonts are all loaded,
+      // true if number of attempts hits the limit,
+      // false otherwise
+      pending: function() {
+        var r = true;
+        for (var i = 0; i < this.fontList.length; i++) {
+          // compares size of text in pixels, if equal, custom font is not yet loaded
+          if (this.fontList[i].offsetWidth === this.template.offsetWidth && this.fontList[i].offsetHeight === this.template.offsetHeight) {
+            r = false;
+            this.attempt++;
+          } else {
+            // removes loaded font from the array and dom, so we don't compare it again
+            document.getElementsByTagName("body")[0].removeChild(this.fontList[i]);
+            this.fontList.splice(i--, 1);
+            this.attempt = 0;
+          }
+        }
+        // give up loading after max attempts have been reached
+        if (this.attempt >= 30) {
+          r = true;
+          // remove remaining elements from the dom and array
+          for (var j = 0; j < this.fontList.length; j++) {
+            document.getElementsByTagName("body")[0].removeChild(this.fontList[j]);
+            this.fontList.splice(j--, 1);
+          }
+        }
+        // Remove the template element from the dom once done comparing
+        if (r) {
+          document.getElementsByTagName("body")[0].removeChild(this.template);
+        }
+        return r;
+      },
+      // fontList contains elements to compare font sizes against a template
+      fontList: [],
+      // string containing a css @font-face list of custom fonts
+      fontFamily: "",
+      // style element to hold the @font-face string
+      style: document.createElement('style'),
+      // adds a font to the font cache
+      // creates an element using the font, to start loading the font,
+      // and compare against a default font to see if the custom font is loaded
+      add: function(fontSrc) {
+        // fontSrc can be a string or a JSON object
+        // string contains a url to a font
+        // JSON object would contain a name and a url
+        // acceptable fonts are .ttf, .otf, and a data uri
+        var fontName = (typeof fontSrc === 'object' ? fontSrc.fontFace : fontSrc),
+            fontUrl = (typeof fontSrc === 'object' ? fontSrc.url : fontSrc);
+        // creating the @font-face style
+        this.fontFamily += "@font-face{\n  font-family: '" + fontName + "';\n  src:  url('" + fontUrl + "');\n}\n";
+        this.style.innerHTML = this.fontFamily;
+        document.getElementsByTagName("head")[0].appendChild(this.style);
+        // creating the element to load, and compare the new font
+        var preLoader = document.createElement('p');
+        preLoader.style.fontFamily = "'" + fontName + "', serif";
+        preLoader.style.fontSize = "72px";
+        preLoader.style.visibility = "hidden";
+        preLoader.innerHTML = "abcmmmmmmmmmmlll";
+        document.getElementsByTagName("body")[0].appendChild(preLoader);
+        this.fontList.push(preLoader);
+      }
+    };
+    this.sourceCode = undefined;
+    this.attach = function(processing, constants) {
+      // either attachFunction or sourceCode must be present on attach
+      if(typeof this.attachFunction === "function") {
+        this.attachFunction(processing, constants);
+      } else if(this.sourceCode) {
+        var func = eval(this.sourceCode);
+        func(processing, constants);
+        this.attachFunction = func;
+      } else {
+        throw "Unable to attach sketch to the processing instance";
+      }
+    };
+    this.toString = function() {
+      return this.sourceCode || "[attach: " + this.attachFunction + "]";
+    };
+    this.onblur = function() {};
+    this.onfocus = function() {};
+  };
+
+  // Automatic Initialization Method
+  var init = function() {
+    var canvas = document.getElementsByTagName('canvas');
+
+    for (var i = 0, l = canvas.length; i < l; i++) {
+      // datasrc and data-src are deprecated.
+      var processingSources = canvas[i].getAttribute('data-processing-sources');
+      if (processingSources === null) {
+        // Temporary fallback for datasrc and data-src
+        processingSources = canvas[i].getAttribute('data-src');
+        if (processingSources === null) {
+          processingSources = canvas[i].getAttribute('datasrc');
+        }
+      }
+      if (processingSources) {
+        // The problem: if the HTML canvas dimensions differ from the
+        // dimensions specified in the size() call in the sketch, for
+        // 3D sketches, browsers will either not render or render the
+        // scene incorrectly. To fix this, we need to adjust the attributes
+        // of the canvas width and height.
+        // Get the source, we'll need to find what the user has used in size()
+        var filenames = processingSources.split(' ');
+        var code = "";
+        for (var j = 0, fl = filenames.length; j < fl; j++) {
+          if (filenames[j]) {
+            var block = ajax(filenames[j]);
+            if (block !== false) {
+              code += ";\n" + block;
+            }
+          }
+        }
+        Processing.addInstance(new Processing(canvas[i], code));
+      }
+    }
+  };
+
+  document.addEventListener('DOMContentLoaded', function() {
+    init();
+  }, false);
+
+  // pauseOnBlur handling
+  window.addEventListener('blur', function() {
+    for (var i = 0; i < Processing.instances.length; i++) {
+      Processing.instances[i].externals.onblur();
+    }
+  }, false);
+
+  window.addEventListener('focus', function() {
+    for (var i = 0; i < Processing.instances.length; i++) {
+      Processing.instances[i].externals.onfocus();
+    }
+  }, false);
+
+}());
diff --git a/lib/processing-0.9.7.min.js b/lib/processing-0.9.7.min.js
new file mode 100644 (file)
index 0000000..ebfa31e
--- /dev/null
@@ -0,0 +1 @@
+(function(){var undef;var ajax=function ajax(url){var xhr=new XMLHttpRequest();xhr.open("GET",url,false);xhr.setRequestHeader("If-Modified-Since","Fri, 1 Jan 1960 00:00:00 GMT");xhr.send(null);if(xhr.status!==200&&xhr.status!==0){throw ("XMLHttpRequest failed, status code "+xhr.status)}return xhr.responseText};function fixReplaceByRegExp(){var re=/t/g;if("t".replace(re,"")!==null&&re.exec("t")){return}var _ie_replace=String.prototype.replace;String.prototype.replace=function(searchValue,repaceValue){var result=_ie_replace.apply(this,arguments);if(searchValue instanceof RegExp&&searchValue.global){searchValue.lastIndex=0}return result}}function fixMatchByRegExp(){var re=/t/g;if("t".match(re)!==null&&re.exec("t")){return}var _ie_match=String.prototype.match;String.prototype.match=function(searchValue){var result=_ie_match.apply(this,arguments);if(searchValue instanceof RegExp&&searchValue.global){searchValue.lastIndex=0}return result}}fixReplaceByRegExp();fixMatchByRegExp();(function fixOperaCreateImageData(){try{if(!("createImageData" in CanvasRenderingContext2D.prototype)){CanvasRenderingContext2D.prototype.createImageData=function(sw,sh){return new ImageData(sw,sh)}}}catch(e){}}());var PConstants={X:0,Y:1,Z:2,R:3,G:4,B:5,A:6,U:7,V:8,NX:9,NY:10,NZ:11,EDGE:12,SR:13,SG:14,SB:15,SA:16,SW:17,TX:18,TY:19,TZ:20,VX:21,VY:22,VZ:23,VW:24,AR:25,AG:26,AB:27,DR:3,DG:4,DB:5,DA:6,SPR:28,SPG:29,SPB:30,SHINE:31,ER:32,EG:33,EB:34,BEEN_LIT:35,VERTEX_FIELD_COUNT:36,P2D:1,JAVA2D:1,WEBGL:2,P3D:2,OPENGL:2,PDF:0,DXF:0,OTHER:0,WINDOWS:1,MAXOSX:2,LINUX:3,EPSILON:0.0001,MAX_FLOAT:3.4028235e+38,MIN_FLOAT:-3.4028235e+38,MAX_INT:2147483647,MIN_INT:-2147483648,PI:Math.PI,TWO_PI:2*Math.PI,HALF_PI:Math.PI/2,THIRD_PI:Math.PI/3,QUARTER_PI:Math.PI/4,DEG_TO_RAD:Math.PI/180,RAD_TO_DEG:180/Math.PI,WHITESPACE:" \t\n\r\f\u00A0",RGB:1,ARGB:2,HSB:3,ALPHA:4,CMYK:5,TIFF:0,TARGA:1,JPEG:2,GIF:3,BLUR:11,GRAY:12,INVERT:13,OPAQUE:14,POSTERIZE:15,THRESHOLD:16,ERODE:17,DILATE:18,REPLACE:0,BLEND:1<<0,ADD:1<<1,SUBTRACT:1<<2,LIGHTEST:1<<3,DARKEST:1<<4,DIFFERENCE:1<<5,EXCLUSION:1<<6,MULTIPLY:1<<7,SCREEN:1<<8,OVERLAY:1<<9,HARD_LIGHT:1<<10,SOFT_LIGHT:1<<11,DODGE:1<<12,BURN:1<<13,ALPHA_MASK:4278190080,RED_MASK:16711680,GREEN_MASK:65280,BLUE_MASK:255,CUSTOM:0,ORTHOGRAPHIC:2,PERSPECTIVE:3,POINT:2,POINTS:2,LINE:4,LINES:4,TRIANGLE:8,TRIANGLES:9,TRIANGLE_STRIP:10,TRIANGLE_FAN:11,QUAD:16,QUADS:16,QUAD_STRIP:17,POLYGON:20,PATH:21,RECT:30,ELLIPSE:31,ARC:32,SPHERE:40,BOX:41,GROUP:0,PRIMITIVE:1,GEOMETRY:3,VERTEX:0,BEZIER_VERTEX:1,CURVE_VERTEX:2,BREAK:3,CLOSESHAPE:4,OPEN:1,CLOSE:2,CORNER:0,CORNERS:1,RADIUS:2,CENTER_RADIUS:2,CENTER:3,DIAMETER:3,CENTER_DIAMETER:3,BASELINE:0,TOP:101,BOTTOM:102,NORMAL:1,NORMALIZED:1,IMAGE:2,MODEL:4,SHAPE:5,SQUARE:"butt",ROUND:"round",PROJECT:"square",MITER:"miter",BEVEL:"bevel",AMBIENT:0,DIRECTIONAL:1,SPOT:3,BACKSPACE:8,TAB:9,ENTER:10,RETURN:13,ESC:27,DELETE:127,CODED:65535,SHIFT:16,CONTROL:17,ALT:18,UP:38,RIGHT:39,DOWN:40,LEFT:37,ARROW:"default",CROSS:"crosshair",HAND:"pointer",MOVE:"move",TEXT:"text",WAIT:"wait",NOCURSOR:"url('data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='), auto",DISABLE_OPENGL_2X_SMOOTH:1,ENABLE_OPENGL_2X_SMOOTH:-1,ENABLE_OPENGL_4X_SMOOTH:2,ENABLE_NATIVE_FONTS:3,DISABLE_DEPTH_TEST:4,ENABLE_DEPTH_TEST:-4,ENABLE_DEPTH_SORT:5,DISABLE_DEPTH_SORT:-5,DISABLE_OPENGL_ERROR_REPORT:6,ENABLE_OPENGL_ERROR_REPORT:-6,ENABLE_ACCURATE_TEXTURES:7,DISABLE_ACCURATE_TEXTURES:-7,HINT_COUNT:10,SINCOS_LENGTH:parseInt(360/0.5,10),PRECISIONB:15,PRECISIONF:1<<15,PREC_MAXVAL:(1<<15)-1,PREC_ALPHA_SHIFT:24-15,PREC_RED_SHIFT:16-15,NORMAL_MODE_AUTO:0,NORMAL_MODE_SHAPE:1,NORMAL_MODE_VERTEX:2,MAX_LIGHTS:8};function setupTypedArray(name,fallback){if(typeof this[name]!=="function"){if(typeof this[fallback]==="function"){this[name]=this[fallback]}else{this[name]=function(obj){if(obj instanceof Array){return obj}else{if(typeof obj==="number"){return new Array(obj)}}}}}}setupTypedArray("Float32Array","WebGLFloatArray");setupTypedArray("Uint16Array","WebGLUnsignedShortArray");setupTypedArray("Uint8Array","WebGLUnsignedByteArray");var ArrayList=function(){function createArrayList(args){var arr=[];for(var i=0;i<args[0];i++){arr[i]=(args.length>1?createArrayList(args.slice(1)):0)}arr.get=function(i){return this[i]};arr.contains=function(item){return this.indexOf(item)!==-1};arr.add=function(){if(arguments.length===1){this.push(arguments[0])}else{if(arguments.length===2){if(typeof arguments[0]==="number"){if(arguments[0]>=0&&arguments[0]<=this.length){this.splice(arguments[0],0,arguments[1])}else{throw (arguments[0]+" is not a valid index")}}else{throw (typeof arguments[0]+" is not a number")}}else{throw ("Please use the proper number of parameters.")}}};arr.set=function(){if(arguments.length===2){if(typeof arguments[0]==="number"){if(arguments[0]>=0&&arguments[0]<this.length){this.splice(arguments[0],1,arguments[1])}else{throw (arguments[0]+" is not a valid index.")}}else{throw (typeof arguments[0]+" is not a number")}}else{throw ("Please use the proper number of parameters.")}};arr.size=function(){return this.length};arr.clear=function(){this.length=0};arr.remove=function(i){return this.splice(i,1)[0]};arr.isEmpty=function(){return !this.length};arr.clone=function(){return this.slice(0)};arr.toArray=function(){return this.slice(0)};return arr}return createArrayList(Array.prototype.slice.call(arguments))};var HashMap=(function(){function virtHashCode(obj){if(obj.constructor===String){var hash=0;for(var i=0;i<obj.length;++i){hash=(hash*31+obj.charCodeAt(i))&4294967295}return hash}else{if(typeof(obj)!=="object"){return obj&4294967295}else{if("hashCode" in obj){return obj.hashCode.call(obj)}else{if(obj.$id===undef){obj.$id=((Math.floor(Math.random()*65536)-32768)<<16)|Math.floor(Math.random()*65536)}return obj.$id}}}}function virtEquals(obj,other){if(obj===null||other===null){return(obj===null)&&(other===null)}else{if(obj.constructor===String){return obj===other}else{if(typeof(obj)!=="object"){return obj===other}else{if("equals" in obj){return obj.equals.call(obj,other)}else{return obj===other}}}}}function HashMap(){if(arguments.length===1&&arguments[0].constructor===HashMap){return arguments[0].clone()}var initialCapacity=arguments.length>0?arguments[0]:16;var loadFactor=arguments.length>1?arguments[1]:0.75;var buckets=new Array(initialCapacity);var count=0;var hashMap=this;function ensureLoad(){if(count<=loadFactor*buckets.length){return}var allEntries=[];for(var i=0;i<buckets.length;++i){if(buckets[i]!==undef){allEntries=allEntries.concat(buckets[i])}}buckets=new Array(buckets.length*2);for(var j=0;j<allEntries.length;++j){var index=virtHashCode(allEntries[j].key)%buckets.length;var bucket=buckets[index];if(bucket===undef){buckets[index]=bucket=[]}bucket.push(allEntries[j])}}function Iterator(conversion,removeItem){var bucketIndex=0;var itemIndex=-1;var endOfBuckets=false;function findNext(){while(!endOfBuckets){++itemIndex;if(bucketIndex>=buckets.length){endOfBuckets=true}else{if(buckets[bucketIndex]===undef||itemIndex>=buckets[bucketIndex].length){itemIndex=-1;++bucketIndex}else{return}}}}this.hasNext=function(){return !endOfBuckets};this.next=function(){var result=conversion(buckets[bucketIndex][itemIndex]);findNext();return result};this.remove=function(){removeItem(this.next());--itemIndex};findNext()}function Set(conversion,isIn,removeItem){this.clear=function(){hashMap.clear()};this.contains=function(o){return isIn(o)};this.containsAll=function(o){var it=o.iterator();while(it.hasNext()){if(!this.contains(it.next())){return false}}return true};this.isEmpty=function(){return hashMap.isEmpty()};this.iterator=function(){return new Iterator(conversion,removeItem)};this.remove=function(o){if(this.contains(o)){removeItem(o);return true}return false};this.removeAll=function(c){var it=c.iterator();var changed=false;while(it.hasNext()){var item=it.next();if(this.contains(item)){removeItem(item);changed=true}}return true};this.retainAll=function(c){var it=this.iterator();var toRemove=[];while(it.hasNext()){var entry=it.next();if(!c.contains(entry)){toRemove.push(entry)}}for(var i=0;i<toRemove.length;++i){removeItem(toRemove[i])}return toRemove.length>0};this.size=function(){return hashMap.size()};this.toArray=function(){var result=new ArrayList(0);var it=this.iterator();while(it.hasNext()){result.push(it.next())}return result}}function Entry(pair){this._isIn=function(map){return map===hashMap&&(pair.removed===undef)};this.equals=function(o){return virtEquals(pair.key,o.getKey())};this.getKey=function(){return pair.key};this.getValue=function(){return pair.value};this.hashCode=function(o){return virtHashCode(pair.key)};this.setValue=function(value){var old=pair.value;pair.value=value;return old}}this.clear=function(){count=0;buckets=new Array(initialCapacity)};this.clone=function(){var map=new HashMap();map.putAll(this);return map};this.containsKey=function(key){var index=virtHashCode(key)%buckets.length;var bucket=buckets[index];if(bucket===undef){return false}for(var i=0;i<bucket.length;++i){if(virtEquals(bucket[i].key,key)){return true}}return false};this.containsValue=function(value){for(var i=0;i<buckets.length;++i){var bucket=buckets[i];if(bucket===undef){continue}for(var j=0;j<bucket.length;++j){if(virtEquals(bucket[j].value,value)){return true}}}return false};this.entrySet=function(){return new Set(function(pair){return new Entry(pair)},function(pair){return pair.constructor===Entry&&pair._isIn(hashMap)},function(pair){return hashMap.remove(pair.getKey())})};this.get=function(key){var index=virtHashCode(key)%buckets.length;var bucket=buckets[index];if(bucket===undef){return null}for(var i=0;i<bucket.length;++i){if(virtEquals(bucket[i].key,key)){return bucket[i].value}}return null};this.isEmpty=function(){return count===0};this.keySet=function(){return new Set(function(pair){return pair.key},function(key){return hashMap.containsKey(key)},function(key){return hashMap.remove(key)})};this.put=function(key,value){var index=virtHashCode(key)%buckets.length;var bucket=buckets[index];if(bucket===undef){++count;buckets[index]=[{key:key,value:value}];ensureLoad();return null}for(var i=0;i<bucket.length;++i){if(virtEquals(bucket[i].key,key)){var previous=bucket[i].value;bucket[i].value=value;return previous}}++count;bucket.push({key:key,value:value});ensureLoad();return null};this.putAll=function(m){var it=m.entrySet().iterator();while(it.hasNext()){var entry=it.next();this.put(entry.getKey(),entry.getValue())}};this.remove=function(key){var index=virtHashCode(key)%buckets.length;var bucket=buckets[index];if(bucket===undef){return null}for(var i=0;i<bucket.length;++i){if(virtEquals(bucket[i].key,key)){--count;var previous=bucket[i].value;bucket[i].removed=true;if(bucket.length>1){bucket.splice(i,1)}else{buckets[index]=undef}return previous}}return null};this.size=function(){return count};this.values=function(){var result=new ArrayList(0);var it=this.entrySet().iterator();while(it.hasNext()){var entry=it.next();result.push(entry.getValue())}return result}}return HashMap}());var PVector=(function(){function PVector(x,y,z){this.x=x||0;this.y=y||0;this.z=z||0}function createPVectorMethod(method){return function(v1,v2){var v=v1.get();v[method](v2);return v}}function createSimplePVectorMethod(method){return function(v1,v2){return v1[method](v2)}}var simplePVMethods="dist dot cross".split(" ");var method=simplePVMethods.length;PVector.angleBetween=function(v1,v2){return Math.acos(v1.dot(v2)/(v1.mag()*v2.mag()))};PVector.prototype={set:function(v,y,z){if(arguments.length===1){this.set(v.x||v[0],v.y||v[1],v.z||v[2])}else{this.x=v;this.y=y;this.z=z}},get:function(){return new PVector(this.x,this.y,this.z)},mag:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},add:function(v,y,z){if(arguments.length===3){this.x+=v;this.y+=y;this.z+=z}else{if(arguments.length===1){this.x+=v.x;this.y+=v.y;this.z+=v.z}}},sub:function(v,y,z){if(arguments.length===3){this.x-=v;this.y-=y;this.z-=z}else{if(arguments.length===1){this.x-=v.x;this.y-=v.y;this.z-=v.z}}},mult:function(v){if(typeof v==="number"){this.x*=v;this.y*=v;this.z*=v}else{if(typeof v==="object"){this.x*=v.x;this.y*=v.y;this.z*=v.z}}},div:function(v){if(typeof v==="number"){this.x/=v;this.y/=v;this.z/=v}else{if(typeof v==="object"){this.x/=v.x;this.y/=v.y;this.z/=v.z}}},dist:function(v){var dx=this.x-v.x,dy=this.y-v.y,dz=this.z-v.z;return Math.sqrt(dx*dx+dy*dy+dz*dz)},dot:function(v,y,z){if(arguments.length===3){return(this.x*v+this.y*y+this.z*z)}else{if(arguments.length===1){return(this.x*v.x+this.y*v.y+this.z*v.z)}}},cross:function(v){return new PVector(this.y*v.z-v.y*this.z,this.z*v.x-v.z*this.x,this.x*v.y-v.x*this.y)},normalize:function(){var m=this.mag();if(m>0){this.div(m)}},limit:function(high){if(this.mag()>high){this.normalize();this.mult(high)}},heading2D:function(){return(-Math.atan2(-this.y,this.x))},toString:function(){return"["+this.x+", "+this.y+", "+this.z+"]"},array:function(){return[this.x,this.y,this.z]}};while(method--){PVector[simplePVMethods[method]]=createSimplePVectorMethod(simplePVMethods[method])}for(method in PVector.prototype){if(PVector.prototype.hasOwnProperty(method)&&!PVector.hasOwnProperty(method)){PVector[method]=createPVectorMethod(method)}}return PVector}());var Processing=this.Processing=function Processing(curElement,aCode){var p=this;p.ArrayList=ArrayList;p.HashMap=HashMap;p.PVector=PVector;p.externals={canvas:curElement,context:undef,sketch:undef,onblur:function(){},onfocus:function(){}};p.name="Processing.js Instance";p.use3DContext=false;p.focused=true;p.breakShape=false;p.glyphTable={};p.pmouseX=0;p.pmouseY=0;p.mouseX=0;p.mouseY=0;p.mouseButton=0;p.mouseScroll=0;p.mouseClicked=undef;p.mouseDragged=undef;p.mouseMoved=undef;p.mousePressed=undef;p.mouseReleased=undef;p.mouseScrolled=undef;p.key=undef;p.keyCode=undef;p.keyPressed=undef;p.keyReleased=undef;p.keyTyped=undef;p.draw=undef;p.setup=undef;p.__mousePressed=false;p.__keyPressed=false;p.__frameRate=0;p.frameCount=0;p.width=curElement.width-0;p.height=curElement.height-0;p.defineProperty=function(obj,name,desc){if("defineProperty" in Object){Object.defineProperty(obj,name,desc)}else{if(desc.hasOwnProperty("get")){obj.__defineGetter__(name,desc.get)}if(desc.hasOwnProperty("set")){obj.__defineSetter__(name,desc.set)}}};var curContext,curSketch,online=true,doFill=true,fillStyle=[1,1,1,1],currentFillColor=4294967295,isFillDirty=true,doStroke=true,strokeStyle=[0.8,0.8,0.8,1],currentStrokeColor=4294835709,isStrokeDirty=true,lineWidth=1,loopStarted=false,doLoop=true,looping=0,curRectMode=PConstants.CORNER,curEllipseMode=PConstants.CENTER,normalX=0,normalY=0,normalZ=0,normalMode=PConstants.NORMAL_MODE_AUTO,inDraw=false,curFrameRate=60,curCursor=PConstants.ARROW,oldCursor=curElement.style.cursor,curMsPerFrame=1,curShape=PConstants.POLYGON,curShapeCount=0,curvePoints=[],curTightness=0,curveDet=20,curveInited=false,bezDetail=20,colorModeA=255,colorModeX=255,colorModeY=255,colorModeZ=255,pathOpen=false,mouseDragging=false,curColorMode=PConstants.RGB,curTint=function(){},curTextSize=12,curTextFont="Arial",getLoaded=false,start=new Date().getTime(),timeSinceLastFPS=start,framesSinceLastFPS=0,textcanvas,curveBasisMatrix,curveToBezierMatrix,curveDrawMatrix,bezierDrawMatrix,bezierBasisInverse,bezierBasisMatrix,programObject3D,programObject2D,programObjectUnlitShape,boxBuffer,boxNormBuffer,boxOutlineBuffer,rectBuffer,rectNormBuffer,sphereBuffer,lineBuffer,fillBuffer,fillColorBuffer,strokeColorBuffer,pointBuffer,shapeTexVBO,canTex,curTexture={width:0,height:0},curTextureMode=PConstants.IMAGE,usingTexture=false,textBuffer,textureBuffer,indexBuffer,horizontalTextAlignment=PConstants.LEFT,verticalTextAlignment=PConstants.BASELINE,baselineOffset=0.2,tMode=PConstants.MODEL,originalContext,proxyContext=null,isContextReplaced=false,setPixelsCached,maxPixelsCached=1000,codedKeys=[PConstants.SHIFT,PConstants.CONTROL,PConstants.ALT,PConstants.UP,PConstants.RIGHT,PConstants.DOWN,PConstants.LEFT];var stylePaddingLeft,stylePaddingTop,styleBorderLeft,styleBorderTop;if(document.defaultView&&document.defaultView.getComputedStyle){stylePaddingLeft=parseInt(document.defaultView.getComputedStyle(curElement,null)["paddingLeft"],10)||0;stylePaddingTop=parseInt(document.defaultView.getComputedStyle(curElement,null)["paddingTop"],10)||0;styleBorderLeft=parseInt(document.defaultView.getComputedStyle(curElement,null)["borderLeftWidth"],10)||0;styleBorderTop=parseInt(document.defaultView.getComputedStyle(curElement,null)["borderTopWidth"],10)||0}var lightCount=0;var sphereDetailV=0,sphereDetailU=0,sphereX=[],sphereY=[],sphereZ=[],sinLUT=new Array(PConstants.SINCOS_LENGTH),cosLUT=new Array(PConstants.SINCOS_LENGTH),sphereVerts,sphereNorms;var cam,cameraInv,forwardTransform,reverseTransform,modelView,modelViewInv,userMatrixStack,inverseCopy,projection,manipulatingCamera=false,frustumMode=false,cameraFOV=60*(Math.PI/180),cameraX=curElement.width/2,cameraY=curElement.height/2,cameraZ=cameraY/Math.tan(cameraFOV/2),cameraNear=cameraZ/10,cameraFar=cameraZ*10,cameraAspect=curElement.width/curElement.height;var vertArray=[],curveVertArray=[],curveVertCount=0,isCurve=false,isBezier=false,firstVert=true;var curShapeMode=PConstants.CORNER;var colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var styleArray=new Array(0);var boxVerts=new Float32Array([0.5,0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,0.5,0.5,0.5,0.5,-0.5,0.5,0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,0.5,0.5,0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,0.5,0.5,0.5]);var boxOutlineVerts=new Float32Array([0.5,0.5,0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,0.5,0.5,0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,0.5,0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5]);var boxNorms=new Float32Array([0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0]);var rectVerts=new Float32Array([0,0,0,0,1,0,1,1,0,1,0,0]);var rectNorms=new Float32Array([0,0,-1,0,0,-1,0,0,-1,0,0,-1]);var vShaderSrcUnlitShape="varying vec4 frontColor;attribute vec3 aVertex;attribute vec4 aColor;uniform mat4 uView;uniform mat4 uProjection;void main(void) {  frontColor = aColor;  gl_Position = uProjection * uView * vec4(aVertex, 1.0);}";var fShaderSrcUnlitShape="#ifdef GL_ES\nprecision highp float;\n#endif\nvarying vec4 frontColor;void main(void){  gl_FragColor = frontColor;}";var vertexShaderSource2D="varying vec4 frontColor;attribute vec3 Vertex;attribute vec2 aTextureCoord;uniform vec4 color;uniform mat4 model;uniform mat4 view;uniform mat4 projection;uniform float pointSize;varying vec2 vTextureCoord;void main(void) {  gl_PointSize = pointSize;  frontColor = color;  gl_Position = projection * view * model * vec4(Vertex, 1.0);  vTextureCoord = aTextureCoord;}";var fragmentShaderSource2D="#ifdef GL_ES\nprecision highp float;\n#endif\nvarying vec4 frontColor;varying vec2 vTextureCoord;uniform sampler2D uSampler;uniform int picktype;void main(void){  if(picktype == 0){    gl_FragColor = frontColor;  }  else if(picktype == 1){    float alpha = texture2D(uSampler, vTextureCoord).a;    gl_FragColor = vec4(frontColor.rgb*alpha, alpha);\n  }}";var vertexShaderSource3D="varying vec4 frontColor;attribute vec3 Vertex;attribute vec3 Normal;attribute vec4 aColor;attribute vec2 aTexture;varying   vec2 vTexture;uniform vec4 color;uniform bool usingMat;uniform vec3 specular;uniform vec3 mat_emissive;uniform vec3 mat_ambient;uniform vec3 mat_specular;uniform float shininess;uniform mat4 model;uniform mat4 view;uniform mat4 projection;uniform mat4 normalTransform;uniform int lightCount;uniform vec3 falloff;struct Light {  bool dummy;  int type;  vec3 color;  vec3 position;  vec3 direction;  float angle;  vec3 halfVector;  float concentration;};uniform Light lights[8];void AmbientLight( inout vec3 totalAmbient, in vec3 ecPos, in Light light ) {  float d = length( light.position - ecPos );  float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));  totalAmbient += light.color * attenuation;}void DirectionalLight( inout vec3 col, in vec3 ecPos, inout vec3 spec, in vec3 vertNormal, in Light light ) {  float powerfactor = 0.0;  float nDotVP = max(0.0, dot( vertNormal, light.position ));  float nDotVH = max(0.0, dot( vertNormal, normalize( light.position-ecPos )));  if( nDotVP != 0.0 ){    powerfactor = pow( nDotVH, shininess );  }  col += light.color * nDotVP;  spec += specular * powerfactor;}void PointLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in vec3 eye, in Light light ) {  float powerfactor;   vec3 VP = light.position - ecPos;  float d = length( VP );   VP = normalize( VP );  float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));  float nDotVP = max( 0.0, dot( vertNormal, VP ));  vec3 halfVector = normalize( VP + eye );  float nDotHV = max( 0.0, dot( vertNormal, halfVector ));  if( nDotVP == 0.0) {    powerfactor = 0.0;  }  else{    powerfactor = pow( nDotHV, shininess );  }  spec += specular * powerfactor * attenuation;  col += light.color * nDotVP * attenuation;}void SpotLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in vec3 eye, in Light light ) {  float spotAttenuation;  float powerfactor;  vec3 VP = light.position - ecPos;   vec3 ldir = normalize( light.direction );  float d = length( VP );  VP = normalize( VP );  float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ) );  float spotDot = dot( VP, ldir );  if( spotDot < cos( light.angle ) ) {    spotAttenuation = pow( spotDot, light.concentration );  }  else{    spotAttenuation = 1.0;  }  attenuation *= spotAttenuation;  float nDotVP = max( 0.0, dot( vertNormal, VP ));  vec3 halfVector = normalize( VP + eye );  float nDotHV = max( 0.0, dot( vertNormal, halfVector ));  if( nDotVP == 0.0 ) {    powerfactor = 0.0;  }  else {    powerfactor = pow( nDotHV, shininess );  }  spec += specular * powerfactor * attenuation;  col += light.color * nDotVP * attenuation;}void main(void) {  vec3 finalAmbient = vec3( 0.0, 0.0, 0.0 );  vec3 finalDiffuse = vec3( 0.0, 0.0, 0.0 );  vec3 finalSpecular = vec3( 0.0, 0.0, 0.0 );  vec4 col = color;  if(color[0] == -1.0){    col = aColor;  }  vec3 norm = vec3( normalTransform * vec4( Normal, 0.0 ) );  vec4 ecPos4 = view * model * vec4(Vertex,1.0);  vec3 ecPos = (vec3(ecPos4))/ecPos4.w;  vec3 eye = vec3( 0.0, 0.0, 1.0 );  if( lightCount == 0 ) {    frontColor = col + vec4(mat_specular,1.0);  }  else {    for( int i = 0; i < lightCount; i++ ) {      if( lights[i].type == 0 ) {        AmbientLight( finalAmbient, ecPos, lights[i] );      }      else if( lights[i].type == 1 ) {        DirectionalLight( finalDiffuse,ecPos, finalSpecular, norm, lights[i] );      }      else if( lights[i].type == 2 ) {        PointLight( finalDiffuse, finalSpecular, norm, ecPos, eye, lights[i] );      }      else if( lights[i].type == 3 ) {        SpotLight( finalDiffuse, finalSpecular, norm, ecPos, eye, lights[i] );      }    }   if( usingMat == false ) {    frontColor = vec4(        vec3(col) * finalAmbient +      vec3(col) * finalDiffuse +      vec3(col) * finalSpecular,      col[3] );   }   else{     frontColor = vec4(        mat_emissive +        (vec3(col) * mat_ambient * finalAmbient) +        (vec3(col) * finalDiffuse) +        (mat_specular * finalSpecular),        col[3] );    }  }  vTexture.xy = aTexture.xy;  gl_Position = projection * view * model * vec4( Vertex, 1.0 );}";var fragmentShaderSource3D="#ifdef GL_ES\nprecision highp float;\n#endif\nvarying vec4 frontColor;uniform sampler2D sampler;uniform bool usingTexture;varying vec2 vTexture;void main(void){  if(usingTexture){    gl_FragColor =  vec4(texture2D(sampler, vTexture.xy));  }  else{    gl_FragColor = frontColor;  }}";function uniformf(programObj,varName,varValue){var varLocation=curContext.getUniformLocation(programObj,varName);if(varLocation!==-1){if(varValue.length===4){curContext.uniform4fv(varLocation,varValue)}else{if(varValue.length===3){curContext.uniform3fv(varLocation,varValue)}else{if(varValue.length===2){curContext.uniform2fv(varLocation,varValue)}else{curContext.uniform1f(varLocation,varValue)}}}}}function uniformi(programObj,varName,varValue){var varLocation=curContext.getUniformLocation(programObj,varName);if(varLocation!==-1){if(varValue.length===4){curContext.uniform4iv(varLocation,varValue)}else{if(varValue.length===3){curContext.uniform3iv(varLocation,varValue)}else{if(varValue.length===2){curContext.uniform2iv(varLocation,varValue)}else{curContext.uniform1i(varLocation,varValue)}}}}}function vertexAttribPointer(programObj,varName,size,VBO){var varLocation=curContext.getAttribLocation(programObj,varName);if(varLocation!==-1){curContext.bindBuffer(curContext.ARRAY_BUFFER,VBO);curContext.vertexAttribPointer(varLocation,size,curContext.FLOAT,false,0,0);curContext.enableVertexAttribArray(varLocation)}}function disableVertexAttribPointer(programObj,varName){var varLocation=curContext.getAttribLocation(programObj,varName);if(varLocation!==-1){curContext.disableVertexAttribArray(varLocation)}}function uniformMatrix(programObj,varName,transpose,matrix){var varLocation=curContext.getUniformLocation(programObj,varName);if(varLocation!==-1){if(matrix.length===16){curContext.uniformMatrix4fv(varLocation,transpose,matrix)}else{if(matrix.length===9){curContext.uniformMatrix3fv(varLocation,transpose,matrix)}else{curContext.uniformMatrix2fv(varLocation,transpose,matrix)}}}}var imageModeCorner=function imageModeCorner(x,y,w,h,whAreSizes){return{x:x,y:y,w:w,h:h}};var imageModeConvert=imageModeCorner;var imageModeCorners=function imageModeCorners(x,y,w,h,whAreSizes){return{x:x,y:y,w:whAreSizes?w:w-x,h:whAreSizes?h:h-y}};var imageModeCenter=function imageModeCenter(x,y,w,h,whAreSizes){return{x:x-w/2,y:y-h/2,w:w,h:h}};var createProgramObject=function(curContext,vetexShaderSource,fragmentShaderSource){var vertexShaderObject=curContext.createShader(curContext.VERTEX_SHADER);curContext.shaderSource(vertexShaderObject,vetexShaderSource);curContext.compileShader(vertexShaderObject);if(!curContext.getShaderParameter(vertexShaderObject,curContext.COMPILE_STATUS)){throw curContext.getShaderInfoLog(vertexShaderObject)}var fragmentShaderObject=curContext.createShader(curContext.FRAGMENT_SHADER);curContext.shaderSource(fragmentShaderObject,fragmentShaderSource);curContext.compileShader(fragmentShaderObject);if(!curContext.getShaderParameter(fragmentShaderObject,curContext.COMPILE_STATUS)){throw curContext.getShaderInfoLog(fragmentShaderObject)}var programObject=curContext.createProgram();curContext.attachShader(programObject,vertexShaderObject);curContext.attachShader(programObject,fragmentShaderObject);curContext.linkProgram(programObject);if(!curContext.getProgramParameter(programObject,curContext.LINK_STATUS)){throw"Error linking shaders."}return programObject};var charMap={};var Char=p.Character=function Char(chr){if(typeof chr==="string"&&chr.length===1){this.code=chr.charCodeAt(0)}else{this.code=NaN}return(charMap[this.code]===undef)?charMap[this.code]=this:charMap[this.code]};Char.prototype.toString=function(){return String.fromCharCode(this.code)};Char.prototype.valueOf=function(){return this.code};var PShape=p.PShape=function(family){this.family=family||PConstants.GROUP;this.visible=true;this.style=true;this.children=[];this.nameTable=[];this.params=[];this.name="";this.image=null;this.matrix=null;this.kind=null;this.close=null;this.width=null;this.height=null;this.parent=null;this.isVisible=function(){return this.visible};this.setVisible=function(visible){this.visible=visible};this.disableStyle=function(){this.style=false;for(var i=0;i<this.children.length;i++){this.children[i].disableStyle()}};this.enableStyle=function(){this.style=true;for(var i=0;i<this.children.length;i++){this.children[i].enableStyle()}};this.getFamily=function(){return this.family};this.getWidth=function(){return this.width};this.getHeight=function(){return this.height};this.setName=function(name){this.name=name};this.getName=function(){return this.name};this.draw=function(){if(this.visible){this.pre();this.drawImpl();this.post()}};this.drawImpl=function(){if(this.family===PConstants.GROUP){this.drawGroup()}else{if(this.family===PConstants.PRIMITIVE){this.drawPrimitive()}else{if(this.family===PConstants.GEOMETRY){this.drawGeometry()}else{if(this.family===PConstants.PATH){this.drawPath()}}}}};this.drawPath=function(){if(this.vertices.length===0){return}p.beginShape();var i;if(this.vertexCodes.length===0){if(this.vertices[0].length===2){for(i=0;i<this.vertices.length;i++){p.vertex(this.vertices[i][0],this.vertices[i][1])}}else{for(i=0;i<this.vertices.length;i++){p.vertex(this.vertices[i][0],this.vertices[i][1],this.vertices[i][2])}}}else{var index=0;var j;if(this.vertices[0].length===2){for(j=0;j<this.vertexCodes.length;j++){switch(this.vertexCodes[j]){case PConstants.VERTEX:p.vertex(this.vertices[index][0],this.vertices[index][1]);if(this.vertices[index]["moveTo"]===true){vertArray[vertArray.length-1]["moveTo"]=true}else{if(this.vertices[index]["moveTo"]===false){vertArray[vertArray.length-1]["moveTo"]=false}}p.breakShape=false;index++;break;case PConstants.BEZIER_VERTEX:p.bezierVertex(this.vertices[index+0][0],this.vertices[index+0][1],this.vertices[index+1][0],this.vertices[index+1][1],this.vertices[index+2][0],this.vertices[index+2][1]);index+=3;break;case PConstants.CURVE_VERTEX:p.curveVertex(this.vertices[index][0],this.vertices[index][1]);index++;break;case PConstants.BREAK:p.breakShape=true;break}}}else{for(j=0;j<this.vertexCodes.length;j++){switch(this.vertexCodes[j]){case PConstants.VERTEX:p.vertex(this.vertices[index][0],this.vertices[index][1],this.vertices[index][2]);if(this.vertices[index]["moveTo"]===true){vertArray[vertArray.length-1]["moveTo"]=true}else{if(this.vertices[index]["moveTo"]===false){vertArray[vertArray.length-1]["moveTo"]=false}}p.breakShape=false;break;case PConstants.BEZIER_VERTEX:p.bezierVertex(this.vertices[index+0][0],this.vertices[index+0][1],this.vertices[index+0][2],this.vertices[index+1][0],this.vertices[index+1][1],this.vertices[index+1][2],this.vertices[index+2][0],this.vertices[index+2][1],this.vertices[index+2][2]);index+=3;break;case PConstants.CURVE_VERTEX:p.curveVertex(this.vertices[index][0],this.vertices[index][1],this.vertices[index][2]);index++;break;case PConstants.BREAK:p.breakShape=true;break}}}}p.endShape(this.close?PConstants.CLOSE:PConstants.OPEN)};this.drawGeometry=function(){p.beginShape(this.kind);var i;if(this.style){for(i=0;i<this.vertices.length;i++){p.vertex(this.vertices[i])}}else{for(i=0;i<this.vertices.length;i++){var vert=this.vertices[i];if(vert[2]===0){p.vertex(vert[0],vert[1])}else{p.vertex(vert[0],vert[1],vert[2])}}}p.endShape()};this.drawGroup=function(){for(var i=0;i<this.children.length;i++){this.children[i].draw()}};this.drawPrimitive=function(){switch(this.kind){case PConstants.POINT:p.point(this.params[0],this.params[1]);break;case PConstants.LINE:if(this.params.length===4){p.line(this.params[0],this.params[1],this.params[2],this.params[3])}else{p.line(this.params[0],this.params[1],this.params[2],this.params[3],this.params[4],this.params[5])}break;case PConstants.TRIANGLE:p.triangle(this.params[0],this.params[1],this.params[2],this.params[3],this.params[4],this.params[5]);break;case PConstants.QUAD:p.quad(this.params[0],this.params[1],this.params[2],this.params[3],this.params[4],this.params[5],this.params[6],this.params[7]);break;case PConstants.RECT:if(this.image!==null){p.imageMode(PConstants.CORNER);p.image(this.image,this.params[0],this.params[1],this.params[2],this.params[3])}else{p.rectMode(PConstants.CORNER);p.rect(this.params[0],this.params[1],this.params[2],this.params[3])}break;case PConstants.ELLIPSE:p.ellipseMode(PConstants.CORNER);p.ellipse(this.params[0],this.params[1],this.params[2],this.params[3]);break;case PConstants.ARC:p.ellipseMode(PConstants.CORNER);p.arc(this.params[0],this.params[1],this.params[2],this.params[3],this.params[4],this.params[5]);break;case PConstants.BOX:if(this.params.length===1){p.box(this.params[0])}else{p.box(this.params[0],this.params[1],this.params[2])}break;case PConstants.SPHERE:p.sphere(this.params[0]);break}};this.pre=function(){if(this.matrix){p.pushMatrix();curContext.transform(this.matrix.elements[0],this.matrix.elements[3],this.matrix.elements[1],this.matrix.elements[4],this.matrix.elements[2],this.matrix.elements[5])}if(this.style){p.pushStyle();this.styles()}};this.post=function(){if(this.matrix){p.popMatrix()}if(this.style){p.popStyle()}};this.styles=function(){if(this.stroke){p.stroke(this.strokeColor);p.strokeWeight(this.strokeWeight);p.strokeCap(this.strokeCap);p.strokeJoin(this.strokeJoin)}else{p.noStroke()}if(this.fill){p.fill(this.fillColor)}else{p.noFill()}};this.getChild=function(child){if(typeof child==="number"){return this.children[child]}else{var found,i;if(child===""||this.name===child){return this}else{if(this.nameTable.length>0){for(i=0;i<this.nameTable.length||found;i++){if(this.nameTable[i].getName===child){found=this.nameTable[i]}}if(found){return found}}for(i=0;i<this.children.lenth;i++){found=this.children[i].getChild(child);if(found){return found}}}return null}};this.getChildCount=function(){return this.children.length};this.addChild=function(child){this.children.push(child);child.parent=this;if(child.getName()!==null){this.addName(child.getName(),child)}};this.addName=function(name,shape){if(this.parent!==null){this.parent.addName(name,shape)}else{this.nameTable.push([name,shape])}};this.translate=function(){if(arguments.length===2){this.checkMatrix(2);this.matrix.translate(arguments[0],arguments[1])}else{this.checkMatrix(3);this.matrix.translate(arguments[0],arguments[1],0)}};this.checkMatrix=function(dimensions){if(this.matrix===null){if(dimensions===2){this.matrix=new p.PMatrix2D()}else{this.matrix=new p.PMatrix3D()}}else{if(dimensions===3&&this.matrix instanceof p.PMatrix2D){this.matrix=new p.PMatrix3D()}}};this.rotateX=function(angle){this.rotate(angle,1,0,0)};this.rotateY=function(angle){this.rotate(angle,0,1,0)};this.rotateZ=function(angle){this.rotate(angle,0,0,1)};this.rotate=function(){if(arguments.length===1){this.checkMatrix(2);this.matrix.rotate(arguments[0])}else{this.checkMatrix(3);this.matrix.rotate(arguments[0],arguments[1],arguments[2],arguments[3])}};this.scale=function(){if(arguments.length===2){this.checkMatrix(2);this.matrix.scale(arguments[0],arguments[1])}else{if(arguments.length===3){this.checkMatrix(2);this.matrix.scale(arguments[0],arguments[1],arguments[2])}else{this.checkMatrix(2);this.matrix.scale(arguments[0])}}};this.resetMatrix=function(){this.checkMatrix(2);this.matrix.reset()};this.applyMatrix=function(matrix){if(arguments.length===1){this.applyMatrix(matrix.elements[0],matrix.elements[1],0,matrix.elements[2],matrix.elements[3],matrix.elements[4],0,matrix.elements[5],0,0,1,0,0,0,0,1)}else{if(arguments.length===6){this.checkMatrix(2);this.matrix.apply(arguments[0],arguments[1],arguments[2],0,arguments[3],arguments[4],arguments[5],0,0,0,1,0,0,0,0,1)}else{if(arguments.length===16){this.checkMatrix(3);this.matrix.apply(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8],arguments[9],arguments[10],arguments[11],arguments[12],arguments[13],arguments[14],arguments[15])}}}}};var PShapeSVG=function(){p.PShape.call(this);if(arguments.length===1){this.element=new p.XMLElement(null,arguments[0]);this.vertexCodes=[];this.vertices=[];this.opacity=1;this.stroke=false;this.strokeColor=PConstants.ALPHA_MASK;this.strokeWeight=1;this.strokeCap=PConstants.SQUARE;this.strokeJoin=PConstants.MITER;this.strokeGradient=null;this.strokeGradientPaint=null;this.strokeName=null;this.strokeOpacity=1;this.fill=true;this.fillColor=PConstants.ALPHA_MASK;this.fillGradient=null;this.fillGradientPaint=null;this.fillName=null;this.fillOpacity=1;if(this.element.getName()!=="svg"){throw ("root is not <svg>, it's <"+this.element.getName()+">")}}else{if(arguments.length===2){if(typeof arguments[1]==="string"){if(arguments[1].indexOf(".svg")>-1){this.element=new p.XMLElement(null,arguments[1]);this.vertexCodes=[];this.vertices=[];this.opacity=1;this.stroke=false;this.strokeColor=PConstants.ALPHA_MASK;this.strokeWeight=1;this.strokeCap=PConstants.SQUARE;this.strokeJoin=PConstants.MITER;this.strokeGradient="";this.strokeGradientPaint="";this.strokeName="";this.strokeOpacity=1;this.fill=true;this.fillColor=PConstants.ALPHA_MASK;this.fillGradient=null;this.fillGradientPaint=null;this.fillOpacity=1}}else{if(arguments[0]){this.element=arguments[1];this.vertexCodes=arguments[0].vertexCodes.slice();this.vertices=arguments[0].vertices.slice();this.stroke=arguments[0].stroke;this.strokeColor=arguments[0].strokeColor;this.strokeWeight=arguments[0].strokeWeight;this.strokeCap=arguments[0].strokeCap;this.strokeJoin=arguments[0].strokeJoin;this.strokeGradient=arguments[0].strokeGradient;this.strokeGradientPaint=arguments[0].strokeGradientPaint;this.strokeName=arguments[0].strokeName;this.fill=arguments[0].fill;this.fillColor=arguments[0].fillColor;this.fillGradient=arguments[0].fillGradient;this.fillGradientPaint=arguments[0].fillGradientPaint;this.fillName=arguments[0].fillName;this.strokeOpacity=arguments[0].strokeOpacity;this.fillOpacity=arguments[0].fillOpacity;this.opacity=arguments[0].opacity}}}}this.name=this.element.getStringAttribute("id");var displayStr=this.element.getStringAttribute("display","inline");this.visible=displayStr!=="none";var str=this.element.getAttribute("transform");if(str){this.matrix=this.parseMatrix(str)}var viewBoxStr=this.element.getStringAttribute("viewBox");if(viewBoxStr!==null){var viewBox=viewBoxStr.split(" ");this.width=viewBox[2];this.height=viewBox[3]}var unitWidth=this.element.getStringAttribute("width");var unitHeight=this.element.getStringAttribute("height");if(unitWidth!==null){this.width=this.parseUnitSize(unitWidth);this.height=this.parseUnitSize(unitHeight)}else{if((this.width===0)||(this.height===0)){this.width=1;this.height=1;throw ("The width and/or height is not readable in the <svg> tag of this file.")}}this.parseColors(this.element);this.parseChildren(this.element)};PShapeSVG.prototype={parseMatrix:function(str){this.checkMatrix(2);var pieces=[];str.replace(/\s*(\w+)\((.*?)\)/g,function(all){pieces.push(p.trim(all))});if(pieces.length===0){p.println("Transformation:"+str+" is empty");return null}for(var i=0;i<pieces.length;i++){var m=[];pieces[i].replace(/\((.*?)\)/,(function(){return function(all,params){m=params.replace(/,+/g," ").split(/\s+/)}}()));if(pieces[i].indexOf("matrix")!==-1){this.matrix.set(m[0],m[2],m[4],m[1],m[3],m[5])}else{if(pieces[i].indexOf("translate")!==-1){var tx=m[0];var ty=(m.length===2)?m[1]:0;this.matrix.translate(tx,ty)}else{if(pieces[i].indexOf("scale")!==-1){var sx=m[0];var sy=(m.length===2)?m[1]:m[0];this.matrix.scale(sx,sy)}else{if(pieces[i].indexOf("rotate")!==-1){var angle=m[0];if(m.length===1){this.matrix.rotate(p.radians(angle))}else{if(m.length===3){this.matrix.translate(m[1],m[2]);this.matrix.rotate(p.radians(m[0]));this.matrix.translate(-m[1],-m[2])}}}else{if(pieces[i].indexOf("skewX")!==-1){this.matrix.skewX(parseFloat(m[0]))}else{if(pieces[i].indexOf("skewY")!==-1){this.matrix.skewY(m[0])}}}}}}}return this.matrix},parseChildren:function(element){var newelement=element.getChildren();var children=new p.PShape();for(var i=0;i<newelement.length;i++){var kid=this.parseChild(newelement[i]);if(kid){children.addChild(kid)}}this.children.push(children)},getName:function(){return this.name},parseChild:function(elem){var name=elem.getName();var shape;switch(name){case"g":shape=new PShapeSVG(this,elem);break;case"defs":shape=new PShapeSVG(this,elem);break;case"line":shape=new PShapeSVG(this,elem);shape.parseLine();break;case"circle":shape=new PShapeSVG(this,elem);shape.parseEllipse(true);break;case"ellipse":shape=new PShapeSVG(this,elem);shape.parseEllipse(false);break;case"rect":shape=new PShapeSVG(this,elem);shape.parseRect();break;case"polygon":shape=new PShapeSVG(this,elem);shape.parsePoly(true);break;case"polyline":shape=new PShapeSVG(this,elem);shape.parsePoly(false);break;case"path":shape=new PShapeSVG(this,elem);shape.parsePath();break;case"radialGradient":break;case"linearGradient":break;case"text":p.println("Text in SVG files is not currently supported, convert text to outlines instead.");break;case"filter":p.println("Filters are not supported.");break;case"mask":p.println("Masks are not supported.");break;default:p.println("Ignoring  <"+name+"> tag.");break}return shape},parsePath:function(){this.family=PConstants.PATH;this.kind=0;var pathDataChars=[];var c;var pathData=p.trim(this.element.getStringAttribute("d").replace(/[\s,]+/g," "));if(pathData===null){return}pathData=pathData.toCharArray();var cx=0,cy=0,ctrlX=0,ctrlY=0,ctrlX1=0,ctrlX2=0,ctrlY1=0,ctrlY2=0,endX=0,endY=0,ppx=0,ppy=0,px=0,py=0,i=0,j=0,valOf=0;var str="";var tmpArray=[];var flag=false;var lastInstruction;var command;while(i<pathData.length){valOf=pathData[i].valueOf();if((valOf>=65&&valOf<=90)||(valOf>=97&&valOf<=122)){j=i;i++;if(i<pathData.length){tmpArray=[];valOf=pathData[i].valueOf();while(!((valOf>=65&&valOf<=90)||(valOf>=97&&valOf<=100)||(valOf>=102&&valOf<=122))&&flag===false){if(valOf===32){if(str!==""){tmpArray.push(parseFloat(str));str=""}i++}else{if(valOf===45){if(pathData[i-1].valueOf()===101){str+=pathData[i].toString();i++}else{if(str!==""){tmpArray.push(parseFloat(str))}str=pathData[i].toString();i++}}else{str+=pathData[i].toString();i++}}if(i===pathData.length){flag=true}else{valOf=pathData[i].valueOf()}}}if(str!==""){tmpArray.push(parseFloat(str));str=""}command=pathData[j];switch(command.valueOf()){case 77:if(tmpArray.length>=2&&tmpArray.length%2===0){cx=tmpArray[0];cy=tmpArray[1];this.parsePathMoveto(cx,cy);if(tmpArray.length>2){for(j=2;j<tmpArray.length;j+=2){cx=tmpArray[j];cy=tmpArray[j+1];this.parsePathLineto(cx,cy)}}}break;case 109:if(tmpArray.length>=2&&tmpArray.length%2===0){this.parsePathMoveto(cx,cy);if(tmpArray.length>2){for(j=2;j<tmpArray.length;j+=2){cx+=tmpArray[j];cy+=tmpArray[j+1];this.parsePathLineto(cx,cy)}}}break;case 76:if(tmpArray.length>=2&&tmpArray.length%2===0){for(j=0;j<tmpArray.length;j+=2){cx=tmpArray[j];cy=tmpArray[j+1];this.parsePathLineto(cx,cy)}}break;case 108:if(tmpArray.length>=2&&tmpArray.length%2===0){for(j=0;j<tmpArray.length;j+=2){cx+=tmpArray[j];cy+=tmpArray[j+1];this.parsePathLineto(cx,cy)}}break;case 72:for(j=0;j<tmpArray.length;j++){cx=tmpArray[j];this.parsePathLineto(cx,cy)}break;case 104:for(j=0;j<tmpArray.length;j++){cx+=tmpArray[j];this.parsePathLineto(cx,cy)}break;case 86:for(j=0;j<tmpArray.length;j++){cy=tmpArray[j];this.parsePathLineto(cx,cy)}break;case 118:for(j=0;j<tmpArray.length;j++){cy+=tmpArray[j];this.parsePathLineto(cx,cy)}break;case 67:if(tmpArray.length>=6&&tmpArray.length%6===0){for(j=0;j<tmpArray.length;j+=6){ctrlX1=tmpArray[j];ctrlY1=tmpArray[j+1];ctrlX2=tmpArray[j+2];ctrlY2=tmpArray[j+3];endX=tmpArray[j+4];endY=tmpArray[j+5];this.parsePathCurveto(ctrlX1,ctrlY1,ctrlX2,ctrlY2,endX,endY);cx=endX;cy=endY}}break;case 99:if(tmpArray.length>=6&&tmpArray.length%6===0){for(j=0;j<tmpArray.length;j+=6){ctrlX1=cx+tmpArray[j];ctrlY1=cy+tmpArray[j+1];ctrlX2=cx+tmpArray[j+2];ctrlY2=cy+tmpArray[j+3];endX=cx+tmpArray[j+4];endY=cy+tmpArray[j+5];this.parsePathCurveto(ctrlX1,ctrlY1,ctrlX2,ctrlY2,endX,endY);cx=endX;cy=endY}}break;case 83:if(tmpArray.length>=4&&tmpArray.length%4===0){for(j=0;j<tmpArray.length;j+=4){if(lastInstruction.toLowerCase()==="c"||lastInstruction.toLowerCase()==="s"){ppx=this.vertices[this.vertices.length-2][0];ppy=this.vertices[this.vertices.length-2][1];px=this.vertices[this.vertices.length-1][0];py=this.vertices[this.vertices.length-1][1];ctrlX1=px+(px-ppx);ctrlY1=py+(py-ppy)}else{ctrlX1=this.vertices[this.vertices.length-1][0];ctrlY1=this.vertices[this.vertices.length-1][1]}ctrlX2=tmpArray[j];ctrlY2=tmpArray[j+1];endX=tmpArray[j+2];endY=tmpArray[j+3];this.parsePathCurveto(ctrlX1,ctrlY1,ctrlX2,ctrlY2,endX,endY);cx=endX;cy=endY}}break;case 115:if(tmpArray.length>=4&&tmpArray.length%4===0){for(j=0;j<tmpArray.length;j+=4){if(lastInstruction.toLowerCase()==="c"||lastInstruction.toLowerCase()==="s"){ppx=this.vertices[this.vertices.length-2][0];ppy=this.vertices[this.vertices.length-2][1];px=this.vertices[this.vertices.length-1][0];py=this.vertices[this.vertices.length-1][1];ctrlX1=px+(px-ppx);ctrlY1=py+(py-ppy)}else{ctrlX1=this.vertices[this.vertices.length-1][0];ctrlY1=this.vertices[this.vertices.length-1][1]}ctrlX2=cx+tmpArray[j];ctrlY2=cy+tmpArray[j+1];endX=cx+tmpArray[j+2];endY=cy+tmpArray[j+3];this.parsePathCurveto(ctrlX1,ctrlY1,ctrlX2,ctrlY2,endX,endY);cx=endX;cy=endY}}break;case 81:if(tmpArray.length>=4&&tmpArray.length%4===0){for(j=0;j<tmpArray.length;j+=4){ctrlX=tmpArray[j];ctrlY=tmpArray[j+1];endX=tmpArray[j+2];endY=tmpArray[j+3];this.parsePathQuadto(cx,cy,ctrlX,ctrlY,endX,endY);cx=endX;cy=endY}}break;case 113:if(tmpArray.length>=4&&tmpArray.length%4===0){for(j=0;j<tmpArray.length;j+=4){ctrlX=cx+tmpArray[j];ctrlY=cy+tmpArray[j+1];endX=cx+tmpArray[j+2];endY=cy+tmpArray[j+3];this.parsePathQuadto(cx,cy,ctrlX,ctrlY,endX,endY);cx=endX;cy=endY}}break;case 84:if(tmpArray.length>=2&&tmpArray.length%2===0){for(j=0;j<tmpArray.length;j+=2){if(lastInstruction.toLowerCase()==="q"||lastInstruction.toLowerCase()==="t"){ppx=this.vertices[this.vertices.length-2][0];ppy=this.vertices[this.vertices.length-2][1];px=this.vertices[this.vertices.length-1][0];py=this.vertices[this.vertices.length-1][1];ctrlX=px+(px-ppx);ctrlY=py+(py-ppy)}else{ctrlX=cx;ctrlY=cy}endX=tmpArray[j];endY=tmpArray[j+1];this.parsePathQuadto(cx,cy,ctrlX,ctrlY,endX,endY);cx=endX;cy=endY}}break;case 116:if(tmpArray.length>=2&&tmpArray.length%2===0){for(j=0;j<tmpArray.length;j+=2){if(lastInstruction.toLowerCase()==="q"||lastInstruction.toLowerCase()==="t"){ppx=this.vertices[this.vertices.length-2][0];ppy=this.vertices[this.vertices.length-2][1];px=this.vertices[this.vertices.length-1][0];py=this.vertices[this.vertices.length-1][1];ctrlX=px+(px-ppx);ctrlY=py+(py-ppy)}else{ctrlX=cx;ctrlY=cy}endX=cx+tmpArray[j];endY=cy+tmpArray[j+1];this.parsePathQuadto(cx,cy,ctrlX,ctrlY,endX,endY);cx=endX;cy=endY}}break;case 90:case 122:this.close=true;break}lastInstruction=command.toString()}else{i++}}},parsePathQuadto:function(x1,y1,cx,cy,x2,y2){if(this.vertices.length>0){this.parsePathCode(PConstants.BEZIER_VERTEX);this.parsePathVertex(x1+((cx-x1)*2/3),y1+((cy-y1)*2/3));this.parsePathVertex(x2+((cx-x2)*2/3),y2+((cy-y2)*2/3));this.parsePathVertex(x2,y2)}else{throw ("Path must start with M/m")}},parsePathCurveto:function(x1,y1,x2,y2,x3,y3){if(this.vertices.length>0){this.parsePathCode(PConstants.BEZIER_VERTEX);this.parsePathVertex(x1,y1);this.parsePathVertex(x2,y2);this.parsePathVertex(x3,y3)}else{throw ("Path must start with M/m")}},parsePathLineto:function(px,py){if(this.vertices.length>0){this.parsePathCode(PConstants.VERTEX);this.parsePathVertex(px,py);this.vertices[this.vertices.length-1]["moveTo"]=false}else{throw ("Path must start with M/m")}},parsePathMoveto:function(px,py){if(this.vertices.length>0){this.parsePathCode(PConstants.BREAK)}this.parsePathCode(PConstants.VERTEX);this.parsePathVertex(px,py);this.vertices[this.vertices.length-1]["moveTo"]=true},parsePathVertex:function(x,y){var verts=[];verts[0]=x;verts[1]=y;this.vertices.push(verts)},parsePathCode:function(what){this.vertexCodes.push(what)},parsePoly:function(val){this.family=PConstants.PATH;this.close=val;var pointsAttr=p.trim(this.element.getStringAttribute("points").replace(/[,\s]+/g," "));if(pointsAttr!==null){var pointsBuffer=pointsAttr.split(" ");if(pointsBuffer.length%2===0){for(var i=0;i<pointsBuffer.length;i++){var verts=[];verts[0]=pointsBuffer[i];verts[1]=pointsBuffer[++i];this.vertices.push(verts)}}else{p.println("Error parsing polygon points: odd number of coordinates provided")}}},parseRect:function(){this.kind=PConstants.RECT;this.family=PConstants.PRIMITIVE;this.params=[];this.params[0]=this.element.getFloatAttribute("x");this.params[1]=this.element.getFloatAttribute("y");this.params[2]=this.element.getFloatAttribute("width");this.params[3]=this.element.getFloatAttribute("height")},parseEllipse:function(val){this.kind=PConstants.ELLIPSE;this.family=PConstants.PRIMITIVE;this.params=[];this.params[0]=this.element.getFloatAttribute("cx");this.params[1]=this.element.getFloatAttribute("cy");var rx,ry;if(val){rx=ry=this.element.getFloatAttribute("r")}else{rx=this.element.getFloatAttribute("rx");ry=this.element.getFloatAttribute("ry")}this.params[0]-=rx;this.params[1]-=ry;this.params[2]=rx*2;this.params[3]=ry*2},parseLine:function(){this.kind=PConstants.LINE;this.family=PConstants.PRIMITIVE;this.params=[];this.params[0]=this.element.getFloatAttribute("x1");this.params[1]=this.element.getFloatAttribute("y1");this.params[2]=this.element.getFloatAttribute("x2");this.params[3]=this.element.getFloatAttribute("y2")},parseColors:function(element){if(element.hasAttribute("opacity")){this.setOpacity(element.getAttribute("opacity"))}if(element.hasAttribute("stroke")){this.setStroke(element.getAttribute("stroke"))}if(element.hasAttribute("stroke-width")){this.setStrokeWeight(element.getAttribute("stroke-width"))}if(element.hasAttribute("stroke-linejoin")){this.setStrokeJoin(element.getAttribute("stroke-linejoin"))}if(element.hasAttribute("stroke-linecap")){this.setStrokeCap(element.getStringAttribute("stroke-linecap"))}if(element.hasAttribute("fill")){this.setFill(element.getStringAttribute("fill"))}if(element.hasAttribute("style")){var styleText=element.getStringAttribute("style");var styleTokens=styleText.toString().split(";");for(var i=0;i<styleTokens.length;i++){var tokens=p.trim(styleTokens[i].split(":"));switch(tokens[0]){case"fill":this.setFill(tokens[1]);break;case"fill-opacity":this.setFillOpacity(tokens[1]);break;case"stroke":this.setStroke(tokens[1]);break;case"stroke-width":this.setStrokeWeight(tokens[1]);break;case"stroke-linecap":this.setStrokeCap(tokens[1]);break;case"stroke-linejoin":this.setStrokeJoin(tokens[1]);break;case"stroke-opacity":this.setStrokeOpacity(tokens[1]);break;case"opacity":this.setOpacity(tokens[1]);break}}}},setFillOpacity:function(opacityText){this.fillOpacity=parseFloat(opacityText);this.fillColor=this.fillOpacity*255<<24|this.fillColor&16777215},setFill:function(fillText){var opacityMask=this.fillColor&4278190080;if(fillText==="none"){this.fill=false}else{if(fillText.indexOf("#")===0){this.fill=true;this.fillColor=opacityMask|(parseInt(fillText.substring(1),16))&16777215}else{if(fillText.indexOf("rgb")===0){this.fill=true;this.fillColor=opacityMask|this.parseRGB(fillText)}else{if(fillText.indexOf("url(#")===0){this.fillName=fillText.substring(5,fillText.length-1)}else{if(colors[fillText]){this.fill=true;this.fillColor=opacityMask|(parseInt(colors[fillText].substring(1),16))&16777215}}}}}},setOpacity:function(opacity){this.strokeColor=parseFloat(opacity)*255<<24|this.strokeColor&16777215;this.fillColor=parseFloat(opacity)*255<<24|this.fillColor&16777215},setStroke:function(strokeText){var opacityMask=this.strokeColor&4278190080;if(strokeText==="none"){this.stroke=false}else{if(strokeText.charAt(0)==="#"){this.stroke=true;this.strokeColor=opacityMask|(parseInt(strokeText.substring(1),16))&16777215}else{if(strokeText.indexOf("rgb")===0){this.stroke=true;this.strokeColor=opacityMask|this.parseRGB(strokeText)}else{if(strokeText.indexOf("url(#")===0){this.strokeName=strokeText.substring(5,strokeText.length-1)}else{if(colors[strokeText]){this.stroke=true;this.strokeColor=opacityMask|(parseInt(colors[strokeText].substring(1),16))&16777215}}}}}},setStrokeWeight:function(weight){this.strokeWeight=this.parseUnitSize(weight)},setStrokeJoin:function(linejoin){if(linejoin==="miter"){this.strokeJoin=PConstants.MITER}else{if(linejoin==="round"){this.strokeJoin=PConstants.ROUND}else{if(linejoin==="bevel"){this.strokeJoin=PConstants.BEVEL}}}},setStrokeCap:function(linecap){if(linecap==="butt"){this.strokeCap=PConstants.SQUARE}else{if(linecap==="round"){this.strokeCap=PConstants.ROUND}else{if(linecap==="square"){this.strokeCap=PConstants.PROJECT}}}},setStrokeOpacity:function(opacityText){this.strokeOpacity=parseFloat(opacityText);this.strokeColor=this.strokeOpacity*255<<24|this.strokeColor&16777215},parseRGB:function(color){var sub=color.substring(color.indexOf("(")+1,color.indexOf(")"));var values=sub.split(", ");return(values[0]<<16)|(values[1]<<8)|(values[2])},parseUnitSize:function(text){var len=text.length-2;if(len<0){return text}if(text.indexOf("pt")===len){return parseFloat(text.substring(0,len))*1.25}else{if(text.indexOf("pc")===len){return parseFloat(text.substring(0,len))*15}else{if(text.indexOf("mm")===len){return parseFloat(text.substring(0,len))*3.543307}else{if(text.indexOf("cm")===len){return parseFloat(text.substring(0,len))*35.43307}else{if(text.indexOf("in")===len){return parseFloat(text.substring(0,len))*90}else{if(text.indexOf("px")===len){return parseFloat(text.substring(0,len))}else{return parseFloat(text)}}}}}}}};p.shape=function(shape,x,y,width,height){if(arguments.length>=1&&arguments[0]!==null){if(shape.isVisible()){p.pushMatrix();if(curShapeMode===PConstants.CENTER){if(arguments.length===5){p.translate(x-width/2,y-height/2);p.scale(width/shape.getWidth(),height/shape.getHeight())}else{if(arguments.length===3){p.translate(x-shape.getWidth()/2,-shape.getHeight()/2)}else{p.translate(-shape.getWidth()/2,-shape.getHeight()/2)}}}else{if(curShapeMode===PConstants.CORNER){if(arguments.length===5){p.translate(x,y);p.scale(width/shape.getWidth(),height/shape.getHeight())}else{if(arguments.length===3){p.translate(x,y)}}}else{if(curShapeMode===PConstants.CORNERS){if(arguments.length===5){width-=x;height-=y;p.translate(x,y);p.scale(width/shape.getWidth(),height/shape.getHeight())}else{if(arguments.length===3){p.translate(x,y)}}}}}shape.draw();if((arguments.length===1&&curShapeMode===PConstants.CENTER)||arguments.length>1){p.popMatrix()}}}};p.shapeMode=function(mode){curShapeMode=mode};p.loadShape=function(filename){if(arguments.length===1){if(filename.indexOf(".svg")>-1){return new PShapeSVG(null,filename)}}return null};var XMLAttribute=function(fname,n,nameSpace,v,t){this.fullName=fname||"";this.name=n||"";this.namespace=nameSpace||"";this.value=v;this.type=t};XMLAttribute.prototype={getName:function(){return this.name},getFullName:function(){return this.fullName},getNamespace:function(){return this.namespace},getValue:function(){return this.value},getType:function(){return this.type},setValue:function(newval){this.value=newval}};var XMLElement=p.XMLElement=function(){if(arguments.length===4){this.attributes=[];this.children=[];this.fullName=arguments[0]||"";if(arguments[1]){this.name=arguments[1]}else{var index=this.fullName.indexOf(":");if(index>=0){this.name=this.fullName.substring(index+1)}else{this.name=this.fullName}}this.namespace=arguments[1];this.content="";this.lineNr=arguments[3];this.systemID=arguments[2];this.parent=null}else{if((arguments.length===2&&arguments[1].indexOf(".")>-1)){this.attributes=[];this.children=[];this.fullName="";this.name="";this.namespace="";this.content="";this.systemID="";this.lineNr="";this.parent=null;this.parse(arguments[arguments.length-1])}else{if(arguments.length===1&&typeof arguments[0]==="string"){this.attributes=[];this.children=[];this.fullName="";this.name="";this.namespace="";this.content="";this.systemID="";this.lineNr="";this.parent=null;this.parse(arguments[0])}else{this.attributes=[];this.children=[];this.fullName="";this.name="";this.namespace="";this.content="";this.systemID="";this.lineNr="";this.parent=null}}}return this};XMLElement.prototype={parse:function(filename){var xmlDoc;try{if(filename.indexOf(".xml")>-1||filename.indexOf(".svg")>-1){filename=ajax(filename)}xmlDoc=new DOMParser().parseFromString(filename,"text/xml");var elements=xmlDoc.documentElement;if(elements){this.parseChildrenRecursive(null,elements)}else{throw ("Error loading document")}return this}catch(e){throw (e)}},createElement:function(){if(arguments.length===2){return new XMLElement(arguments[0],arguments[1],null,null)}else{return new XMLElement(arguments[0],arguments[1],arguments[2],arguments[3])}},hasAttribute:function(name){return this.getAttribute(name)!==null},createPCDataElement:function(){return new XMLElement()},equals:function(object){if(typeof object==="Object"){return this.equalsXMLElement(object)}},equalsXMLElement:function(object){if(object instanceof XMLElement){if(this.name!==object.getLocalName){return false}if(this.attributes.length!==object.getAttributeCount()){return false}for(var i=0;i<this.attributes.length;i++){if(!object.hasAttribute(this.attributes[i].getName(),this.attributes[i].getNamespace())){return false}if(this.attributes[i].getValue()!==object.attributes[i].getValue()){return false}if(this.attributes[i].getType()!==object.attributes[i].getType()){return false}}if(this.children.length!==object.getChildCount()){return false}var child1,child2;for(i=0;i<this.children.length;i++){child1=this.getChildAtIndex(i);child2=object.getChildAtIndex(i);if(!child1.equalsXMLElement(child2)){return false}}return true}},getContent:function(){return this.content},getAttribute:function(){var attribute;if(arguments.length===2){attribute=this.findAttribute(arguments[0]);if(attribute){return attribute.getValue()}else{return arguments[1]}}else{if(arguments.length===1){attribute=this.findAttribute(arguments[0]);if(attribute){return attribute.getValue()}else{return null}}}},getStringAttribute:function(){if(arguments.length===1){return this.getAttribute(arguments[0])}else{if(arguments.length===2){return this.getAttribute(arguments[0],arguments[1])}else{return this.getAttribute(arguments[0],arguments[1],arguments[2])}}},getFloatAttribute:function(){if(arguments.length===1){return parseFloat(this.getAttribute(arguments[0],0))}else{if(arguments.length===2){return this.getAttribute(arguments[0],arguments[1])}else{return this.getAttribute(arguments[0],arguments[1],arguments[2])}}},getIntAttribute:function(){if(arguments.length===1){return this.getAttribute(arguments[0],0)}else{if(arguments.length===2){return this.getAttribute(arguments[0],arguments[1])}else{return this.getAttribute(arguments[0],arguments[1],arguments[2])}}},hasChildren:function(){return this.children.length>0},addChild:function(child){if(child!==null){child.parent=this;this.children.push(child)}},insertChild:function(child,index){if(child){if((child.getLocalName()===null)&&(!this.hasChildren())){var lastChild=this.children[this.children.length-1];if(lastChild.getLocalName()===null){lastChild.setContent(lastChild.getContent()+child.getContent());return}}child.parent=this;this.children.splice(index,0,child)}},getChild:function(index){if(typeof index==="number"){return this.children[index]}else{if(index.indexOf("/")!==-1){this.getChildRecursive(index.split("/"),0)}else{var kid,kidName;for(var i=0;i<this.getChildCount();i++){kid=this.getChild(i);kidName=kid.getName();if(kidName!==null&&kidName===index){return kid}}return null}}},getChildren:function(){if(arguments.length===1){if(typeof arguments[0]==="number"){return this.getChild(arguments[0])}else{if(arguments[0].indexOf("/")!==-1){return this.getChildrenRecursive(arguments[0].split("/"),0)}else{var matches=[];var kid,kidName;for(var i=0;i<this.getChildCount();i++){kid=this.getChild(i);kidName=kid.getName();if(kidName!==null&&kidName===arguments[0]){matches.push(kid)}}return matches}}}else{return this.children}},getChildCount:function(){return this.children.length},getChildRecursive:function(items,offset){var kid,kidName;for(var i=0;i<this.getChildCount();i++){kid=this.getChild(i);kidName=kid.getName();if(kidName!==null&&kidName===items[offset]){if(offset===items.length-1){return kid}else{offset+=1;return kid.getChildRecursive(items,offset)}}}return null},getChildrenRecursive:function(items,offset){if(offset===items.length-1){return this.getChildren(items[offset])}var matches=this.getChildren(items[offset]);var kidMatches;for(var i=0;i<matches.length;i++){kidMatches=matches[i].getChildrenRecursive(items,offset+1)}return kidMatches},parseChildrenRecursive:function(parent,elementpath){var xmlelement,xmlattribute,tmpattrib;if(!parent){this.fullName=elementpath.localName;this.name=elementpath.nodeName;this.content=elementpath.textContent||"";xmlelement=this}else{xmlelement=new XMLElement(elementpath.localName,elementpath.nodeName,"","");xmlelement.content=elementpath.textContent||"";xmlelement.parent=parent}for(var l=0;l<elementpath.attributes.length;l++){tmpattrib=elementpath.attributes[l];xmlattribute=new XMLAttribute(tmpattrib.getname,tmpattrib.nodeName,tmpattrib.namespaceURI,tmpattrib.nodeValue,tmpattrib.nodeType);xmlelement.attributes.push(xmlattribute)}for(var node in elementpath.childNodes){if(elementpath.childNodes[node].nodeType===1){xmlelement.children.push(xmlelement.parseChildrenRecursive(xmlelement,elementpath.childNodes[node]))}}return xmlelement},isLeaf:function(){return this.hasChildren()},listChildren:function(){var arr=[];for(var i=0;i<this.children.length;i++){arr.push(this.getChild(i).getName())}return arr},removeAttribute:function(name,namespace){this.namespace=namespace||"";for(var i=0;i<this.attributes.length;i++){if(this.attributes[i].getName()===name&&this.attributes[i].getNamespace()===this.namespace){this.attributes.splice(i,0)}}},removeChild:function(child){if(child){for(var i=0;i<this.children.length;i++){if(this.children[i].equalsXMLElement(child)){this.children.splice(i,0)}}}},removeChildAtIndex:function(index){if(this.children.length>index){this.children.splice(index,0)}},findAttribute:function(name,namespace){this.namespace=namespace||"";for(var i=0;i<this.attributes.length;i++){if(this.attributes[i].getName()===name&&this.attributes[i].getNamespace()===this.namespace){return this.attributes[i]}}},setAttribute:function(){var attr;if(arguments.length===3){var index=arguments[0].indexOf(":");var name=arguments[0].substring(index+1);attr=this.findAttribute(name,arguments[1]);if(attr){attr.setValue(arguments[2])}else{attr=new XMLAttribute(arguments[0],name,arguments[1],arguments[2],"CDATA");this.attributes.addElement(attr)}}else{attr=this.findAttribute(arguments[0]);if(attr){attr.setValue(arguments[1])}else{attr=new XMLAttribute(arguments[0],arguments[0],null,arguments[1],"CDATA");this.attributes.addElement(attr)}}},setContent:function(content){this.content=content},setName:function(){if(arguments.length===1){this.name=arguments[0];this.fullName=arguments[0];this.namespace=arguments[0]}else{var index=arguments[0].indexOf(":");if((arguments[1]===null)||(index<0)){this.name=arguments[0]}else{this.name=arguments[0].substring(index+1)}this.fullName=arguments[0];this.namespace=arguments[1]}},getName:function(){return this.fullName}};var printMatrixHelper=function printMatrixHelper(elements){var big=0;for(var i=0;i<elements.length;i++){if(i!==0){big=Math.max(big,Math.abs(elements[i]))}else{big=Math.abs(elements[i])}}var digits=(big+"").indexOf(".");if(digits===0){digits=1}else{if(digits===-1){digits=(big+"").length}}return digits};var PMatrix2D=p.PMatrix2D=function(){if(arguments.length===0){this.reset()}else{if(arguments.length===1&&arguments[0] instanceof PMatrix2D){this.set(arguments[0].array())}else{if(arguments.length===6){this.set(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])}}}};PMatrix2D.prototype={set:function(){if(arguments.length===6){var a=arguments;this.set([a[0],a[1],a[2],a[3],a[4],a[5]])}else{if(arguments.length===1&&arguments[0] instanceof PMatrix2D){this.elements=arguments[0].array()}else{if(arguments.length===1&&arguments[0] instanceof Array){this.elements=arguments[0].slice()}}}},get:function(){var outgoing=new PMatrix2D();outgoing.set(this.elements);return outgoing},reset:function(){this.set([1,0,0,0,1,0])},array:function array(){return this.elements.slice()},translate:function(tx,ty){this.elements[2]=tx*this.elements[0]+ty*this.elements[1]+this.elements[2];this.elements[5]=tx*this.elements[3]+ty*this.elements[4]+this.elements[5]},transpose:function(){},mult:function(source,target){var x,y;if(source instanceof PVector){x=source.x;y=source.y;if(!target){target=new PVector()}}else{if(source instanceof Array){x=source[0];y=source[1];if(!target){target=[]}}}if(target instanceof Array){target[0]=this.elements[0]*x+this.elements[1]*y+this.elements[2];target[1]=this.elements[3]*x+this.elements[4]*y+this.elements[5]}else{if(target instanceof PVector){target.x=this.elements[0]*x+this.elements[1]*y+this.elements[2];target.y=this.elements[3]*x+this.elements[4]*y+this.elements[5];target.z=0}}return target},multX:function(x,y){return(x*this.elements[0]+y*this.elements[1]+this.elements[2])},multY:function(x,y){return(x*this.elements[3]+y*this.elements[4]+this.elements[5])},skewX:function(angle){this.apply(1,0,1,angle,0,0)},skewY:function(angle){this.apply(1,0,1,0,angle,0)},determinant:function(){return(this.elements[0]*this.elements[4]-this.elements[1]*this.elements[3])},invert:function(){var d=this.determinant();if(Math.abs(d)>PConstants.FLOAT_MIN){var old00=this.elements[0];var old01=this.elements[1];var old02=this.elements[2];var old10=this.elements[3];var old11=this.elements[4];var old12=this.elements[5];this.elements[0]=old11/d;this.elements[3]=-old10/d;this.elements[1]=-old01/d;this.elements[1]=old00/d;this.elements[2]=(old01*old12-old11*old02)/d;this.elements[5]=(old10*old02-old00*old12)/d;return true}return false},scale:function(sx,sy){if(sx&&!sy){sy=sx}if(sx&&sy){this.elements[0]*=sx;this.elements[1]*=sy;this.elements[3]*=sx;this.elements[4]*=sy}},apply:function(){var source;if(arguments.length===1&&arguments[0] instanceof PMatrix2D){source=arguments[0].array()}else{if(arguments.length===6){source=Array.prototype.slice.call(arguments)}else{if(arguments.length===1&&arguments[0] instanceof Array){source=arguments[0]}}}var result=[0,0,this.elements[2],0,0,this.elements[5]];var e=0;for(var row=0;row<2;row++){for(var col=0;col<3;col++,e++){result[e]+=this.elements[row*3+0]*source[col+0]+this.elements[row*3+1]*source[col+3]}}this.elements=result.slice()},preApply:function(){var source;if(arguments.length===1&&arguments[0] instanceof PMatrix2D){source=arguments[0].array()}else{if(arguments.length===6){source=Array.prototype.slice.call(arguments)}else{if(arguments.length===1&&arguments[0] instanceof Array){source=arguments[0]}}}var result=[0,0,source[2],0,0,source[5]];result[2]=source[2]+this.elements[2]*source[0]+this.elements[5]*source[1];result[5]=source[5]+this.elements[2]*source[3]+this.elements[5]*source[4];result[0]=this.elements[0]*source[0]+this.elements[3]*source[1];result[3]=this.elements[0]*source[3]+this.elements[3]*source[4];result[1]=this.elements[1]*source[0]+this.elements[4]*source[1];result[4]=this.elements[1]*source[3]+this.elements[4]*source[4];this.elements=result.slice()},rotate:function(angle){var c=Math.cos(angle);var s=Math.sin(angle);var temp1=this.elements[0];var temp2=this.elements[1];this.elements[0]=c*temp1+s*temp2;this.elements[1]=-s*temp1+c*temp2;temp1=this.elements[3];temp2=this.elements[4];this.elements[3]=c*temp1+s*temp2;this.elements[4]=-s*temp1+c*temp2},rotateZ:function(angle){this.rotate(angle)},print:function(){var digits=printMatrixHelper(this.elements);var output=""+p.nfs(this.elements[0],digits,4)+" "+p.nfs(this.elements[1],digits,4)+" "+p.nfs(this.elements[2],digits,4)+"\n"+p.nfs(this.elements[3],digits,4)+" "+p.nfs(this.elements[4],digits,4)+" "+p.nfs(this.elements[5],digits,4)+"\n\n";p.println(output)}};var PMatrix3D=p.PMatrix3D=function PMatrix3D(){this.reset()};PMatrix3D.prototype={set:function(){if(arguments.length===16){this.elements=Array.prototype.slice.call(arguments)}else{if(arguments.length===1&&arguments[0] instanceof PMatrix3D){this.elements=arguments[0].array()}else{if(arguments.length===1&&arguments[0] instanceof Array){this.elements=arguments[0].slice()}}}},get:function(){var outgoing=new PMatrix3D();outgoing.set(this.elements);return outgoing},reset:function(){this.set([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])},array:function array(){return this.elements.slice()},translate:function(tx,ty,tz){if(tz===undef){tz=0}this.elements[3]+=tx*this.elements[0]+ty*this.elements[1]+tz*this.elements[2];this.elements[7]+=tx*this.elements[4]+ty*this.elements[5]+tz*this.elements[6];this.elements[11]+=tx*this.elements[8]+ty*this.elements[9]+tz*this.elements[10];this.elements[15]+=tx*this.elements[12]+ty*this.elements[13]+tz*this.elements[14]},transpose:function(){var temp=this.elements.slice();this.elements[0]=temp[0];this.elements[1]=temp[4];this.elements[2]=temp[8];this.elements[3]=temp[12];this.elements[4]=temp[1];this.elements[5]=temp[5];this.elements[6]=temp[9];this.elements[7]=temp[13];this.elements[8]=temp[2];this.elements[9]=temp[6];this.elements[10]=temp[10];this.elements[11]=temp[14];this.elements[12]=temp[3];this.elements[13]=temp[7];this.elements[14]=temp[11];this.elements[15]=temp[15]},mult:function(source,target){var x,y,z,w;if(source instanceof PVector){x=source.x;y=source.y;z=source.z;w=1;if(!target){target=new PVector()}}else{if(source instanceof Array){x=source[0];y=source[1];z=source[2];w=source[3]||1;if(!target||target.length!==3&&target.length!==4){target=[0,0,0]}}}if(target instanceof Array){if(target.length===3){target[0]=this.elements[0]*x+this.elements[1]*y+this.elements[2]*z+this.elements[3];target[1]=this.elements[4]*x+this.elements[5]*y+this.elements[6]*z+this.elements[7];target[2]=this.elements[8]*x+this.elements[9]*y+this.elements[10]*z+this.elements[11]}else{if(target.length===4){target[0]=this.elements[0]*x+this.elements[1]*y+this.elements[2]*z+this.elements[3]*w;target[1]=this.elements[4]*x+this.elements[5]*y+this.elements[6]*z+this.elements[7]*w;target[2]=this.elements[8]*x+this.elements[9]*y+this.elements[10]*z+this.elements[11]*w;target[3]=this.elements[12]*x+this.elements[13]*y+this.elements[14]*z+this.elements[15]*w}}}if(target instanceof PVector){target.x=this.elements[0]*x+this.elements[1]*y+this.elements[2]*z+this.elements[3];target.y=this.elements[4]*x+this.elements[5]*y+this.elements[6]*z+this.elements[7];target.z=this.elements[8]*x+this.elements[9]*y+this.elements[10]*z+this.elements[11]}return target},preApply:function(){var source;if(arguments.length===1&&arguments[0] instanceof PMatrix3D){source=arguments[0].array()}else{if(arguments.length===16){source=Array.prototype.slice.call(arguments)}else{if(arguments.length===1&&arguments[0] instanceof Array){source=arguments[0]}}}var result=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];var e=0;for(var row=0;row<4;row++){for(var col=0;col<4;col++,e++){result[e]+=this.elements[col+0]*source[row*4+0]+this.elements[col+4]*source[row*4+1]+this.elements[col+8]*source[row*4+2]+this.elements[col+12]*source[row*4+3]}}this.elements=result.slice()},apply:function(){var source;if(arguments.length===1&&arguments[0] instanceof PMatrix3D){source=arguments[0].array()}else{if(arguments.length===16){source=Array.prototype.slice.call(arguments)}else{if(arguments.length===1&&arguments[0] instanceof Array){source=arguments[0]}}}var result=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];var e=0;for(var row=0;row<4;row++){for(var col=0;col<4;col++,e++){result[e]+=this.elements[row*4+0]*source[col+0]+this.elements[row*4+1]*source[col+4]+this.elements[row*4+2]*source[col+8]+this.elements[row*4+3]*source[col+12]}}this.elements=result.slice()},rotate:function(angle,v0,v1,v2){if(!v1){this.rotateZ(angle)}else{var c=p.cos(angle);var s=p.sin(angle);var t=1-c;this.apply((t*v0*v0)+c,(t*v0*v1)-(s*v2),(t*v0*v2)+(s*v1),0,(t*v0*v1)+(s*v2),(t*v1*v1)+c,(t*v1*v2)-(s*v0),0,(t*v0*v2)-(s*v1),(t*v1*v2)+(s*v0),(t*v2*v2)+c,0,0,0,0,1)}},invApply:function(){if(inverseCopy===undef){inverseCopy=new PMatrix3D()}var a=arguments;inverseCopy.set(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]);if(!inverseCopy.invert()){return false}this.preApply(inverseCopy);return true},rotateX:function(angle){var c=p.cos(angle);var s=p.sin(angle);this.apply([1,0,0,0,0,c,-s,0,0,s,c,0,0,0,0,1])},rotateY:function(angle){var c=p.cos(angle);var s=p.sin(angle);this.apply([c,0,s,0,0,1,0,0,-s,0,c,0,0,0,0,1])},rotateZ:function(angle){var c=Math.cos(angle);var s=Math.sin(angle);this.apply([c,-s,0,0,s,c,0,0,0,0,1,0,0,0,0,1])},scale:function(sx,sy,sz){if(sx&&!sy&&!sz){sy=sz=sx}else{if(sx&&sy&&!sz){sz=1}}if(sx&&sy&&sz){this.elements[0]*=sx;this.elements[1]*=sy;this.elements[2]*=sz;this.elements[4]*=sx;this.elements[5]*=sy;this.elements[6]*=sz;this.elements[8]*=sx;this.elements[9]*=sy;this.elements[10]*=sz;this.elements[12]*=sx;this.elements[13]*=sy;this.elements[14]*=sz}},skewX:function(angle){var t=Math.tan(angle);this.apply(1,t,0,0,0,1,0,0,0,0,1,0,0,0,0,1)},skewY:function(angle){var t=Math.tan(angle);this.apply(1,0,0,0,t,1,0,0,0,0,1,0,0,0,0,1)},multX:function(x,y,z,w){if(!z){return this.elements[0]*x+this.elements[1]*y+this.elements[3]}else{if(!w){return this.elements[0]*x+this.elements[1]*y+this.elements[2]*z+this.elements[3]}else{return this.elements[0]*x+this.elements[1]*y+this.elements[2]*z+this.elements[3]*w}}},multY:function(x,y,z,w){if(!z){return this.elements[4]*x+this.elements[5]*y+this.elements[7]}else{if(!w){return this.elements[4]*x+this.elements[5]*y+this.elements[6]*z+this.elements[7]}else{return this.elements[4]*x+this.elements[5]*y+this.elements[6]*z+this.elements[7]*w}}},multZ:function(x,y,z,w){if(!w){return this.elements[8]*x+this.elements[9]*y+this.elements[10]*z+this.elements[11]}else{return this.elements[8]*x+this.elements[9]*y+this.elements[10]*z+this.elements[11]*w}},multW:function(x,y,z,w){if(!w){return this.elements[12]*x+this.elements[13]*y+this.elements[14]*z+this.elements[15]}else{return this.elements[12]*x+this.elements[13]*y+this.elements[14]*z+this.elements[15]*w}},invert:function(){var fA0=this.elements[0]*this.elements[5]-this.elements[1]*this.elements[4];var fA1=this.elements[0]*this.elements[6]-this.elements[2]*this.elements[4];var fA2=this.elements[0]*this.elements[7]-this.elements[3]*this.elements[4];var fA3=this.elements[1]*this.elements[6]-this.elements[2]*this.elements[5];var fA4=this.elements[1]*this.elements[7]-this.elements[3]*this.elements[5];var fA5=this.elements[2]*this.elements[7]-this.elements[3]*this.elements[6];var fB0=this.elements[8]*this.elements[13]-this.elements[9]*this.elements[12];var fB1=this.elements[8]*this.elements[14]-this.elements[10]*this.elements[12];var fB2=this.elements[8]*this.elements[15]-this.elements[11]*this.elements[12];var fB3=this.elements[9]*this.elements[14]-this.elements[10]*this.elements[13];var fB4=this.elements[9]*this.elements[15]-this.elements[11]*this.elements[13];var fB5=this.elements[10]*this.elements[15]-this.elements[11]*this.elements[14];var fDet=fA0*fB5-fA1*fB4+fA2*fB3+fA3*fB2-fA4*fB1+fA5*fB0;if(Math.abs(fDet)<=1e-9){return false}var kInv=[];kInv[0]=+this.elements[5]*fB5-this.elements[6]*fB4+this.elements[7]*fB3;kInv[4]=-this.elements[4]*fB5+this.elements[6]*fB2-this.elements[7]*fB1;kInv[8]=+this.elements[4]*fB4-this.elements[5]*fB2+this.elements[7]*fB0;kInv[12]=-this.elements[4]*fB3+this.elements[5]*fB1-this.elements[6]*fB0;kInv[1]=-this.elements[1]*fB5+this.elements[2]*fB4-this.elements[3]*fB3;kInv[5]=+this.elements[0]*fB5-this.elements[2]*fB2+this.elements[3]*fB1;kInv[9]=-this.elements[0]*fB4+this.elements[1]*fB2-this.elements[3]*fB0;kInv[13]=+this.elements[0]*fB3-this.elements[1]*fB1+this.elements[2]*fB0;kInv[2]=+this.elements[13]*fA5-this.elements[14]*fA4+this.elements[15]*fA3;kInv[6]=-this.elements[12]*fA5+this.elements[14]*fA2-this.elements[15]*fA1;kInv[10]=+this.elements[12]*fA4-this.elements[13]*fA2+this.elements[15]*fA0;kInv[14]=-this.elements[12]*fA3+this.elements[13]*fA1-this.elements[14]*fA0;kInv[3]=-this.elements[9]*fA5+this.elements[10]*fA4-this.elements[11]*fA3;kInv[7]=+this.elements[8]*fA5-this.elements[10]*fA2+this.elements[11]*fA1;kInv[11]=-this.elements[8]*fA4+this.elements[9]*fA2-this.elements[11]*fA0;kInv[15]=+this.elements[8]*fA3-this.elements[9]*fA1+this.elements[10]*fA0;var fInvDet=1/fDet;kInv[0]*=fInvDet;kInv[1]*=fInvDet;kInv[2]*=fInvDet;kInv[3]*=fInvDet;kInv[4]*=fInvDet;kInv[5]*=fInvDet;kInv[6]*=fInvDet;kInv[7]*=fInvDet;kInv[8]*=fInvDet;kInv[9]*=fInvDet;kInv[10]*=fInvDet;kInv[11]*=fInvDet;kInv[12]*=fInvDet;kInv[13]*=fInvDet;kInv[14]*=fInvDet;kInv[15]*=fInvDet;this.elements=kInv.slice();return true},toString:function(){var str="";for(var i=0;i<15;i++){str+=this.elements[i]+", "}str+=this.elements[15];return str},print:function(){var digits=printMatrixHelper(this.elements);var output=""+p.nfs(this.elements[0],digits,4)+" "+p.nfs(this.elements[1],digits,4)+" "+p.nfs(this.elements[2],digits,4)+" "+p.nfs(this.elements[3],digits,4)+"\n"+p.nfs(this.elements[4],digits,4)+" "+p.nfs(this.elements[5],digits,4)+" "+p.nfs(this.elements[6],digits,4)+" "+p.nfs(this.elements[7],digits,4)+"\n"+p.nfs(this.elements[8],digits,4)+" "+p.nfs(this.elements[9],digits,4)+" "+p.nfs(this.elements[10],digits,4)+" "+p.nfs(this.elements[11],digits,4)+"\n"+p.nfs(this.elements[12],digits,4)+" "+p.nfs(this.elements[13],digits,4)+" "+p.nfs(this.elements[14],digits,4)+" "+p.nfs(this.elements[15],digits,4)+"\n\n";p.println(output)},invTranslate:function(tx,ty,tz){this.preApply(1,0,0,-tx,0,1,0,-ty,0,0,1,-tz,0,0,0,1)},invRotateX:function(angle){var c=Math.cos(-angle);var s=Math.sin(-angle);this.preApply([1,0,0,0,0,c,-s,0,0,s,c,0,0,0,0,1])},invRotateY:function(angle){var c=Math.cos(-angle);var s=Math.sin(-angle);this.preApply([c,0,s,0,0,1,0,0,-s,0,c,0,0,0,0,1])},invRotateZ:function(angle){var c=Math.cos(-angle);var s=Math.sin(-angle);this.preApply([c,-s,0,0,s,c,0,0,0,0,1,0,0,0,0,1])},invScale:function(x,y,z){this.preApply([1/x,0,0,0,0,1/y,0,0,0,0,1/z,0,0,0,0,1])}};var PMatrixStack=p.PMatrixStack=function PMatrixStack(){this.matrixStack=[]};PMatrixStack.prototype.load=function load(){var tmpMatrix;if(p.use3DContext){tmpMatrix=new PMatrix3D()}else{tmpMatrix=new PMatrix2D()}if(arguments.length===1){tmpMatrix.set(arguments[0])}else{tmpMatrix.set(arguments)}this.matrixStack.push(tmpMatrix)};PMatrixStack.prototype.push=function push(){this.matrixStack.push(this.peek())};PMatrixStack.prototype.pop=function pop(){return this.matrixStack.pop()};PMatrixStack.prototype.peek=function peek(){var tmpMatrix;if(p.use3DContext){tmpMatrix=new PMatrix3D()}else{tmpMatrix=new PMatrix2D()}tmpMatrix.set(this.matrixStack[this.matrixStack.length-1]);return tmpMatrix};PMatrixStack.prototype.mult=function mult(matrix){this.matrixStack[this.matrixStack.length-1].apply(matrix)};p.split=function(str,delim){return str.split(delim)};p.splitTokens=function(str,tokens){if(arguments.length===1){tokens="\n\t\r\f "}tokens="["+tokens+"]";var ary=[];var index=0;var pos=str.search(tokens);while(pos>=0){if(pos===0){str=str.substring(1)}else{ary[index]=str.substring(0,pos);index++;str=str.substring(pos)}pos=str.search(tokens)}if(str.length>0){ary[index]=str}if(ary.length===0){ary=undef}return ary};p.append=function(array,element){array[array.length]=element;return array};p.concat=function(array1,array2){return array1.concat(array2)};p.sort=function(array,numElem){var ret=[];if(array.length>0){var elemsToCopy=numElem>0?numElem:array.length;for(var i=0;i<elemsToCopy;i++){ret.push(array[i])}if(typeof array[0]==="string"){ret.sort()}else{ret.sort(function(a,b){return a-b})}if(numElem>0){for(var j=ret.length;j<array.length;j++){ret.push(array[j])}}}return ret};p.splice=function(array,value,index){if(value.length===0){return array}if(value instanceof Array){for(var i=0,j=index;i<value.length;j++,i++){array.splice(j,0,value[i])}}else{array.splice(index,0,value)}return array};p.subset=function(array,offset,length){if(arguments.length===2){return array.slice(offset,array.length-offset)}else{if(arguments.length===3){return array.slice(offset,offset+length)}}};p.join=function(array,seperator){return array.join(seperator)};p.shorten=function(ary){var newary=[];var len=ary.length;for(var i=0;i<len;i++){newary[i]=ary[i]}newary.pop();return newary};p.expand=function(ary,newSize){var temp=ary.slice(0);if(arguments.length===1){temp.length=ary.length*2;return temp}else{if(arguments.length===2){temp.length=newSize;return temp}}};p.arrayCopy=function(){var src,srcPos=0,dest,destPos=0,length;if(arguments.length===2){src=arguments[0];dest=arguments[1];length=src.length}else{if(arguments.length===3){src=arguments[0];dest=arguments[1];length=arguments[2]}else{if(arguments.length===5){src=arguments[0];srcPos=arguments[1];dest=arguments[2];destPos=arguments[3];length=arguments[4]}}}for(var i=srcPos,j=destPos;i<length+srcPos;i++,j++){if(dest[j]!==undef){dest[j]=src[i]}else{throw"array index out of bounds exception"}}};p.reverse=function(array){return array.reverse()};p.mix=function(a,b,f){return a+(((b-a)*f)>>8)};p.peg=function(n){return(n<0)?0:((n>255)?255:n)};p.modes={replace:function(c1,c2){return c2},blend:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|p.mix(c1&PConstants.RED_MASK,c2&PConstants.RED_MASK,f)&PConstants.RED_MASK|p.mix(c1&PConstants.GREEN_MASK,c2&PConstants.GREEN_MASK,f)&PConstants.GREEN_MASK|p.mix(c1&PConstants.BLUE_MASK,c2&PConstants.BLUE_MASK,f))},add:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|Math.min(((c1&PConstants.RED_MASK)+((c2&PConstants.RED_MASK)>>8)*f),PConstants.RED_MASK)&PConstants.RED_MASK|Math.min(((c1&PConstants.GREEN_MASK)+((c2&PConstants.GREEN_MASK)>>8)*f),PConstants.GREEN_MASK)&PConstants.GREEN_MASK|Math.min((c1&PConstants.BLUE_MASK)+(((c2&PConstants.BLUE_MASK)*f)>>8),PConstants.BLUE_MASK))},subtract:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|Math.max(((c1&PConstants.RED_MASK)-((c2&PConstants.RED_MASK)>>8)*f),PConstants.GREEN_MASK)&PConstants.RED_MASK|Math.max(((c1&PConstants.GREEN_MASK)-((c2&PConstants.GREEN_MASK)>>8)*f),PConstants.BLUE_MASK)&PConstants.GREEN_MASK|Math.max((c1&PConstants.BLUE_MASK)-(((c2&PConstants.BLUE_MASK)*f)>>8),0))},lightest:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|Math.max(c1&PConstants.RED_MASK,((c2&PConstants.RED_MASK)>>8)*f)&PConstants.RED_MASK|Math.max(c1&PConstants.GREEN_MASK,((c2&PConstants.GREEN_MASK)>>8)*f)&PConstants.GREEN_MASK|Math.max(c1&PConstants.BLUE_MASK,((c2&PConstants.BLUE_MASK)*f)>>8))},darkest:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|p.mix(c1&PConstants.RED_MASK,Math.min(c1&PConstants.RED_MASK,((c2&PConstants.RED_MASK)>>8)*f),f)&PConstants.RED_MASK|p.mix(c1&PConstants.GREEN_MASK,Math.min(c1&PConstants.GREEN_MASK,((c2&PConstants.GREEN_MASK)>>8)*f),f)&PConstants.GREEN_MASK|p.mix(c1&PConstants.BLUE_MASK,Math.min(c1&PConstants.BLUE_MASK,((c2&PConstants.BLUE_MASK)*f)>>8),f))},difference:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=(ar>br)?(ar-br):(br-ar);var cg=(ag>bg)?(ag-bg):(bg-ag);var cb=(ab>bb)?(ab-bb):(bb-ab);return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},exclusion:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=ar+br-((ar*br)>>7);var cg=ag+bg-((ag*bg)>>7);var cb=ab+bb-((ab*bb)>>7);return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},multiply:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=(ar*br)>>8;var cg=(ag*bg)>>8;var cb=(ab*bb)>>8;return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},screen:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=255-(((255-ar)*(255-br))>>8);var cg=255-(((255-ag)*(255-bg))>>8);var cb=255-(((255-ab)*(255-bb))>>8);return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},hard_light:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=(br<128)?((ar*br)>>7):(255-(((255-ar)*(255-br))>>7));var cg=(bg<128)?((ag*bg)>>7):(255-(((255-ag)*(255-bg))>>7));var cb=(bb<128)?((ab*bb)>>7):(255-(((255-ab)*(255-bb))>>7));return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},soft_light:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=((ar*br)>>7)+((ar*ar)>>8)-((ar*ar*br)>>15);var cg=((ag*bg)>>7)+((ag*ag)>>8)-((ag*ag*bg)>>15);var cb=((ab*bb)>>7)+((ab*ab)>>8)-((ab*ab*bb)>>15);return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},overlay:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=(ar<128)?((ar*br)>>7):(255-(((255-ar)*(255-br))>>7));var cg=(ag<128)?((ag*bg)>>7):(255-(((255-ag)*(255-bg))>>7));var cb=(ab<128)?((ab*bb)>>7):(255-(((255-ab)*(255-bb))>>7));return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},dodge:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=(br===255)?255:p.peg((ar<<8)/(255-br));var cg=(bg===255)?255:p.peg((ag<<8)/(255-bg));var cb=(bb===255)?255:p.peg((ab<<8)/(255-bb));return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))},burn:function(c1,c2){var f=(c2&PConstants.ALPHA_MASK)>>>24;var ar=(c1&PConstants.RED_MASK)>>16;var ag=(c1&PConstants.GREEN_MASK)>>8;var ab=(c1&PConstants.BLUE_MASK);var br=(c2&PConstants.RED_MASK)>>16;var bg=(c2&PConstants.GREEN_MASK)>>8;var bb=(c2&PConstants.BLUE_MASK);var cr=(br===0)?0:255-p.peg(((255-ar)<<8)/br);var cg=(bg===0)?0:255-p.peg(((255-ag)<<8)/bg);var cb=(bb===0)?0:255-p.peg(((255-ab)<<8)/bb);return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|(p.peg(ar+(((cr-ar)*f)>>8))<<16)|(p.peg(ag+(((cg-ag)*f)>>8))<<8)|(p.peg(ab+(((cb-ab)*f)>>8))))}};function color$4(aValue1,aValue2,aValue3,aValue4){var r,g,b,a;if(curColorMode===PConstants.HSB){var rgb=p.color.toRGB(aValue1,aValue2,aValue3);r=rgb[0];g=rgb[1];b=rgb[2]}else{r=Math.round(255*(aValue1/colorModeX));g=Math.round(255*(aValue2/colorModeY));b=Math.round(255*(aValue3/colorModeZ))}a=Math.round(255*(aValue4/colorModeA));r=(r>255)?255:r;g=(g>255)?255:g;b=(b>255)?255:b;a=(a>255)?255:a;return(a<<24)&PConstants.ALPHA_MASK|(r<<16)&PConstants.RED_MASK|(g<<8)&PConstants.GREEN_MASK|b&PConstants.BLUE_MASK}function color$2(aValue1,aValue2){var a;if(aValue1&PConstants.ALPHA_MASK){a=Math.round(255*(aValue2/colorModeA));a=(a>255)?255:a;return aValue1-(aValue1&PConstants.ALPHA_MASK)+((a<<24)&PConstants.ALPHA_MASK)}else{if(curColorMode===PConstants.RGB){return color$4(aValue1,aValue1,aValue1,aValue2)}else{if(curColorMode===PConstants.HSB){return color$4(0,0,(aValue1/colorModeX)*colorModeZ,aValue2)}}}}function color$1(aValue1){if(aValue1<=colorModeX&&aValue1>=0){if(curColorMode===PConstants.RGB){return color$4(aValue1,aValue1,aValue1,colorModeA)}else{if(curColorMode===PConstants.HSB){return color$4(0,0,(aValue1/colorModeX)*colorModeZ,colorModeA)}}}else{if(aValue1){return aValue1}}}p.color=function color(aValue1,aValue2,aValue3,aValue4){if(aValue1!==undef&&aValue2!==undef&&aValue3!==undef&&aValue4!==undef){return color$4(aValue1,aValue2,aValue3,aValue4)}else{if(aValue1!==undef&&aValue2!==undef&&aValue3!==undef){return color$4(aValue1,aValue2,aValue3,colorModeA)}else{if(aValue1!==undef&&aValue2!==undef){return color$2(aValue1,aValue2)}else{if(typeof aValue1==="number"){return color$1(aValue1)}else{return color$4(colorModeX,colorModeY,colorModeZ,colorModeA)}}}}};p.color.toString=function(colorInt){return"rgba("+((colorInt&PConstants.RED_MASK)>>>16)+","+((colorInt&PConstants.GREEN_MASK)>>>8)+","+((colorInt&PConstants.BLUE_MASK))+","+((colorInt&PConstants.ALPHA_MASK)>>>24)/255+")"};p.color.toInt=function(r,g,b,a){return(a<<24)&PConstants.ALPHA_MASK|(r<<16)&PConstants.RED_MASK|(g<<8)&PConstants.GREEN_MASK|b&PConstants.BLUE_MASK};p.color.toArray=function(colorInt){return[(colorInt&PConstants.RED_MASK)>>>16,(colorInt&PConstants.GREEN_MASK)>>>8,colorInt&PConstants.BLUE_MASK,(colorInt&PConstants.ALPHA_MASK)>>>24]};p.color.toGLArray=function(colorInt){return[((colorInt&PConstants.RED_MASK)>>>16)/255,((colorInt&PConstants.GREEN_MASK)>>>8)/255,(colorInt&PConstants.BLUE_MASK)/255,((colorInt&PConstants.ALPHA_MASK)>>>24)/255]};p.color.toRGB=function(h,s,b){h=(h>colorModeX)?colorModeX:h;s=(s>colorModeY)?colorModeY:s;b=(b>colorModeZ)?colorModeZ:b;h=(h/colorModeX)*360;s=(s/colorModeY)*100;b=(b/colorModeZ)*100;var br=Math.round(b/100*255);if(s===0){return[br,br,br]}else{var hue=h%360;var f=hue%60;var p=Math.round((b*(100-s))/10000*255);var q=Math.round((b*(6000-s*f))/600000*255);var t=Math.round((b*(6000-s*(60-f)))/600000*255);switch(Math.floor(hue/60)){case 0:return[br,t,p];case 1:return[q,br,p];case 2:return[p,br,t];case 3:return[p,q,br];case 4:return[t,p,br];case 5:return[br,p,q]}}};p.color.toHSB=function(colorInt){var red,green,blue;red=((colorInt&PConstants.RED_MASK)>>>16)/255;green=((colorInt&PConstants.GREEN_MASK)>>>8)/255;blue=(colorInt&PConstants.BLUE_MASK)/255;var max=p.max(p.max(red,green),blue),min=p.min(p.min(red,green),blue),hue,saturation;if(min===max){return[0,0,max]}else{saturation=(max-min)/max;if(red===max){hue=(green-blue)/(max-min)}else{if(green===max){hue=2+((blue-red)/(max-min))}else{hue=4+((red-green)/(max-min))}}hue/=6;if(hue<0){hue+=1}else{if(hue>1){hue-=1}}}return[hue*colorModeX,saturation*colorModeY,max*colorModeZ]};p.brightness=function(colInt){return p.color.toHSB(colInt)[2]};p.saturation=function(colInt){return p.color.toHSB(colInt)[1]};p.hue=function(colInt){return p.color.toHSB(colInt)[0]};var verifyChannel=function verifyChannel(aColor){if(aColor.constructor===Array){return aColor}else{return p.color(aColor)}};p.red=function(aColor){return((aColor&PConstants.RED_MASK)>>>16)/255*colorModeX};p.green=function(aColor){return((aColor&PConstants.GREEN_MASK)>>>8)/255*colorModeY};p.blue=function(aColor){return(aColor&PConstants.BLUE_MASK)/255*colorModeZ};p.alpha=function(aColor){return((aColor&PConstants.ALPHA_MASK)>>>24)/255*colorModeA};p.lerpColor=function lerpColor(c1,c2,amt){var colorBits1=p.color(c1);var r1=(colorBits1&PConstants.RED_MASK)>>>16;var g1=(colorBits1&PConstants.GREEN_MASK)>>>8;var b1=(colorBits1&PConstants.BLUE_MASK);var a1=((colorBits1&PConstants.ALPHA_MASK)>>>24)/colorModeA;var colorBits2=p.color(c2);var r2=(colorBits2&PConstants.RED_MASK)>>>16;var g2=(colorBits2&PConstants.GREEN_MASK)>>>8;var b2=(colorBits2&PConstants.BLUE_MASK);var a2=((colorBits2&PConstants.ALPHA_MASK)>>>24)/colorModeA;var r=parseInt(p.lerp(r1,r2,amt),10);var g=parseInt(p.lerp(g1,g2,amt),10);var b=parseInt(p.lerp(b1,b2,amt),10);var a=parseFloat(p.lerp(a1,a2,amt)*colorModeA);return p.color.toInt(r,g,b,a)};p.defaultColor=function(aValue1,aValue2,aValue3){var tmpColorMode=curColorMode;curColorMode=PConstants.RGB;var c=p.color(aValue1/255*colorModeX,aValue2/255*colorModeY,aValue3/255*colorModeZ);curColorMode=tmpColorMode;return c};p.colorMode=function colorMode(){curColorMode=arguments[0];if(arguments.length>1){colorModeX=arguments[1];colorModeY=arguments[2]||arguments[1];colorModeZ=arguments[3]||arguments[1];colorModeA=arguments[4]||arguments[1]}};p.blendColor=function(c1,c2,mode){var color=0;switch(mode){case PConstants.REPLACE:color=p.modes.replace(c1,c2);break;case PConstants.BLEND:color=p.modes.blend(c1,c2);break;case PConstants.ADD:color=p.modes.add(c1,c2);break;case PConstants.SUBTRACT:color=p.modes.subtract(c1,c2);break;case PConstants.LIGHTEST:color=p.modes.lightest(c1,c2);break;case PConstants.DARKEST:color=p.modes.darkest(c1,c2);break;case PConstants.DIFFERENCE:color=p.modes.difference(c1,c2);break;case PConstants.EXCLUSION:color=p.modes.exclusion(c1,c2);break;case PConstants.MULTIPLY:color=p.modes.multiply(c1,c2);break;case PConstants.SCREEN:color=p.modes.screen(c1,c2);break;case PConstants.HARD_LIGHT:color=p.modes.hard_light(c1,c2);break;case PConstants.SOFT_LIGHT:color=p.modes.soft_light(c1,c2);break;case PConstants.OVERLAY:color=p.modes.overlay(c1,c2);break;case PConstants.DODGE:color=p.modes.dodge(c1,c2);break;case PConstants.BURN:color=p.modes.burn(c1,c2);break}return color};function saveContext(){curContext.save()}function restoreContext(){curContext.restore();isStrokeDirty=true;isFillDirty=true}p.printMatrix=function printMatrix(){modelView.print()};p.translate=function translate(x,y,z){if(p.use3DContext){forwardTransform.translate(x,y,z);reverseTransform.invTranslate(x,y,z)}else{curContext.translate(x,y)}};p.scale=function scale(x,y,z){if(p.use3DContext){forwardTransform.scale(x,y,z);reverseTransform.invScale(x,y,z)}else{curContext.scale(x,y||x)}};p.pushMatrix=function pushMatrix(){if(p.use3DContext){userMatrixStack.load(modelView)}else{saveContext()}};p.popMatrix=function popMatrix(){if(p.use3DContext){modelView.set(userMatrixStack.pop())}else{restoreContext()}};p.resetMatrix=function resetMatrix(){if(p.use3DContext){forwardTransform.reset();reverseTransform.reset()}else{curContext.setTransform(1,0,0,1,0,0)}};p.applyMatrix=function applyMatrix(){var a=arguments;if(!p.use3DContext){for(var cnt=a.length;cnt<16;cnt++){a[cnt]=0}a[10]=a[15]=1}forwardTransform.apply(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]);reverseTransform.invApply(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15])};p.rotateX=function(angleInRadians){forwardTransform.rotateX(angleInRadians);reverseTransform.invRotateX(angleInRadians)};p.rotateZ=function(angleInRadians){forwardTransform.rotateZ(angleInRadians);reverseTransform.invRotateZ(angleInRadians)};p.rotateY=function(angleInRadians){forwardTransform.rotateY(angleInRadians);reverseTransform.invRotateY(angleInRadians)};p.rotate=function rotate(angleInRadians){if(p.use3DContext){forwardTransform.rotateZ(angleInRadians);reverseTransform.invRotateZ(angleInRadians)}else{curContext.rotate(angleInRadians)}};p.pushStyle=function pushStyle(){saveContext();p.pushMatrix();var newState={doFill:doFill,currentFillColor:currentFillColor,doStroke:doStroke,currentStrokeColor:currentStrokeColor,curTint:curTint,curRectMode:curRectMode,curColorMode:curColorMode,colorModeX:colorModeX,colorModeZ:colorModeZ,colorModeY:colorModeY,colorModeA:colorModeA,curTextFont:curTextFont,curTextSize:curTextSize};styleArray.push(newState)};p.popStyle=function popStyle(){var oldState=styleArray.pop();if(oldState){restoreContext();p.popMatrix();doFill=oldState.doFill;currentFillColor=oldState.currentFillColor;doStroke=oldState.doStroke;currentStrokeColor=oldState.currentStrokeColor;curTint=oldState.curTint;curRectMode=oldState.curRectmode;curColorMode=oldState.curColorMode;colorModeX=oldState.colorModeX;colorModeZ=oldState.colorModeZ;colorModeY=oldState.colorModeY;colorModeA=oldState.colorModeA;curTextFont=oldState.curTextFont;curTextSize=oldState.curTextSize}else{throw"Too many popStyle() without enough pushStyle()"}};p.year=function year(){return new Date().getFullYear()};p.month=function month(){return new Date().getMonth()+1};p.day=function day(){return new Date().getDate()};p.hour=function hour(){return new Date().getHours()};p.minute=function minute(){return new Date().getMinutes()};p.second=function second(){return new Date().getSeconds()};p.millis=function millis(){return new Date().getTime()-start};p.redraw=function redraw(){var sec=(new Date().getTime()-timeSinceLastFPS)/1000;framesSinceLastFPS++;var fps=framesSinceLastFPS/sec;if(sec>0.5){timeSinceLastFPS=new Date().getTime();framesSinceLastFPS=0;p.__frameRate=fps}p.frameCount++;inDraw=true;if(p.use3DContext){curContext.clear(curContext.DEPTH_BUFFER_BIT);p.noLights();p.lightFalloff(1,0,0);p.shininess(1);p.ambient(255,255,255);p.specular(0,0,0);p.camera();p.draw()}else{saveContext();p.draw();restoreContext()}inDraw=false};p.noLoop=function noLoop(){doLoop=false;loopStarted=false;clearInterval(looping)};p.loop=function loop(){if(loopStarted){return}looping=window.setInterval(function(){try{if(document.hasFocus instanceof Function){p.focused=document.hasFocus()}p.redraw()}catch(e_loop){window.clearInterval(looping);throw e_loop}},curMsPerFrame);doLoop=true;loopStarted=true};p.frameRate=function frameRate(aRate){curFrameRate=aRate;curMsPerFrame=1000/curFrameRate;if(doLoop){p.noLoop();p.loop()}};var eventHandlers=[];p.exit=function exit(){window.clearInterval(looping);Processing.removeInstance(p.externals.canvas.id);for(var i=0,ehl=eventHandlers.length;i<ehl;i++){var elem=eventHandlers[i][0],type=eventHandlers[i][1],fn=eventHandlers[i][2];if(elem.removeEventListener){elem.removeEventListener(type,fn,false)}else{if(elem.detachEvent){elem.detachEvent("on"+type,fn)}}}};p.cursor=function cursor(){if(arguments.length>1||(arguments.length===1&&arguments[0] instanceof p.PImage)){var image=arguments[0],x,y;if(arguments.length>=3){x=arguments[1];y=arguments[2];if(x<0||y<0||y>=image.height||x>=image.width){throw"x and y must be non-negative and less than the dimensions of the image"}}else{x=image.width>>>1;y=image.height>>>1}var imageDataURL=image.toDataURL();var style='url("'+imageDataURL+'") '+x+" "+y+", default";curCursor=curElement.style.cursor=style}else{if(arguments.length===1){var mode=arguments[0];curCursor=curElement.style.cursor=mode}else{curCursor=curElement.style.cursor=oldCursor}}};p.noCursor=function noCursor(){curCursor=curElement.style.cursor=PConstants.NOCURSOR};p.link=function(href,target){if(target!==undef){window.open(href,target)}else{window.location=href}};p.beginDraw=function beginDraw(){};p.endDraw=function endDraw(){};p.Import=function Import(lib){};var contextMenu=function(e){e.preventDefault();e.stopPropagation()};p.disableContextMenu=function disableContextMenu(){curElement.addEventListener("contextmenu",contextMenu,false)};p.enableContextMenu=function enableContextMenu(){curElement.removeEventListener("contextmenu",contextMenu,false)};p.status=function(text){window.status=text};function decToBin(value,numBitsInValue){var mask=1;mask=mask<<(numBitsInValue-1);var str="";for(var i=0;i<numBitsInValue;i++){str+=(mask&value)?"1":"0";mask=mask>>>1}return str}p.binary=function(num,numBits){var numBitsInValue=32;if(typeof num==="number"){if(numBits){numBitsInValue=numBits}return decToBin(num,numBitsInValue)}if(num instanceof Char){num=num.toString().charCodeAt(0);if(numBits){numBitsInValue=32}else{numBitsInValue=16}}var str=decToBin(num,numBitsInValue);if(numBits){str=str.substr(-numBits)}return str};p.unbinary=function unbinary(binaryString){var binaryPattern=new RegExp("^[0|1]{8}$");var addUp=0;var i;if(binaryString instanceof Array){var values=[];for(i=0;i<binaryString.length;i++){values[i]=p.unbinary(binaryString[i])}return values}else{if(isNaN(binaryString)){throw"NaN_Err"}else{if(arguments.length===1||binaryString.length===8){if(binaryPattern.test(binaryString)){for(i=0;i<8;i++){addUp+=(Math.pow(2,i)*parseInt(binaryString.charAt(7-i),10))}return addUp+""}else{throw"notBinary: the value passed into unbinary was not an 8 bit binary number"}}else{throw"longErr"}}}};function nfCoreScalar(value,plus,minus,leftDigits,rightDigits,group){var sign=(value<0)?minus:plus;var autoDetectDecimals=rightDigits===0;var rightDigitsOfDefault=(rightDigits===undef||rightDigits<0)?0:rightDigits;if(rightDigits<0&&Math.floor(value)%2===1){if((value-Math.floor(value))>=0.5){value+=1}}var absValue=Math.abs(value);if(autoDetectDecimals){rightDigitsOfDefault=1;absValue*=10;while(Math.abs(Math.round(absValue)-absValue)>0.000001&&rightDigitsOfDefault<7){++rightDigitsOfDefault;absValue*=10}}else{if(rightDigitsOfDefault!==0){absValue*=Math.pow(10,rightDigitsOfDefault)}}var number=Math.round(absValue);var buffer="";var totalDigits=leftDigits+rightDigitsOfDefault;while(totalDigits>0||number>0){totalDigits--;buffer=""+(number%10)+buffer;number=Math.floor(number/10)}if(group!==undef){var i=buffer.length-3-rightDigitsOfDefault;while(i>0){buffer=buffer.substring(0,i)+group+buffer.substring(i);i-=3}}if(rightDigitsOfDefault>0){return sign+buffer.substring(0,buffer.length-rightDigitsOfDefault)+"."+buffer.substring(buffer.length-rightDigitsOfDefault,buffer.length)}else{return sign+buffer}}function nfCore(value,plus,minus,leftDigits,rightDigits,group){if(value instanceof Array){var arr=[];for(var i=0,len=value.length;i<len;i++){arr.push(nfCoreScalar(value[i],plus,minus,leftDigits,rightDigits,group))}return arr}else{return nfCoreScalar(value,plus,minus,leftDigits,rightDigits,group)}}p.nf=function(){var str,num,pad,arr,left,right,isNegative,test,i;if(arguments.length===2&&typeof arguments[0]==="number"&&typeof arguments[1]==="number"&&(arguments[0]+"").indexOf(".")===-1){num=arguments[0];pad=arguments[1];isNegative=num<0;if(isNegative){num=Math.abs(num)}str=""+num;for(i=pad-str.length;i>0;i--){str="0"+str}if(isNegative){str="-"+str}}else{if(arguments.length===2&&typeof arguments[0]==="object"&&arguments[0].constructor===Array&&typeof arguments[1]==="number"){arr=arguments[0];pad=arguments[1];str=new Array(arr.length);for(i=0;i<arr.length&&str!==undef;i++){test=p.nf(arr[i],pad);if(test===undef){str=undef}else{str[i]=test}}}else{if(arguments.length===3&&typeof arguments[0]==="number"&&typeof arguments[1]==="number"&&typeof arguments[2]==="number"&&(arguments[0]+"").indexOf(".")>=0){num=arguments[0];left=arguments[1];right=arguments[2];isNegative=num<0;if(isNegative){num=Math.abs(num)}if(right<0&&Math.floor(num)%2===1){if((num)-Math.floor(num)>=0.5){num=num+1}}str=""+num;for(i=left-str.indexOf(".");i>0;i--){str="0"+str}var numDec=str.length-str.indexOf(".")-1;if(numDec<=right){for(i=right-(str.length-str.indexOf(".")-1);i>0;i--){str=str+"0"}}else{if(right>0){str=str.substring(0,str.length-(numDec-right))}else{if(right<0){str=str.substring(0,str.indexOf("."))}}}if(isNegative){str="-"+str}}else{if(arguments.length===3&&typeof arguments[0]==="object"&&arguments[0].constructor===Array&&typeof arguments[1]==="number"&&typeof arguments[2]==="number"){arr=arguments[0];left=arguments[1];right=arguments[2];str=new Array(arr.length);for(i=0;i<arr.length&&str!==undef;i++){test=p.nf(arr[i],left,right);if(test===undef){str=undef}else{str[i]=test}}}}}}return str};p.nfs=function(value,leftDigits,rightDigits){return nfCore(value," ","-",leftDigits,rightDigits)};p.nfp=function(value,leftDigits,rightDigits){return nfCore(value,"+","-",leftDigits,rightDigits)};p.nfc=function(value,leftDigits,rightDigits){return nfCore(value,"","-",leftDigits,rightDigits,",")};var decimalToHex=function decimalToHex(d,padding){padding=(padding===undef||padding===null)?padding=8:padding;if(d<0){d=4294967295+d+1}var hex=Number(d).toString(16).toUpperCase();while(hex.length<padding){hex="0"+hex}if(hex.length>=padding){hex=hex.substring(hex.length-padding,hex.length)}return hex};p.hex=function hex(value,len){if(arguments.length===1){if(value instanceof Char){len=4}else{len=8}}return decimalToHex(value,len)};function unhexScalar(hex){var value=parseInt("0x"+hex,16);if(value>2147483647){value-=4294967296}return value}p.unhex=function(hex){if(hex instanceof Array){var arr=[];for(var i=0;i<hex.length;i++){arr.push(unhexScalar(hex[i]))}return arr}else{return unhexScalar(hex)}};p.loadStrings=function loadStrings(filename){return(localStorage[filename]?localStorage[filename]:ajax(filename).slice(0,-1)).split("\n")};p.saveStrings=function saveStrings(filename,strings){localStorage[filename]=strings.join("\n")};p.loadBytes=function loadBytes(url,strings){var string=ajax(url);var ret=[];for(var i=0;i<string.length;i++){ret.push(string.charCodeAt(i))}return ret};p.matchAll=function matchAll(aString,aRegExp){var results=[],latest;var regexp=new RegExp(aRegExp,"g");while((latest=regexp.exec(aString))!==null){results.push(latest);if(latest[0].length===0){++regexp.lastIndex}}return results.length>0?results:null};String.prototype.replaceAll=function(re,replace){return this.replace(new RegExp(re,"g"),replace)};String.prototype.equals=function equals(str){return this.valueOf()===str.valueOf()};String.prototype.toCharArray=function(){var chars=this.split("");for(var i=chars.length-1;i>=0;i--){chars[i]=new Char(chars[i])}return chars};p.match=function(str,regexp){return str.match(regexp)};var tinylogLite=(function(){var tinylogLite={},undef="undefined",func="function",False=!1,True=!0,logLimit=512,log="log";if(typeof tinylog!==undef&&typeof tinylog[log]===func){tinylogLite[log]=tinylog[log]}else{if(typeof document!==undef&&!document.fake){(function(){var doc=document,$div="div",$style="style",$title="title",containerStyles={zIndex:10000,position:"fixed",bottom:"0px",width:"100%",height:"15%",fontFamily:"sans-serif",color:"#ccc",backgroundColor:"black"},outputStyles={position:"relative",fontFamily:"monospace",overflow:"auto",height:"100%",paddingTop:"5px"},resizerStyles={height:"5px",marginTop:"-5px",cursor:"n-resize",backgroundColor:"darkgrey"},closeButtonStyles={position:"absolute",top:"5px",right:"20px",color:"#111",MozBorderRadius:"4px",webkitBorderRadius:"4px",borderRadius:"4px",cursor:"pointer",fontWeight:"normal",textAlign:"center",padding:"3px 5px",backgroundColor:"#333",fontSize:"12px"},entryStyles={minHeight:"16px"},entryTextStyles={fontSize:"12px",margin:"0 8px 0 8px",maxWidth:"100%",whiteSpace:"pre-wrap",overflow:"auto"},view=doc.defaultView,docElem=doc.documentElement,docElemStyle=docElem[$style],setStyles=function(){var i=arguments.length,elemStyle,styles,style;while(i--){styles=arguments[i--];elemStyle=arguments[i][$style];for(style in styles){if(styles.hasOwnProperty(style)){elemStyle[style]=styles[style]}}}},observer=function(obj,event,handler){if(obj.addEventListener){obj.addEventListener(event,handler,False)}else{if(obj.attachEvent){obj.attachEvent("on"+event,handler)}}return[obj,event,handler]},unobserve=function(obj,event,handler){if(obj.removeEventListener){obj.removeEventListener(event,handler,False)}else{if(obj.detachEvent){obj.detachEvent("on"+event,handler)}}},clearChildren=function(node){var children=node.childNodes,child=children.length;while(child--){node.removeChild(children.item(0))}},append=function(to,elem){return to.appendChild(elem)},createElement=function(localName){return doc.createElement(localName)},createTextNode=function(text){return doc.createTextNode(text)},createLog=tinylogLite[log]=function(message){var uninit,originalPadding=docElemStyle.paddingBottom,container=createElement($div),containerStyle=container[$style],resizer=append(container,createElement($div)),output=append(container,createElement($div)),closeButton=append(container,createElement($div)),resizingLog=False,previousHeight=False,previousScrollTop=False,messages=0,updateSafetyMargin=function(){docElemStyle.paddingBottom=container.clientHeight+"px"},setContainerHeight=function(height){var viewHeight=view.innerHeight,resizerHeight=resizer.clientHeight;if(height<0){height=0}else{if(height+resizerHeight>viewHeight){height=viewHeight-resizerHeight}}containerStyle.height=height/viewHeight*100+"%";updateSafetyMargin()},observers=[observer(doc,"mousemove",function(evt){if(resizingLog){setContainerHeight(view.innerHeight-evt.clientY);output.scrollTop=previousScrollTop}}),observer(doc,"mouseup",function(){if(resizingLog){resizingLog=previousScrollTop=False}}),observer(resizer,"dblclick",function(evt){evt.preventDefault();if(previousHeight){setContainerHeight(previousHeight);previousHeight=False}else{previousHeight=container.clientHeight;containerStyle.height="0px"}}),observer(resizer,"mousedown",function(evt){evt.preventDefault();resizingLog=True;previousScrollTop=output.scrollTop}),observer(resizer,"contextmenu",function(){resizingLog=False}),observer(closeButton,"click",function(){uninit()})];uninit=function(){var i=observers.length;while(i--){unobserve.apply(tinylogLite,observers[i])}docElem.removeChild(container);docElemStyle.paddingBottom=originalPadding;clearChildren(output);clearChildren(container);tinylogLite[log]=createLog};setStyles(container,containerStyles,output,outputStyles,resizer,resizerStyles,closeButton,closeButtonStyles);closeButton[$title]="Close Log";append(closeButton,createTextNode("\u2716"));resizer[$title]="Double-click to toggle log minimization";docElem.insertBefore(container,docElem.firstChild);tinylogLite[log]=function(message){if(messages===logLimit){output.removeChild(output.firstChild)}else{messages++}var entry=append(output,createElement($div)),entryText=append(entry,createElement($div));entry[$title]=(new Date()).toLocaleTimeString();setStyles(entry,entryStyles,entryText,entryTextStyles);append(entryText,createTextNode(message));output.scrollTop=output.scrollHeight};tinylogLite[log](message)}}())}else{if(typeof print===func){tinylogLite[log]=print}}}return tinylogLite}()),logBuffer=[];p.console=window.console||tinylogLite;p.println=function println(message){var bufferLen=logBuffer.length;if(bufferLen){tinylogLite.log(logBuffer.join(""));logBuffer.length=0}if(arguments.length===0&&bufferLen===0){tinylogLite.log("")}else{if(arguments.length!==0){tinylogLite.log(message)}}};p.print=function print(message){logBuffer.push(message)};p.str=function str(val){if(val instanceof Array){var arr=[];for(var i=0;i<val.length;i++){arr.push(val[i]+"")}return arr}else{return(val+"")}};p.trim=function(str){if(str instanceof Array){var arr=[];for(var i=0;i<str.length;i++){arr.push(str[i].replace(/^\s*/,"").replace(/\s*$/,"").replace(/\r*$/,""))}return arr}else{return str.replace(/^\s*/,"").replace(/\s*$/,"").replace(/\r*$/,"")}};function booleanScalar(val){if(typeof val==="number"){return val!==0}else{if(typeof val==="boolean"){return val}else{if(typeof val==="string"){return val.toLowerCase()==="true"}else{if(val instanceof Char){return val.code===49||val.code===84||val.code===116}}}}}p["boolean"]=function(val){if(val instanceof Array){var ret=[];for(var i=0;i<val.length;i++){ret.push(booleanScalar(val[i]))}return ret}else{return booleanScalar(val)}};p["byte"]=function(aNumber){if(aNumber instanceof Array){var bytes=[];for(var i=0;i<aNumber.length;i++){bytes.push((0-(aNumber[i]&128))|(aNumber[i]&127))}return bytes}else{return(0-(aNumber&128))|(aNumber&127)}};p["char"]=function(key){if(typeof key==="number"){return new Char(String.fromCharCode(key&65535))}else{if(key instanceof Array){var ret=[];for(var i=0;i<key.length;i++){ret.push(new Char(String.fromCharCode(key[i]&65535)))}return ret}else{throw"char() may receive only one argument of type int, byte, int[], or byte[]."}}};function floatScalar(val){if(typeof val==="number"){return val}else{if(typeof val==="boolean"){return val?1:0}else{if(typeof val==="string"){return parseFloat(val)}else{if(val instanceof Char){return val.code}}}}}p["float"]=function(val){if(val instanceof Array){var ret=[];for(var i=0;i<val.length;i++){ret.push(floatScalar(val[i]))}return ret}else{return floatScalar(val)}};function intScalar(val){if(typeof val==="number"){return val&4294967295}else{if(typeof val==="boolean"){return val?1:0}else{if(typeof val==="string"){var number=parseInt(val,10);return number&4294967295}else{if(val instanceof Char){return val.code}}}}}p["int"]=function(val){if(val instanceof Array){var ret=[];for(var i=0;i<val.length;i++){if(typeof val[i]==="string"&&!/^\s*[+\-]?\d+\s*$/.test(val[i])){ret.push(0)}else{ret.push(intScalar(val[i]))}}return ret}else{return intScalar(val)}};p.abs=Math.abs;p.ceil=Math.ceil;p.constrain=function(aNumber,aMin,aMax){return aNumber>aMax?aMax:aNumber<aMin?aMin:aNumber};p.dist=function(){var dx,dy,dz;if(arguments.length===4){dx=arguments[0]-arguments[2];dy=arguments[1]-arguments[3];return Math.sqrt(dx*dx+dy*dy)}else{if(arguments.length===6){dx=arguments[0]-arguments[3];dy=arguments[1]-arguments[4];dz=arguments[2]-arguments[5];return Math.sqrt(dx*dx+dy*dy+dz*dz)}}};p.exp=Math.exp;p.floor=Math.floor;p.lerp=function(value1,value2,amt){return((value2-value1)*amt)+value1};p.log=Math.log;p.mag=function(a,b,c){if(arguments.length===2){return Math.sqrt(a*a+b*b)}else{if(arguments.length===3){return Math.sqrt(a*a+b*b+c*c)}}};p.map=function(value,istart,istop,ostart,ostop){return ostart+(ostop-ostart)*((value-istart)/(istop-istart))};p.max=function(){if(arguments.length===2){return arguments[0]<arguments[1]?arguments[1]:arguments[0]}else{var numbers=arguments.length===1?arguments[0]:arguments;if(!("length" in numbers&&numbers.length>0)){throw"Non-empty array is expected"}var max=numbers[0],count=numbers.length;for(var i=1;i<count;++i){if(max<numbers[i]){max=numbers[i]}}return max}};p.min=function(){if(arguments.length===2){return arguments[0]<arguments[1]?arguments[0]:arguments[1]}else{var numbers=arguments.length===1?arguments[0]:arguments;if(!("length" in numbers&&numbers.length>0)){throw"Non-empty array is expected"}var min=numbers[0],count=numbers.length;for(var i=1;i<count;++i){if(min>numbers[i]){min=numbers[i]}}return min}};p.norm=function(aNumber,low,high){return(aNumber-low)/(high-low)};p.pow=Math.pow;p.round=Math.round;p.sq=function(aNumber){return aNumber*aNumber};p.sqrt=Math.sqrt;p.acos=Math.acos;p.asin=Math.asin;p.atan=Math.atan;p.atan2=Math.atan2;p.cos=Math.cos;p.degrees=function(aAngle){return(aAngle*180)/Math.PI};p.radians=function(aAngle){return(aAngle/180)*Math.PI};p.sin=Math.sin;p.tan=Math.tan;var currentRandom=Math.random;p.random=function random(){if(arguments.length===0){return currentRandom()}else{if(arguments.length===1){return currentRandom()*arguments[0]}else{var aMin=arguments[0],aMax=arguments[1];return currentRandom()*(aMax-aMin)+aMin}}};function Marsaglia(i1,i2){var z=i1||362436069,w=i2||521288629;var nextInt=function(){z=(36969*(z&65535)+(z>>>16))&4294967295;w=(18000*(w&65535)+(w>>>16))&4294967295;return(((z&65535)<<16)|(w&65535))&4294967295};this.nextDouble=function(){var i=nextInt()/4294967296;return i<0?1+i:i};this.nextInt=nextInt}Marsaglia.createRandomized=function(){var now=new Date();return new Marsaglia((now/60000)&4294967295,now&4294967295)};p.randomSeed=function(seed){currentRandom=(new Marsaglia(seed)).nextDouble};p.Random=function(seed){var haveNextNextGaussian=false,nextNextGaussian,random;this.nextGaussian=function(){if(haveNextNextGaussian){haveNextNextGaussian=false;return nextNextGaussian}else{var v1,v2,s;do{v1=2*random()-1;v2=2*random()-1;s=v1*v1+v2*v2}while(s>=1||s===0);var multiplier=Math.sqrt(-2*Math.log(s)/s);nextNextGaussian=v2*multiplier;haveNextNextGaussian=true;return v1*multiplier}};random=(seed===undef)?Math.random:(new Marsaglia(seed)).nextDouble};function PerlinNoise(seed){var rnd=seed!==undef?new Marsaglia(seed):Marsaglia.createRandomized();var i,j;var perm=new Array(512);for(i=0;i<256;++i){perm[i]=i}for(i=0;i<256;++i){var t=perm[j=rnd.nextInt()&255];perm[j]=perm[i];perm[i]=t}for(i=0;i<256;++i){perm[i+256]=perm[i]}function grad3d(i,x,y,z){var h=i&15;var u=h<8?x:y,v=h<4?y:h===12||h===14?x:z;return((h&1)===0?u:-u)+((h&2)===0?v:-v)}function grad2d(i,x,y){var v=(i&1)===0?x:y;return(i&2)===0?-v:v}function grad1d(i,x){return(i&1)===0?-x:x}function lerp(t,a,b){return a+t*(b-a)}this.noise3d=function(x,y,z){var X=Math.floor(x)&255,Y=Math.floor(y)&255,Z=Math.floor(z)&255;x-=Math.floor(x);y-=Math.floor(y);z-=Math.floor(z);var fx=(3-2*x)*x*x,fy=(3-2*y)*y*y,fz=(3-2*z)*z*z;var p0=perm[X]+Y,p00=perm[p0]+Z,p01=perm[p0+1]+Z,p1=perm[X+1]+Y,p10=perm[p1]+Z,p11=perm[p1+1]+Z;return lerp(fz,lerp(fy,lerp(fx,grad3d(perm[p00],x,y,z),grad3d(perm[p10],x-1,y,z)),lerp(fx,grad3d(perm[p01],x,y-1,z),grad3d(perm[p11],x-1,y-1,z))),lerp(fy,lerp(fx,grad3d(perm[p00+1],x,y,z-1),grad3d(perm[p10+1],x-1,y,z-1)),lerp(fx,grad3d(perm[p01+1],x,y-1,z-1),grad3d(perm[p11+1],x-1,y-1,z-1))))};this.noise2d=function(x,y){var X=Math.floor(x)&255,Y=Math.floor(y)&255;x-=Math.floor(x);y-=Math.floor(y);var fx=(3-2*x)*x*x,fy=(3-2*y)*y*y;var p0=perm[X]+Y,p1=perm[X+1]+Y;return lerp(fy,lerp(fx,grad2d(perm[p0],x,y),grad2d(perm[p1],x-1,y)),lerp(fx,grad2d(perm[p0+1],x,y-1),grad2d(perm[p1+1],x-1,y-1)))};this.noise1d=function(x){var X=Math.floor(x)&255;x-=Math.floor(x);var fx=(3-2*x)*x*x;return lerp(fx,grad1d(perm[X],x),grad1d(perm[X+1],x-1))}}var noiseProfile={generator:undef,octaves:4,fallout:0.5,seed:undef};p.noise=function(x,y,z){if(noiseProfile.generator===undef){noiseProfile.generator=new PerlinNoise(noiseProfile.seed)}var generator=noiseProfile.generator;var effect=1,k=1,sum=0;for(var i=0;i<noiseProfile.octaves;++i){effect*=noiseProfile.fallout;switch(arguments.length){case 1:sum+=effect*(1+generator.noise1d(k*x))/2;break;case 2:sum+=effect*(1+generator.noise2d(k*x,k*y))/2;break;case 3:sum+=effect*(1+generator.noise3d(k*x,k*y,k*z))/2;break}k*=2}return sum};p.noiseDetail=function(octaves,fallout){noiseProfile.octaves=octaves;if(fallout!==undef){noiseProfile.fallout=fallout}};p.noiseSeed=function(seed){noiseProfile.seed=seed;noiseProfile.generator=undef};var refreshBackground=function(){if(!curSketch.options.isTransparent){if(p.use3DContext){curContext.clearColor(204/255,204/255,204/255,1);curContext.clear(curContext.COLOR_BUFFER_BIT|curContext.DEPTH_BUFFER_BIT)}else{curContext.fillStyle="rgb(204, 204, 204)";curContext.fillRect(0,0,p.width,p.height);isFillDirty=true}}};p.size=function size(aWidth,aHeight,aMode){if(aMode&&(aMode===PConstants.WEBGL)){try{if(curElement.width!==aWidth||curElement.height!==aHeight){curElement.setAttribute("width",aWidth);curElement.setAttribute("height",aHeight)}curContext=curElement.getContext("experimental-webgl");p.use3DContext=true;canTex=curContext.createTexture()}catch(e_size){Processing.debug(e_size)}if(!curContext){throw"OPENGL 3D context is not supported on this browser."}else{for(var i=0;i<PConstants.SINCOS_LENGTH;i++){sinLUT[i]=p.sin(i*(PConstants.PI/180)*0.5);cosLUT[i]=p.cos(i*(PConstants.PI/180)*0.5)}curContext.viewport(0,0,curElement.width,curElement.height);curContext.enable(curContext.DEPTH_TEST);curContext.enable(curContext.BLEND);curContext.blendFunc(curContext.SRC_ALPHA,curContext.ONE_MINUS_SRC_ALPHA);refreshBackground();programObject2D=createProgramObject(curContext,vertexShaderSource2D,fragmentShaderSource2D);curContext.useProgram(programObject2D);p.strokeWeight(1);programObject3D=createProgramObject(curContext,vertexShaderSource3D,fragmentShaderSource3D);programObjectUnlitShape=createProgramObject(curContext,vShaderSrcUnlitShape,fShaderSrcUnlitShape);curContext.useProgram(programObject3D);uniformi(programObject3D,"usingTexture",usingTexture);p.lightFalloff(1,0,0);p.shininess(1);p.ambient(255,255,255);p.specular(0,0,0);boxBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,boxBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,boxVerts,curContext.STATIC_DRAW);boxNormBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,boxNormBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,boxNorms,curContext.STATIC_DRAW);boxOutlineBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,boxOutlineBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,boxOutlineVerts,curContext.STATIC_DRAW);rectBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,rectBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,rectVerts,curContext.STATIC_DRAW);rectNormBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,rectNormBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,rectNorms,curContext.STATIC_DRAW);sphereBuffer=curContext.createBuffer();lineBuffer=curContext.createBuffer();fillBuffer=curContext.createBuffer();fillColorBuffer=curContext.createBuffer();strokeColorBuffer=curContext.createBuffer();shapeTexVBO=curContext.createBuffer();pointBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,pointBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array([0,0,0]),curContext.STATIC_DRAW);textBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,textBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array([1,1,0,-1,1,0,-1,-1,0,1,-1,0]),curContext.STATIC_DRAW);textureBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ARRAY_BUFFER,textureBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array([0,0,1,0,1,1,0,1]),curContext.STATIC_DRAW);indexBuffer=curContext.createBuffer();curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER,indexBuffer);curContext.bufferData(curContext.ELEMENT_ARRAY_BUFFER,new Uint16Array([0,1,2,2,3,0]),curContext.STATIC_DRAW);cam=new PMatrix3D();cameraInv=new PMatrix3D();forwardTransform=new PMatrix3D();reverseTransform=new PMatrix3D();modelView=new PMatrix3D();modelViewInv=new PMatrix3D();projection=new PMatrix3D();p.camera();p.perspective();forwardTransform=modelView;reverseTransform=modelViewInv;userMatrixStack=new PMatrixStack();curveBasisMatrix=new PMatrix3D();curveToBezierMatrix=new PMatrix3D();curveDrawMatrix=new PMatrix3D();bezierDrawMatrix=new PMatrix3D();bezierBasisInverse=new PMatrix3D();bezierBasisMatrix=new PMatrix3D();bezierBasisMatrix.set(-1,3,-3,1,3,-6,3,0,-3,3,0,0,1,0,0,0)}p.stroke(0);p.fill(255)}else{if(curContext===undef){curContext=curElement.getContext("2d");p.use3DContext=false;userMatrixStack=new PMatrixStack();modelView=new PMatrix2D()}}var props={fillStyle:curContext.fillStyle,strokeStyle:curContext.strokeStyle,lineCap:curContext.lineCap,lineJoin:curContext.lineJoin};curElement.width=p.width=aWidth||100;curElement.height=p.height=aHeight||100;for(var j in props){if(props){curContext[j]=props[j]}}refreshBackground();maxPixelsCached=Math.max(1000,aWidth*aHeight*0.05);p.externals.context=curContext;p.toImageData=function(){if(!p.use3DContext){return curContext.getImageData(0,0,this.width,this.height)}else{var c=document.createElement("canvas");var ctx=c.getContext("2d");var obj=ctx.createImageData(this.width,this.height);var uBuff=new Uint8Array(this.width*this.height*4);curContext.readPixels(0,0,this.width,this.height,curContext.RGBA,curContext.UNSIGNED_BYTE,uBuff);for(var i=0;i<uBuff.length;i++){obj.data[i]=uBuff[(this.height-1-Math.floor(i/4/this.width))*this.width*4+(i%(this.width*4))]}return obj}}};p.ambientLight=function(r,g,b,x,y,z){if(p.use3DContext){if(lightCount===PConstants.MAX_LIGHTS){throw"can only create "+PConstants.MAX_LIGHTS+" lights"}var pos=new PVector(x,y,z);var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.mult(pos,pos);curContext.useProgram(programObject3D);uniformf(programObject3D,"lights["+lightCount+"].color",[r/255,g/255,b/255]);uniformf(programObject3D,"lights["+lightCount+"].position",pos.array());uniformi(programObject3D,"lights["+lightCount+"].type",0);uniformi(programObject3D,"lightCount",++lightCount)}};p.directionalLight=function(r,g,b,nx,ny,nz){if(p.use3DContext){if(lightCount===PConstants.MAX_LIGHTS){throw"can only create "+PConstants.MAX_LIGHTS+" lights"}curContext.useProgram(programObject3D);var dir=[nx,ny,nz,1e-7];var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.mult(dir,dir);uniformf(programObject3D,"lights["+lightCount+"].color",[r/255,g/255,b/255]);uniformf(programObject3D,"lights["+lightCount+"].position",[-dir[0],-dir[1],-dir[2]]);uniformi(programObject3D,"lights["+lightCount+"].type",1);uniformi(programObject3D,"lightCount",++lightCount)}};p.lightFalloff=function lightFalloff(constant,linear,quadratic){if(p.use3DContext){curContext.useProgram(programObject3D);uniformf(programObject3D,"falloff",[constant,linear,quadratic])}};p.lightSpecular=function lightSpecular(r,g,b){if(p.use3DContext){curContext.useProgram(programObject3D);uniformf(programObject3D,"specular",[r/255,g/255,b/255])}};p.lights=function lights(){p.ambientLight(128,128,128);p.directionalLight(128,128,128,0,0,-1);p.lightFalloff(1,0,0);p.lightSpecular(0,0,0)};p.pointLight=function(r,g,b,x,y,z){if(p.use3DContext){if(lightCount===PConstants.MAX_LIGHTS){throw"can only create "+PConstants.MAX_LIGHTS+" lights"}var pos=new PVector(x,y,z);var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.mult(pos,pos);curContext.useProgram(programObject3D);uniformf(programObject3D,"lights["+lightCount+"].color",[r/255,g/255,b/255]);uniformf(programObject3D,"lights["+lightCount+"].position",pos.array());uniformi(programObject3D,"lights["+lightCount+"].type",2);uniformi(programObject3D,"lightCount",++lightCount)}};p.noLights=function noLights(){if(p.use3DContext){lightCount=0;curContext.useProgram(programObject3D);uniformi(programObject3D,"lightCount",lightCount)}};p.spotLight=function spotLight(r,g,b,x,y,z,nx,ny,nz,angle,concentration){if(p.use3DContext){if(lightCount===PConstants.MAX_LIGHTS){throw"can only create "+PConstants.MAX_LIGHTS+" lights"}curContext.useProgram(programObject3D);var pos=new PVector(x,y,z);var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.mult(pos,pos);var dir=[nx,ny,nz,1e-7];view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.mult(dir,dir);uniformf(programObject3D,"lights["+lightCount+"].color",[r/255,g/255,b/255]);uniformf(programObject3D,"lights["+lightCount+"].position",pos.array());uniformf(programObject3D,"lights["+lightCount+"].direction",[dir[0],dir[1],dir[2]]);uniformf(programObject3D,"lights["+lightCount+"].concentration",concentration);uniformf(programObject3D,"lights["+lightCount+"].angle",angle);uniformi(programObject3D,"lights["+lightCount+"].type",3);uniformi(programObject3D,"lightCount",++lightCount)}};p.beginCamera=function beginCamera(){if(manipulatingCamera){throw ("You cannot call beginCamera() again before calling endCamera()")}else{manipulatingCamera=true;forwardTransform=cameraInv;reverseTransform=cam}};p.endCamera=function endCamera(){if(!manipulatingCamera){throw ("You cannot call endCamera() before calling beginCamera()")}else{modelView.set(cam);modelViewInv.set(cameraInv);forwardTransform=modelView;reverseTransform=modelViewInv;manipulatingCamera=false}};p.camera=function camera(eyeX,eyeY,eyeZ,centerX,centerY,centerZ,upX,upY,upZ){if(arguments.length===0){cameraX=curElement.width/2;cameraY=curElement.height/2;cameraZ=cameraY/Math.tan(cameraFOV/2);eyeX=cameraX;eyeY=cameraY;eyeZ=cameraZ;centerX=cameraX;centerY=cameraY;centerZ=0;upX=0;upY=1;upZ=0}var z=new p.PVector(eyeX-centerX,eyeY-centerY,eyeZ-centerZ);var y=new p.PVector(upX,upY,upZ);var transX,transY,transZ;z.normalize();var x=p.PVector.cross(y,z);y=p.PVector.cross(z,x);x.normalize();y.normalize();cam.set(x.x,x.y,x.z,0,y.x,y.y,y.z,0,z.x,z.y,z.z,0,0,0,0,1);cam.translate(-eyeX,-eyeY,-eyeZ);cameraInv.reset();cameraInv.invApply(x.x,x.y,x.z,0,y.x,y.y,y.z,0,z.x,z.y,z.z,0,0,0,0,1);cameraInv.translate(eyeX,eyeY,eyeZ);modelView.set(cam);modelViewInv.set(cameraInv)};p.perspective=function perspective(fov,aspect,near,far){if(arguments.length===0){cameraY=curElement.height/2;cameraZ=cameraY/Math.tan(cameraFOV/2);cameraNear=cameraZ/10;cameraFar=cameraZ*10;cameraAspect=curElement.width/curElement.height;fov=cameraFOV;aspect=cameraAspect;near=cameraNear;far=cameraFar}var yMax,yMin,xMax,xMin;yMax=near*Math.tan(fov/2);yMin=-yMax;xMax=yMax*aspect;xMin=yMin*aspect;p.frustum(xMin,xMax,yMin,yMax,near,far)};p.frustum=function frustum(left,right,bottom,top,near,far){frustumMode=true;projection=new PMatrix3D();projection.set((2*near)/(right-left),0,(right+left)/(right-left),0,0,(2*near)/(top-bottom),(top+bottom)/(top-bottom),0,0,0,-(far+near)/(far-near),-(2*far*near)/(far-near),0,0,-1,0)};p.ortho=function ortho(left,right,bottom,top,near,far){if(arguments.length===0){left=0;right=p.width;bottom=0;top=p.height;near=-10;far=10}var x=2/(right-left);var y=2/(top-bottom);var z=-2/(far-near);var tx=-(right+left)/(right-left);var ty=-(top+bottom)/(top-bottom);var tz=-(far+near)/(far-near);projection=new PMatrix3D();projection.set(x,0,0,tx,0,y,0,ty,0,0,z,tz,0,0,0,1);frustumMode=false};p.printProjection=function(){projection.print()};p.printCamera=function(){cam.print()};p.box=function(w,h,d){if(p.use3DContext){if(!h||!d){h=d=w}var model=new PMatrix3D();model.scale(w,h,d);var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();if(doFill===true){curContext.useProgram(programObject3D);disableVertexAttribPointer(programObject3D,"aTexture");uniformMatrix(programObject3D,"model",false,model.array());uniformMatrix(programObject3D,"view",false,view.array());uniformMatrix(programObject3D,"projection",false,proj.array());curContext.enable(curContext.POLYGON_OFFSET_FILL);curContext.polygonOffset(1,1);uniformf(programObject3D,"color",fillStyle);var v=new PMatrix3D();v.set(view);var m=new PMatrix3D();m.set(model);v.mult(m);var normalMatrix=new PMatrix3D();normalMatrix.set(v);normalMatrix.invert();normalMatrix.transpose();uniformMatrix(programObject3D,"normalTransform",false,normalMatrix.array());vertexAttribPointer(programObject3D,"Vertex",3,boxBuffer);vertexAttribPointer(programObject3D,"Normal",3,boxNormBuffer);vertexAttribPointer(programObject3D,"aColor",3,boxNormBuffer);curContext.drawArrays(curContext.TRIANGLES,0,boxVerts.length/3);curContext.disable(curContext.POLYGON_OFFSET_FILL)}if(lineWidth>0&&doStroke){curContext.useProgram(programObject2D);uniformMatrix(programObject2D,"model",false,model.array());uniformMatrix(programObject2D,"view",false,view.array());uniformMatrix(programObject2D,"projection",false,proj.array());uniformf(programObject2D,"color",strokeStyle);uniformi(programObject2D,"picktype",0);vertexAttribPointer(programObject2D,"Vertex",3,boxOutlineBuffer);disableVertexAttribPointer(programObject2D,"aTextureCoord");curContext.lineWidth(lineWidth);curContext.drawArrays(curContext.LINES,0,boxOutlineVerts.length/3)}}};var initSphere=function(){var i;sphereVerts=[];for(i=0;i<sphereDetailU;i++){sphereVerts.push(0);sphereVerts.push(-1);sphereVerts.push(0);sphereVerts.push(sphereX[i]);sphereVerts.push(sphereY[i]);sphereVerts.push(sphereZ[i])}sphereVerts.push(0);sphereVerts.push(-1);sphereVerts.push(0);sphereVerts.push(sphereX[0]);sphereVerts.push(sphereY[0]);sphereVerts.push(sphereZ[0]);var v1,v11,v2;var voff=0;for(i=2;i<sphereDetailV;i++){v1=v11=voff;voff+=sphereDetailU;v2=voff;for(var j=0;j<sphereDetailU;j++){sphereVerts.push(parseFloat(sphereX[v1]));sphereVerts.push(parseFloat(sphereY[v1]));sphereVerts.push(parseFloat(sphereZ[v1++]));sphereVerts.push(parseFloat(sphereX[v2]));sphereVerts.push(parseFloat(sphereY[v2]));sphereVerts.push(parseFloat(sphereZ[v2++]))}v1=v11;v2=voff;sphereVerts.push(parseFloat(sphereX[v1]));sphereVerts.push(parseFloat(sphereY[v1]));sphereVerts.push(parseFloat(sphereZ[v1]));sphereVerts.push(parseFloat(sphereX[v2]));sphereVerts.push(parseFloat(sphereY[v2]));sphereVerts.push(parseFloat(sphereZ[v2]))}for(i=0;i<sphereDetailU;i++){v2=voff+i;sphereVerts.push(parseFloat(sphereX[v2]));sphereVerts.push(parseFloat(sphereY[v2]));sphereVerts.push(parseFloat(sphereZ[v2]));sphereVerts.push(0);sphereVerts.push(1);sphereVerts.push(0)}sphereVerts.push(parseFloat(sphereX[voff]));sphereVerts.push(parseFloat(sphereY[voff]));sphereVerts.push(parseFloat(sphereZ[voff]));sphereVerts.push(0);sphereVerts.push(1);sphereVerts.push(0);curContext.bindBuffer(curContext.ARRAY_BUFFER,sphereBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(sphereVerts),curContext.STATIC_DRAW)};p.sphereDetail=function sphereDetail(ures,vres){var i;if(arguments.length===1){ures=vres=arguments[0]}if(ures<3){ures=3}if(vres<2){vres=2}if((ures===sphereDetailU)&&(vres===sphereDetailV)){return}var delta=PConstants.SINCOS_LENGTH/ures;var cx=new Array(ures);var cz=new Array(ures);for(i=0;i<ures;i++){cx[i]=cosLUT[parseInt((i*delta)%PConstants.SINCOS_LENGTH,10)];cz[i]=sinLUT[parseInt((i*delta)%PConstants.SINCOS_LENGTH,10)]}var vertCount=ures*(vres-1)+2;var currVert=0;sphereX=new Array(vertCount);sphereY=new Array(vertCount);sphereZ=new Array(vertCount);var angle_step=(PConstants.SINCOS_LENGTH*0.5)/vres;var angle=angle_step;for(i=1;i<vres;i++){var curradius=sinLUT[parseInt(angle%PConstants.SINCOS_LENGTH,10)];var currY=-cosLUT[parseInt(angle%PConstants.SINCOS_LENGTH,10)];for(var j=0;j<ures;j++){sphereX[currVert]=cx[j]*curradius;sphereY[currVert]=currY;sphereZ[currVert++]=cz[j]*curradius}angle+=angle_step}sphereDetailU=ures;sphereDetailV=vres;initSphere()};p.sphere=function(){if(p.use3DContext){var sRad=arguments[0],c;if((sphereDetailU<3)||(sphereDetailV<2)){p.sphereDetail(30)}var model=new PMatrix3D();model.scale(sRad,sRad,sRad);var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();if(doFill===true){var v=new PMatrix3D();v.set(view);var m=new PMatrix3D();m.set(model);v.mult(m);var normalMatrix=new PMatrix3D();normalMatrix.set(v);normalMatrix.invert();normalMatrix.transpose();curContext.useProgram(programObject3D);disableVertexAttribPointer(programObject3D,"aTexture");uniformMatrix(programObject3D,"model",false,model.array());uniformMatrix(programObject3D,"view",false,view.array());uniformMatrix(programObject3D,"projection",false,proj.array());uniformMatrix(programObject3D,"normalTransform",false,normalMatrix.array());vertexAttribPointer(programObject3D,"Vertex",3,sphereBuffer);vertexAttribPointer(programObject3D,"Normal",3,sphereBuffer);vertexAttribPointer(programObject3D,"aColor",3,sphereBuffer);curContext.enable(curContext.POLYGON_OFFSET_FILL);curContext.polygonOffset(1,1);uniformf(programObject3D,"color",fillStyle);curContext.drawArrays(curContext.TRIANGLE_STRIP,0,sphereVerts.length/3);curContext.disable(curContext.POLYGON_OFFSET_FILL)}if(lineWidth>0&&doStroke){curContext.useProgram(programObject2D);uniformMatrix(programObject2D,"model",false,model.array());uniformMatrix(programObject2D,"view",false,view.array());uniformMatrix(programObject2D,"projection",false,proj.array());vertexAttribPointer(programObject2D,"Vertex",3,sphereBuffer);disableVertexAttribPointer(programObject2D,"aTextureCoord");uniformf(programObject2D,"color",strokeStyle);uniformi(programObject2D,"picktype",0);curContext.lineWidth(lineWidth);curContext.drawArrays(curContext.LINE_STRIP,0,sphereVerts.length/3)}}};p.modelX=function modelX(x,y,z){var mv=modelView.array();var ci=cameraInv.array();var ax=mv[0]*x+mv[1]*y+mv[2]*z+mv[3];var ay=mv[4]*x+mv[5]*y+mv[6]*z+mv[7];var az=mv[8]*x+mv[9]*y+mv[10]*z+mv[11];var aw=mv[12]*x+mv[13]*y+mv[14]*z+mv[15];var ox=ci[0]*ax+ci[1]*ay+ci[2]*az+ci[3]*aw;var ow=ci[12]*ax+ci[13]*ay+ci[14]*az+ci[15]*aw;return(ow!==0)?ox/ow:ox};p.modelY=function modelY(x,y,z){var mv=modelView.array();var ci=cameraInv.array();var ax=mv[0]*x+mv[1]*y+mv[2]*z+mv[3];var ay=mv[4]*x+mv[5]*y+mv[6]*z+mv[7];var az=mv[8]*x+mv[9]*y+mv[10]*z+mv[11];var aw=mv[12]*x+mv[13]*y+mv[14]*z+mv[15];var oy=ci[4]*ax+ci[5]*ay+ci[6]*az+ci[7]*aw;var ow=ci[12]*ax+ci[13]*ay+ci[14]*az+ci[15]*aw;return(ow!==0)?oy/ow:oy};p.modelZ=function modelZ(x,y,z){var mv=modelView.array();var ci=cameraInv.array();var ax=mv[0]*x+mv[1]*y+mv[2]*z+mv[3];var ay=mv[4]*x+mv[5]*y+mv[6]*z+mv[7];var az=mv[8]*x+mv[9]*y+mv[10]*z+mv[11];var aw=mv[12]*x+mv[13]*y+mv[14]*z+mv[15];var oz=ci[8]*ax+ci[9]*ay+ci[10]*az+ci[11]*aw;var ow=ci[12]*ax+ci[13]*ay+ci[14]*az+ci[15]*aw;return(ow!==0)?oz/ow:oz};p.ambient=function ambient(){var a=arguments;if(p.use3DContext){curContext.useProgram(programObject3D);uniformi(programObject3D,"usingMat",true);if(a.length===1){if(typeof a[0]==="string"){var c=a[0].slice(5,-1).split(",");uniformf(programObject3D,"mat_ambient",[c[0]/255,c[1]/255,c[2]/255])}else{uniformf(programObject3D,"mat_ambient",[a[0]/255,a[0]/255,a[0]/255])}}else{uniformf(programObject3D,"mat_ambient",[a[0]/255,a[1]/255,a[2]/255])}}};p.emissive=function emissive(){var a=arguments;if(p.use3DContext){curContext.useProgram(programObject3D);uniformi(programObject3D,"usingMat",true);if(a.length===1){if(typeof a[0]==="string"){var c=a[0].slice(5,-1).split(",");uniformf(programObject3D,"mat_emissive",[c[0]/255,c[1]/255,c[2]/255])}else{uniformf(programObject3D,"mat_emissive",[a[0]/255,a[0]/255,a[0]/255])}}else{uniformf(programObject3D,"mat_emissive",[a[0]/255,a[1]/255,a[2]/255])}}};p.shininess=function shininess(shine){if(p.use3DContext){curContext.useProgram(programObject3D);uniformi(programObject3D,"usingMat",true);uniformf(programObject3D,"shininess",shine)}};p.specular=function specular(){var c=p.color.apply(this,arguments);if(p.use3DContext){curContext.useProgram(programObject3D);uniformi(programObject3D,"usingMat",true);uniformf(programObject3D,"mat_specular",p.color.toGLArray(c).slice(0,3))}};p.screenX=function screenX(x,y,z){var mv=modelView.array();var pj=projection.array();var ax=mv[0]*x+mv[1]*y+mv[2]*z+mv[3];var ay=mv[4]*x+mv[5]*y+mv[6]*z+mv[7];var az=mv[8]*x+mv[9]*y+mv[10]*z+mv[11];var aw=mv[12]*x+mv[13]*y+mv[14]*z+mv[15];var ox=pj[0]*ax+pj[1]*ay+pj[2]*az+pj[3]*aw;var ow=pj[12]*ax+pj[13]*ay+pj[14]*az+pj[15]*aw;if(ow!==0){ox/=ow}return p.width*(1+ox)/2};p.screenY=function screenY(x,y,z){var mv=modelView.array();var pj=projection.array();var ax=mv[0]*x+mv[1]*y+mv[2]*z+mv[3];var ay=mv[4]*x+mv[5]*y+mv[6]*z+mv[7];var az=mv[8]*x+mv[9]*y+mv[10]*z+mv[11];var aw=mv[12]*x+mv[13]*y+mv[14]*z+mv[15];var oy=pj[4]*ax+pj[5]*ay+pj[6]*az+pj[7]*aw;var ow=pj[12]*ax+pj[13]*ay+pj[14]*az+pj[15]*aw;if(ow!==0){oy/=ow}return p.height*(1+oy)/2};p.screenZ=function screenZ(x,y,z){var mv=modelView.array();var pj=projection.array();var ax=mv[0]*x+mv[1]*y+mv[2]*z+mv[3];var ay=mv[4]*x+mv[5]*y+mv[6]*z+mv[7];var az=mv[8]*x+mv[9]*y+mv[10]*z+mv[11];var aw=mv[12]*x+mv[13]*y+mv[14]*z+mv[15];var oz=pj[8]*ax+pj[9]*ay+pj[10]*az+pj[11]*aw;var ow=pj[12]*ax+pj[13]*ay+pj[14]*az+pj[15]*aw;if(ow!==0){oz/=ow}return(oz+1)/2};p.fill=function fill(){var color=p.color(arguments[0],arguments[1],arguments[2],arguments[3]);if(color===currentFillColor&&doFill){return}doFill=true;currentFillColor=color;if(p.use3DContext){fillStyle=p.color.toGLArray(color)}else{isFillDirty=true}};function executeContextFill(){if(doFill){if(isFillDirty){curContext.fillStyle=p.color.toString(currentFillColor);isFillDirty=false}curContext.fill()}}p.noFill=function noFill(){doFill=false};p.stroke=function stroke(){var color=p.color(arguments[0],arguments[1],arguments[2],arguments[3]);if(color===currentStrokeColor&&doStroke){return}doStroke=true;currentStrokeColor=color;if(p.use3DContext){strokeStyle=p.color.toGLArray(color)}else{isStrokeDirty=true}};function executeContextStroke(){if(doStroke){if(isStrokeDirty){curContext.strokeStyle=p.color.toString(currentStrokeColor);isStrokeDirty=false}curContext.stroke()}}p.noStroke=function noStroke(){doStroke=false};p.strokeWeight=function strokeWeight(w){lineWidth=w;if(p.use3DContext){curContext.useProgram(programObject2D);uniformf(programObject2D,"pointSize",w)}else{curContext.lineWidth=w}};p.strokeCap=function strokeCap(value){curContext.lineCap=value};p.strokeJoin=function strokeJoin(value){curContext.lineJoin=value};p.smooth=function(){curElement.style.setProperty("image-rendering","optimizeQuality","important");if(!p.use3DContext&&"mozImageSmoothingEnabled" in curContext){curContext.mozImageSmoothingEnabled=true}};p.noSmooth=function(){curElement.style.setProperty("image-rendering","optimizeSpeed","important");if(!p.use3DContext&&"mozImageSmoothingEnabled" in curContext){curContext.mozImageSmoothingEnabled=false}};function colorBlendWithAlpha(c1,c2,k){var f=0|(k*((c2&PConstants.ALPHA_MASK)>>>24));return(Math.min(((c1&PConstants.ALPHA_MASK)>>>24)+f,255)<<24|p.mix(c1&PConstants.RED_MASK,c2&PConstants.RED_MASK,f)&PConstants.RED_MASK|p.mix(c1&PConstants.GREEN_MASK,c2&PConstants.GREEN_MASK,f)&PConstants.GREEN_MASK|p.mix(c1&PConstants.BLUE_MASK,c2&PConstants.BLUE_MASK,f))}p.point=function point(x,y,z){if(p.use3DContext){var model=new PMatrix3D();model.translate(x,y,z||0);model.transpose();var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();curContext.useProgram(programObject2D);uniformMatrix(programObject2D,"model",false,model.array());uniformMatrix(programObject2D,"view",false,view.array());uniformMatrix(programObject2D,"projection",false,proj.array());if(lineWidth>0&&doStroke){uniformf(programObject2D,"color",strokeStyle);uniformi(programObject2D,"picktype",0);vertexAttribPointer(programObject2D,"Vertex",3,pointBuffer);disableVertexAttribPointer(programObject2D,"aTextureCoord");curContext.drawArrays(curContext.POINTS,0,1)}}else{if(doStroke){if(curSketch.options.crispLines){var alphaOfPointWeight=Math.PI/4;var c=p.get(x,y);p.set(x,y,colorBlendWithAlpha(c,currentStrokeColor,alphaOfPointWeight))}else{if(lineWidth>1){curContext.fillStyle=p.color.toString(currentStrokeColor);isFillDirty=true;curContext.beginPath();curContext.arc(x,y,lineWidth/2,0,PConstants.TWO_PI,false);curContext.fill();curContext.closePath()}else{curContext.fillStyle=p.color.toString(currentStrokeColor);curContext.fillRect(Math.round(x),Math.round(y),1,1);isFillDirty=true}}}}};p.beginShape=function beginShape(type){curShape=type;curShapeCount=0;curvePoints=[];vertArray=[];if(p.use3DContext){}};p.vertex=function vertex(){var vert=[];if(firstVert){firstVert=false}if(arguments.length===4){vert[0]=arguments[0];vert[1]=arguments[1];vert[2]=0;vert[3]=arguments[2];vert[4]=arguments[3]}else{vert[0]=arguments[0];vert[1]=arguments[1];vert[2]=arguments[2]||0;vert[3]=arguments[3]||0;vert[4]=arguments[4]||0}vert.isVert=true;if(p.use3DContext){vert[5]=fillStyle[0];vert[6]=fillStyle[1];vert[7]=fillStyle[2];vert[8]=fillStyle[3];vert[9]=strokeStyle[0];vert[10]=strokeStyle[1];vert[11]=strokeStyle[2];vert[12]=strokeStyle[3];vert[13]=normalX;vert[14]=normalY;vert[15]=normalZ}else{vert[5]=currentFillColor;vert[6]=currentStrokeColor}vertArray.push(vert)};var point3D=function point3D(vArray,cArray){var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();curContext.useProgram(programObjectUnlitShape);uniformMatrix(programObjectUnlitShape,"uView",false,view.array());uniformMatrix(programObjectUnlitShape,"uProjection",false,proj.array());vertexAttribPointer(programObjectUnlitShape,"aVertex",3,pointBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(vArray),curContext.STREAM_DRAW);vertexAttribPointer(programObjectUnlitShape,"aColor",4,fillColorBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(cArray),curContext.STREAM_DRAW);curContext.drawArrays(curContext.POINTS,0,vArray.length/3)};var line3D=function line3D(vArray,mode,cArray){var ctxMode;if(mode==="LINES"){ctxMode=curContext.LINES}else{if(mode==="LINE_LOOP"){ctxMode=curContext.LINE_LOOP}else{ctxMode=curContext.LINE_STRIP}}var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();curContext.useProgram(programObjectUnlitShape);uniformMatrix(programObjectUnlitShape,"uView",false,view.array());uniformMatrix(programObjectUnlitShape,"uProjection",false,proj.array());vertexAttribPointer(programObjectUnlitShape,"aVertex",3,lineBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(vArray),curContext.STREAM_DRAW);vertexAttribPointer(programObjectUnlitShape,"aColor",4,strokeColorBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(cArray),curContext.STREAM_DRAW);curContext.lineWidth(lineWidth);curContext.drawArrays(ctxMode,0,vArray.length/3)};var fill3D=function fill3D(vArray,mode,cArray,tArray){var ctxMode;if(mode==="TRIANGLES"){ctxMode=curContext.TRIANGLES}else{if(mode==="TRIANGLE_FAN"){ctxMode=curContext.TRIANGLE_FAN}else{ctxMode=curContext.TRIANGLE_STRIP}}var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();curContext.useProgram(programObject3D);uniformMatrix(programObject3D,"model",false,[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);uniformMatrix(programObject3D,"view",false,view.array());uniformMatrix(programObject3D,"projection",false,proj.array());curContext.enable(curContext.POLYGON_OFFSET_FILL);curContext.polygonOffset(1,1);uniformf(programObject3D,"color",[-1,0,0,0]);vertexAttribPointer(programObject3D,"Vertex",3,fillBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(vArray),curContext.STREAM_DRAW);vertexAttribPointer(programObject3D,"aColor",4,fillColorBuffer);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(cArray),curContext.STREAM_DRAW);disableVertexAttribPointer(programObject3D,"Normal");var i;if(usingTexture){if(curTextureMode===PConstants.IMAGE){for(i=0;i<tArray.length;i+=2){tArray[i]=tArray[i]/curTexture.width;tArray[i+1]/=curTexture.height}}for(i=0;i<tArray.length;i+=2){if(tArray[i+0]>1){tArray[i+0]-=(tArray[i+0]-1)}if(tArray[i+1]>1){tArray[i+1]-=(tArray[i+1]-1)}}uniformi(programObject3D,"usingTexture",usingTexture);vertexAttribPointer(programObject3D,"aTexture",2,shapeTexVBO);curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(tArray),curContext.STREAM_DRAW)}curContext.drawArrays(ctxMode,0,vArray.length/3);curContext.disable(curContext.POLYGON_OFFSET_FILL)};p.endShape=function endShape(mode){var closeShape=mode===PConstants.CLOSE;var lineVertArray=[];var fillVertArray=[];var colorVertArray=[];var strokeVertArray=[];var texVertArray=[];firstVert=true;var i,j,k;var last=vertArray.length-1;for(i=0;i<vertArray.length;i++){for(j=0;j<3;j++){fillVertArray.push(vertArray[i][j])}}for(i=0;i<vertArray.length;i++){for(j=5;j<9;j++){colorVertArray.push(vertArray[i][j])}}for(i=0;i<vertArray.length;i++){for(j=9;j<13;j++){strokeVertArray.push(vertArray[i][j])}}for(i=0;i<vertArray.length;i++){texVertArray.push(vertArray[i][3]);texVertArray.push(vertArray[i][4])}if(closeShape){fillVertArray.push(vertArray[0][0]);fillVertArray.push(vertArray[0][1]);fillVertArray.push(vertArray[0][2]);for(i=5;i<9;i++){colorVertArray.push(vertArray[0][i])}for(i=9;i<13;i++){strokeVertArray.push(vertArray[0][i])}texVertArray.push(vertArray[0][3]);texVertArray.push(vertArray[0][4])}if(isCurve&&curShape===PConstants.POLYGON||isCurve&&curShape===undef){if(p.use3DContext){lineVertArray=fillVertArray;if(doStroke){line3D(lineVertArray,null,strokeVertArray)}if(doFill){fill3D(fillVertArray,null,colorVertArray)}}else{if(vertArray.length>3){var b=[],s=1-curTightness;curContext.beginPath();curContext.moveTo(vertArray[1][0],vertArray[1][1]);for(i=1;(i+2)<vertArray.length;i++){b[0]=[vertArray[i][0],vertArray[i][1]];b[1]=[vertArray[i][0]+(s*vertArray[i+1][0]-s*vertArray[i-1][0])/6,vertArray[i][1]+(s*vertArray[i+1][1]-s*vertArray[i-1][1])/6];b[2]=[vertArray[i+1][0]+(s*vertArray[i][0]-s*vertArray[i+2][0])/6,vertArray[i+1][1]+(s*vertArray[i][1]-s*vertArray[i+2][1])/6];b[3]=[vertArray[i+1][0],vertArray[i+1][1]];curContext.bezierCurveTo(b[1][0],b[1][1],b[2][0],b[2][1],b[3][0],b[3][1])}if(closeShape){curContext.lineTo(vertArray[0][0],vertArray[0][1])}executeContextFill();executeContextStroke();curContext.closePath()}}}else{if(isBezier&&curShape===PConstants.POLYGON||isBezier&&curShape===undef){if(p.use3DContext){lineVertArray=fillVertArray;lineVertArray.splice(lineVertArray.length-3);strokeVertArray.splice(strokeVertArray.length-4);if(doStroke){line3D(lineVertArray,null,strokeVertArray)}if(doFill){fill3D(fillVertArray,"TRIANGLES",colorVertArray)}}else{curContext.beginPath();for(i=0;i<vertArray.length;i++){if(vertArray[i]["isVert"]===true){if(vertArray[i]["moveTo"]===true){curContext.moveTo(vertArray[i][0],vertArray[i][1])}else{if(vertArray[i]["moveTo"]===false){curContext.lineTo(vertArray[i][0],vertArray[i][1])}else{curContext.moveTo(vertArray[i][0],vertArray[i][1])}}}else{curContext.bezierCurveTo(vertArray[i][0],vertArray[i][1],vertArray[i][2],vertArray[i][3],vertArray[i][4],vertArray[i][5])}}if(closeShape){curContext.lineTo(vertArray[0][0],vertArray[0][1])}executeContextFill();executeContextStroke();curContext.closePath()}}else{if(p.use3DContext){if(curShape===PConstants.POINTS){for(i=0;i<vertArray.length;i++){for(j=0;j<3;j++){lineVertArray.push(vertArray[i][j])}}point3D(lineVertArray,strokeVertArray)}else{if(curShape===PConstants.LINES){for(i=0;i<vertArray.length;i++){for(j=0;j<3;j++){lineVertArray.push(vertArray[i][j])}}for(i=0;i<vertArray.length;i++){for(j=5;j<9;j++){colorVertArray.push(vertArray[i][j])}}line3D(lineVertArray,"LINES",strokeVertArray)}else{if(curShape===PConstants.TRIANGLES){if(vertArray.length>2){for(i=0;(i+2)<vertArray.length;i+=3){fillVertArray=[];texVertArray=[];lineVertArray=[];colorVertArray=[];strokeVertArray=[];for(j=0;j<3;j++){for(k=0;k<3;k++){lineVertArray.push(vertArray[i+j][k]);fillVertArray.push(vertArray[i+j][k])}}for(j=0;j<3;j++){for(k=3;k<5;k++){texVertArray.push(vertArray[i+j][k])}}for(j=0;j<3;j++){for(k=5;k<9;k++){colorVertArray.push(vertArray[i+j][k]);strokeVertArray.push(vertArray[i+j][k+4])}}if(doStroke){line3D(lineVertArray,"LINE_LOOP",strokeVertArray)}if(doFill||usingTexture){fill3D(fillVertArray,"TRIANGLES",colorVertArray,texVertArray)}}}}else{if(curShape===PConstants.TRIANGLE_STRIP){if(vertArray.length>2){for(i=0;(i+2)<vertArray.length;i++){lineVertArray=[];fillVertArray=[];strokeVertArray=[];colorVertArray=[];texVertArray=[];for(j=0;j<3;j++){for(k=0;k<3;k++){lineVertArray.push(vertArray[i+j][k]);fillVertArray.push(vertArray[i+j][k])}}for(j=0;j<3;j++){for(k=3;k<5;k++){texVertArray.push(vertArray[i+j][k])}}for(j=0;j<3;j++){for(k=5;k<9;k++){strokeVertArray.push(vertArray[i+j][k+4]);colorVertArray.push(vertArray[i+j][k])}}if(doFill||usingTexture){fill3D(fillVertArray,"TRIANGLE_STRIP",colorVertArray,texVertArray)}if(doStroke){line3D(lineVertArray,"LINE_LOOP",strokeVertArray)}}}}else{if(curShape===PConstants.TRIANGLE_FAN){if(vertArray.length>2){for(i=0;i<3;i++){for(j=0;j<3;j++){lineVertArray.push(vertArray[i][j])}}for(i=0;i<3;i++){for(j=9;j<13;j++){strokeVertArray.push(vertArray[i][j])}}if(doStroke){line3D(lineVertArray,"LINE_LOOP",strokeVertArray)}for(i=2;(i+1)<vertArray.length;i++){lineVertArray=[];strokeVertArray=[];lineVertArray.push(vertArray[0][0]);lineVertArray.push(vertArray[0][1]);lineVertArray.push(vertArray[0][2]);strokeVertArray.push(vertArray[0][9]);strokeVertArray.push(vertArray[0][10]);strokeVertArray.push(vertArray[0][11]);strokeVertArray.push(vertArray[0][12]);for(j=0;j<2;j++){for(k=0;k<3;k++){lineVertArray.push(vertArray[i+j][k])}}for(j=0;j<2;j++){for(k=9;k<13;k++){strokeVertArray.push(vertArray[i+j][k])}}if(doStroke){line3D(lineVertArray,"LINE_STRIP",strokeVertArray)}}if(doFill||usingTexture){fill3D(fillVertArray,"TRIANGLE_FAN",colorVertArray,texVertArray)}}}else{if(curShape===PConstants.QUADS){for(i=0;(i+3)<vertArray.length;i+=4){lineVertArray=[];for(j=0;j<4;j++){for(k=0;k<3;k++){lineVertArray.push(vertArray[i+j][k])}}if(doStroke){line3D(lineVertArray,"LINE_LOOP",strokeVertArray)}if(doFill){fillVertArray=[];colorVertArray=[];texVertArray=[];for(j=0;j<3;j++){fillVertArray.push(vertArray[i][j])}for(j=5;j<9;j++){colorVertArray.push(vertArray[i][j])}for(j=0;j<3;j++){fillVertArray.push(vertArray[i+1][j])}for(j=5;j<9;j++){colorVertArray.push(vertArray[i+1][j])}for(j=0;j<3;j++){fillVertArray.push(vertArray[i+3][j])}for(j=5;j<9;j++){colorVertArray.push(vertArray[i+3][j])}for(j=0;j<3;j++){fillVertArray.push(vertArray[i+2][j])}for(j=5;j<9;j++){colorVertArray.push(vertArray[i+2][j])}if(usingTexture){texVertArray.push(vertArray[i+0][3]);texVertArray.push(vertArray[i+0][4]);texVertArray.push(vertArray[i+1][3]);texVertArray.push(vertArray[i+1][4]);texVertArray.push(vertArray[i+3][3]);texVertArray.push(vertArray[i+3][4]);texVertArray.push(vertArray[i+2][3]);texVertArray.push(vertArray[i+2][4])}fill3D(fillVertArray,"TRIANGLE_STRIP",colorVertArray,texVertArray)}}}else{if(curShape===PConstants.QUAD_STRIP){var tempArray=[];if(vertArray.length>3){for(i=0;i<2;i++){for(j=0;j<3;j++){lineVertArray.push(vertArray[i][j])}}for(i=0;i<2;i++){for(j=9;j<13;j++){strokeVertArray.push(vertArray[i][j])}}line3D(lineVertArray,"LINE_STRIP",strokeVertArray);if(vertArray.length>4&&vertArray.length%2>0){tempArray=fillVertArray.splice(fillVertArray.length-3);vertArray.pop()}for(i=0;(i+3)<vertArray.length;i+=2){lineVertArray=[];strokeVertArray=[];for(j=0;j<3;j++){lineVertArray.push(vertArray[i+1][j])}for(j=0;j<3;j++){lineVertArray.push(vertArray[i+3][j])}for(j=0;j<3;j++){lineVertArray.push(vertArray[i+2][j])}for(j=0;j<3;j++){lineVertArray.push(vertArray[i+0][j])}for(j=9;j<13;j++){strokeVertArray.push(vertArray[i+1][j])}for(j=9;j<13;j++){strokeVertArray.push(vertArray[i+3][j])}for(j=9;j<13;j++){strokeVertArray.push(vertArray[i+2][j])}for(j=9;j<13;j++){strokeVertArray.push(vertArray[i+0][j])}if(doStroke){line3D(lineVertArray,"LINE_STRIP",strokeVertArray)}}if(doFill||usingTexture){fill3D(fillVertArray,"TRIANGLE_LIST",colorVertArray,texVertArray)}}}else{if(vertArray.length===1){for(j=0;j<3;j++){lineVertArray.push(vertArray[0][j])}for(j=9;j<13;j++){strokeVertArray.push(vertArray[0][j])}point3D(lineVertArray,strokeVertArray)}else{for(i=0;i<vertArray.length;i++){for(j=0;j<3;j++){lineVertArray.push(vertArray[i][j])}for(j=5;j<9;j++){strokeVertArray.push(vertArray[i][j])}}if(closeShape){line3D(lineVertArray,"LINE_LOOP",strokeVertArray)}else{line3D(lineVertArray,"LINE_STRIP",strokeVertArray)}if(doFill||usingTexture){fill3D(fillVertArray,"TRIANGLE_FAN",colorVertArray,texVertArray)}}}}}}}}}usingTexture=false;curContext.useProgram(programObject3D);uniformi(programObject3D,"usingTexture",usingTexture)}else{if(curShape===PConstants.POINTS){for(i=0;i<vertArray.length;i++){if(doStroke){p.stroke(vertArray[i][6])}p.point(vertArray[i][0],vertArray[i][1])}}else{if(curShape===PConstants.LINES){for(i=0;(i+1)<vertArray.length;i+=2){if(doStroke){p.stroke(vertArray[i+1][6])}p.line(vertArray[i][0],vertArray[i][1],vertArray[i+1][0],vertArray[i+1][1])}}else{if(curShape===PConstants.TRIANGLES){for(i=0;(i+2)<vertArray.length;i+=3){curContext.beginPath();curContext.moveTo(vertArray[i][0],vertArray[i][1]);curContext.lineTo(vertArray[i+1][0],vertArray[i+1][1]);curContext.lineTo(vertArray[i+2][0],vertArray[i+2][1]);curContext.lineTo(vertArray[i][0],vertArray[i][1]);if(doFill){p.fill(vertArray[i+2][5]);executeContextFill()}if(doStroke){p.stroke(vertArray[i+2][6]);executeContextStroke()}curContext.closePath()}}else{if(curShape===PConstants.TRIANGLE_STRIP){for(i=0;(i+1)<vertArray.length;i++){curContext.beginPath();curContext.moveTo(vertArray[i+1][0],vertArray[i+1][1]);curContext.lineTo(vertArray[i][0],vertArray[i][1]);if(doStroke){p.stroke(vertArray[i+1][6])}if(doFill){p.fill(vertArray[i+1][5])}if(i+2<vertArray.length){curContext.lineTo(vertArray[i+2][0],vertArray[i+2][1]);if(doStroke){p.stroke(vertArray[i+2][6])}if(doFill){p.fill(vertArray[i+2][5])}}executeContextFill();executeContextStroke();curContext.closePath()}}else{if(curShape===PConstants.TRIANGLE_FAN){if(vertArray.length>2){curContext.beginPath();curContext.moveTo(vertArray[0][0],vertArray[0][1]);curContext.lineTo(vertArray[1][0],vertArray[1][1]);curContext.lineTo(vertArray[2][0],vertArray[2][1]);if(doFill){p.fill(vertArray[2][5]);executeContextFill()}if(doStroke){p.stroke(vertArray[2][6]);executeContextStroke()}curContext.closePath();for(i=3;i<vertArray.length;i++){curContext.beginPath();curContext.moveTo(vertArray[0][0],vertArray[0][1]);curContext.lineTo(vertArray[i-1][0],vertArray[i-1][1]);curContext.lineTo(vertArray[i][0],vertArray[i][1]);if(doFill){p.fill(vertArray[i][5]);executeContextFill()}if(doStroke){p.stroke(vertArray[i][6]);executeContextStroke()}curContext.closePath()}}}else{if(curShape===PConstants.QUADS){for(i=0;(i+3)<vertArray.length;i+=4){curContext.beginPath();curContext.moveTo(vertArray[i][0],vertArray[i][1]);for(j=1;j<4;j++){curContext.lineTo(vertArray[i+j][0],vertArray[i+j][1])}curContext.lineTo(vertArray[i][0],vertArray[i][1]);if(doFill){p.fill(vertArray[i+3][5]);executeContextFill()}if(doStroke){p.stroke(vertArray[i+3][6]);executeContextStroke()}curContext.closePath()}}else{if(curShape===PConstants.QUAD_STRIP){if(vertArray.length>3){for(i=0;(i+1)<vertArray.length;i+=2){curContext.beginPath();if(i+3<vertArray.length){curContext.moveTo(vertArray[i+2][0],vertArray[i+2][1]);curContext.lineTo(vertArray[i][0],vertArray[i][1]);curContext.lineTo(vertArray[i+1][0],vertArray[i+1][1]);curContext.lineTo(vertArray[i+3][0],vertArray[i+3][1]);if(doFill){p.fill(vertArray[i+3][5])}if(doStroke){p.stroke(vertArray[i+3][6])}}else{curContext.moveTo(vertArray[i][0],vertArray[i][1]);curContext.lineTo(vertArray[i+1][0],vertArray[i+1][1])}executeContextFill();executeContextStroke();curContext.closePath()}}}else{curContext.beginPath();curContext.moveTo(vertArray[0][0],vertArray[0][1]);for(i=1;i<vertArray.length;i++){if(vertArray[i]["isVert"]===true){if(vertArray[i]["moveTo"]===true){curContext.moveTo(vertArray[i][0],vertArray[i][1])}else{if(vertArray[i]["moveTo"]===false){curContext.lineTo(vertArray[i][0],vertArray[i][1])}else{curContext.lineTo(vertArray[i][0],vertArray[i][1])}}}}if(closeShape){curContext.lineTo(vertArray[0][0],vertArray[0][1])}executeContextFill();executeContextStroke();curContext.closePath()}}}}}}}}}}isCurve=false;isBezier=false;curveVertArray=[];curveVertCount=0};var splineForward=function(segments,matrix){var f=1/segments;var ff=f*f;var fff=ff*f;matrix.set(0,0,0,1,fff,ff,f,0,6*fff,2*ff,0,0,6*fff,0,0,0)};var curveInit=function(){if(!curveDrawMatrix){curveBasisMatrix=new PMatrix3D();curveDrawMatrix=new PMatrix3D();curveInited=true}var s=curTightness;curveBasisMatrix.set(((s-1)/2).toFixed(2),((s+3)/2).toFixed(2),((-3-s)/2).toFixed(2),((1-s)/2).toFixed(2),(1-s),((-5-s)/2).toFixed(2),(s+2),((s-1)/2).toFixed(2),((s-1)/2).toFixed(2),0,((1-s)/2).toFixed(2),0,0,1,0,0);splineForward(curveDet,curveDrawMatrix);if(!bezierBasisInverse){curveToBezierMatrix=new PMatrix3D()}curveToBezierMatrix.set(curveBasisMatrix);curveToBezierMatrix.preApply(bezierBasisInverse);curveDrawMatrix.apply(curveBasisMatrix)};p.bezierVertex=function bezierVertex(){isBezier=true;var vert=[];if(firstVert){throw ("vertex() must be used at least once before calling bezierVertex()")}else{if(arguments.length===9){if(p.use3DContext){if(bezierDrawMatrix===undef){bezierDrawMatrix=new PMatrix3D()}var lastPoint=vertArray.length-1;splineForward(bezDetail,bezierDrawMatrix);bezierDrawMatrix.apply(bezierBasisMatrix);var draw=bezierDrawMatrix.array();var x1=vertArray[lastPoint][0],y1=vertArray[lastPoint][1],z1=vertArray[lastPoint][2];var xplot1=draw[4]*x1+draw[5]*arguments[0]+draw[6]*arguments[3]+draw[7]*arguments[6];var xplot2=draw[8]*x1+draw[9]*arguments[0]+draw[10]*arguments[3]+draw[11]*arguments[6];var xplot3=draw[12]*x1+draw[13]*arguments[0]+draw[14]*arguments[3]+draw[15]*arguments[6];var yplot1=draw[4]*y1+draw[5]*arguments[1]+draw[6]*arguments[4]+draw[7]*arguments[7];var yplot2=draw[8]*y1+draw[9]*arguments[1]+draw[10]*arguments[4]+draw[11]*arguments[7];var yplot3=draw[12]*y1+draw[13]*arguments[1]+draw[14]*arguments[4]+draw[15]*arguments[7];var zplot1=draw[4]*z1+draw[5]*arguments[2]+draw[6]*arguments[5]+draw[7]*arguments[8];var zplot2=draw[8]*z1+draw[9]*arguments[2]+draw[10]*arguments[5]+draw[11]*arguments[8];var zplot3=draw[12]*z1+draw[13]*arguments[2]+draw[14]*arguments[5]+draw[15]*arguments[8];for(var j=0;j<bezDetail;j++){x1+=xplot1;xplot1+=xplot2;xplot2+=xplot3;y1+=yplot1;yplot1+=yplot2;yplot2+=yplot3;z1+=zplot1;zplot1+=zplot2;zplot2+=zplot3;p.vertex(x1,y1,z1)}p.vertex(arguments[6],arguments[7],arguments[8])}}else{for(var i=0;i<arguments.length;i++){vert[i]=arguments[i]}vertArray.push(vert);vertArray[vertArray.length-1]["isVert"]=false}}};var executeTexImage2D=function(){var canvas2d=document.createElement("canvas");try{curContext.texImage2D(curContext.TEXTURE_2D,0,curContext.RGBA,curContext.RGBA,curContext.UNSIGNED_BYTE,canvas2d);executeTexImage2D=function(texture){curContext.texImage2D(curContext.TEXTURE_2D,0,curContext.RGBA,curContext.RGBA,curContext.UNSIGNED_BYTE,texture)}}catch(e){executeTexImage2D=function(texture){curContext.texImage2D(curContext.TEXTURE_2D,0,texture,false)}}executeTexImage2D.apply(this,arguments)};p.texture=function(pimage){if(pimage.localName==="canvas"){curContext.bindTexture(curContext.TEXTURE_2D,canTex);executeTexImage2D(pimage);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_MAG_FILTER,curContext.LINEAR);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_MIN_FILTER,curContext.LINEAR);curContext.generateMipmap(curContext.TEXTURE_2D)}else{if(!pimage.__texture){var texture=curContext.createTexture();pimage.__texture=texture;var cvs=document.createElement("canvas");cvs.width=pimage.width;cvs.height=pimage.height;var ctx=cvs.getContext("2d");var textureImage=ctx.createImageData(cvs.width,cvs.height);var imgData=pimage.toImageData();for(var i=0;i<cvs.width;i+=1){for(var j=0;j<cvs.height;j+=1){var index=(j*cvs.width+i)*4;textureImage.data[index+0]=imgData.data[index+0];textureImage.data[index+1]=imgData.data[index+1];textureImage.data[index+2]=imgData.data[index+2];textureImage.data[index+3]=255}}ctx.putImageData(textureImage,0,0);pimage.__cvs=cvs;curContext.bindTexture(curContext.TEXTURE_2D,pimage.__texture);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_MIN_FILTER,curContext.LINEAR_MIPMAP_LINEAR);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_MAG_FILTER,curContext.LINEAR);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_WRAP_T,curContext.CLAMP_TO_EDGE);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_WRAP_S,curContext.CLAMP_TO_EDGE);executeTexImage2D(pimage.__cvs);curContext.generateMipmap(curContext.TEXTURE_2D)}else{curContext.bindTexture(curContext.TEXTURE_2D,pimage.__texture)}}curTexture.width=pimage.width;curTexture.height=pimage.height;usingTexture=true;curContext.useProgram(programObject3D);uniformi(programObject3D,"usingTexture",usingTexture)};p.textureMode=function(mode){curTextureMode=mode};var curveVertexSegment=function(x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4){var x0=x2;var y0=y2;var z0=z2;var draw=curveDrawMatrix.array();var xplot1=draw[4]*x1+draw[5]*x2+draw[6]*x3+draw[7]*x4;var xplot2=draw[8]*x1+draw[9]*x2+draw[10]*x3+draw[11]*x4;var xplot3=draw[12]*x1+draw[13]*x2+draw[14]*x3+draw[15]*x4;var yplot1=draw[4]*y1+draw[5]*y2+draw[6]*y3+draw[7]*y4;var yplot2=draw[8]*y1+draw[9]*y2+draw[10]*y3+draw[11]*y4;var yplot3=draw[12]*y1+draw[13]*y2+draw[14]*y3+draw[15]*y4;var zplot1=draw[4]*z1+draw[5]*z2+draw[6]*z3+draw[7]*z4;var zplot2=draw[8]*z1+draw[9]*z2+draw[10]*z3+draw[11]*z4;var zplot3=draw[12]*z1+draw[13]*z2+draw[14]*z3+draw[15]*z4;p.vertex(x0,y0,z0);for(var j=0;j<curveDet;j++){x0+=xplot1;xplot1+=xplot2;xplot2+=xplot3;y0+=yplot1;yplot1+=yplot2;yplot2+=yplot3;z0+=zplot1;zplot1+=zplot2;zplot2+=zplot3;p.vertex(x0,y0,z0)}};p.curveVertex=function(x,y,z){isCurve=true;if(p.use3DContext){if(!curveInited){curveInit()}var vert=[];vert[0]=x;vert[1]=y;vert[2]=z;curveVertArray.push(vert);curveVertCount++;if(curveVertCount>3){curveVertexSegment(curveVertArray[curveVertCount-4][0],curveVertArray[curveVertCount-4][1],curveVertArray[curveVertCount-4][2],curveVertArray[curveVertCount-3][0],curveVertArray[curveVertCount-3][1],curveVertArray[curveVertCount-3][2],curveVertArray[curveVertCount-2][0],curveVertArray[curveVertCount-2][1],curveVertArray[curveVertCount-2][2],curveVertArray[curveVertCount-1][0],curveVertArray[curveVertCount-1][1],curveVertArray[curveVertCount-1][2])}}else{p.vertex(x,y,z)}};p.curve=function curve(){if(arguments.length===8){p.beginShape();p.curveVertex(arguments[0],arguments[1]);p.curveVertex(arguments[2],arguments[3]);p.curveVertex(arguments[4],arguments[5]);p.curveVertex(arguments[6],arguments[7]);p.endShape()}else{if(p.use3DContext){p.beginShape();p.curveVertex(arguments[0],arguments[1],arguments[2]);p.curveVertex(arguments[3],arguments[4],arguments[5]);p.curveVertex(arguments[6],arguments[7],arguments[8]);p.curveVertex(arguments[9],arguments[10],arguments[11]);p.endShape()}}};p.curveTightness=function(tightness){curTightness=tightness};p.curveDetail=function curveDetail(detail){curveDet=detail;curveInit()};p.rectMode=function rectMode(aRectMode){curRectMode=aRectMode};p.imageMode=function(mode){switch(mode){case PConstants.CORNER:imageModeConvert=imageModeCorner;break;case PConstants.CORNERS:imageModeConvert=imageModeCorners;break;case PConstants.CENTER:imageModeConvert=imageModeCenter;break;default:throw"Invalid imageMode"}};p.ellipseMode=function ellipseMode(aEllipseMode){curEllipseMode=aEllipseMode};p.arc=function arc(x,y,width,height,start,stop){if(width<=0){return}if(curEllipseMode===PConstants.CORNER){x+=width/2;y+=height/2}curContext.moveTo(x,y);curContext.beginPath();curContext.arc(x,y,curEllipseMode===PConstants.CENTER_RADIUS?width:width/2,start,stop,false);executeContextStroke();curContext.lineTo(x,y);executeContextFill();curContext.closePath()};p.line=function line(){var x1,y1,z1,x2,y2,z2;if(p.use3DContext){if(arguments.length===6){x1=arguments[0];y1=arguments[1];z1=arguments[2];x2=arguments[3];y2=arguments[4];z2=arguments[5]}else{if(arguments.length===4){x1=arguments[0];y1=arguments[1];z1=0;x2=arguments[2];y2=arguments[3];z2=0}}var lineVerts=[x1,y1,z1,x2,y2,z2];var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();if(lineWidth>0&&doStroke){curContext.useProgram(programObject2D);uniformMatrix(programObject2D,"model",false,[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);uniformMatrix(programObject2D,"view",false,view.array());uniformMatrix(programObject2D,"projection",false,proj.array());uniformf(programObject2D,"color",strokeStyle);uniformi(programObject2D,"picktype",0);curContext.lineWidth(lineWidth);vertexAttribPointer(programObject2D,"Vertex",3,lineBuffer);disableVertexAttribPointer(programObject2D,"aTextureCoord");curContext.bufferData(curContext.ARRAY_BUFFER,new Float32Array(lineVerts),curContext.STREAM_DRAW);curContext.drawArrays(curContext.LINES,0,2)}}else{x1=arguments[0];y1=arguments[1];x2=arguments[2];y2=arguments[3];if((x1===x2||y1===y2)&&lineWidth<=1&&doStroke&&curSketch.options.crispLines){var temp;if(x1===x2){if(y1>y2){temp=y1;y1=y2;y2=temp}for(var y=y1;y<=y2;++y){p.set(x1,y,currentStrokeColor)}}else{if(x1>x2){temp=x1;x1=x2;x2=temp}for(var x=x1;x<=x2;++x){p.set(x,y1,currentStrokeColor)}}return}if(doStroke){curContext.beginPath();curContext.moveTo(x1||0,y1||0);curContext.lineTo(x2||0,y2||0);executeContextStroke();curContext.closePath()}}};p.bezier=function bezier(){if(arguments.length===8&&!p.use3DContext){p.beginShape();p.vertex(arguments[0],arguments[1]);p.bezierVertex(arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7]);p.endShape()}else{if(arguments.length===12&&p.use3DContext){p.beginShape();p.vertex(arguments[0],arguments[1],arguments[2]);p.bezierVertex(arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8],arguments[9],arguments[10],arguments[11]);p.endShape()}else{throw ("Please use the proper parameters!")}}};p.bezierDetail=function bezierDetail(detail){bezDetail=detail};p.bezierPoint=function bezierPoint(a,b,c,d,t){return(1-t)*(1-t)*(1-t)*a+3*(1-t)*(1-t)*t*b+3*(1-t)*t*t*c+t*t*t*d};p.bezierTangent=function bezierTangent(a,b,c,d,t){return(3*t*t*(-a+3*b-3*c+d)+6*t*(a-2*b+c)+3*(-a+b))};p.curvePoint=function curvePoint(a,b,c,d,t){return 0.5*((2*b)+(-a+c)*t+(2*a-5*b+4*c-d)*t*t+(-a+3*b-3*c+d)*t*t*t)};p.curveTangent=function curveTangent(a,b,c,d,t){return 0.5*((-a+c)+2*(2*a-5*b+4*c-d)*t+3*(-a+3*b-3*c+d)*t*t)};p.triangle=function triangle(x1,y1,x2,y2,x3,y3){p.beginShape(PConstants.TRIANGLES);p.vertex(x1,y1,0);p.vertex(x2,y2,0);p.vertex(x3,y3,0);p.endShape()};p.quad=function quad(x1,y1,x2,y2,x3,y3,x4,y4){p.beginShape(PConstants.QUADS);p.vertex(x1,y1,0);p.vertex(x2,y2,0);p.vertex(x3,y3,0);p.vertex(x4,y4,0);p.endShape()};p.rect=function rect(x,y,width,height){if(p.use3DContext){var model=new PMatrix3D();model.translate(x,y,0);model.scale(width,height,1);model.transpose();var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();if(lineWidth>0&&doStroke){curContext.useProgram(programObject2D);uniformMatrix(programObject2D,"model",false,model.array());uniformMatrix(programObject2D,"view",false,view.array());uniformMatrix(programObject2D,"projection",false,proj.array());uniformf(programObject2D,"color",strokeStyle);uniformi(programObject2D,"picktype",0);vertexAttribPointer(programObject2D,"Vertex",3,rectBuffer);disableVertexAttribPointer(programObject2D,"aTextureCoord");curContext.lineWidth(lineWidth);curContext.drawArrays(curContext.LINE_LOOP,0,rectVerts.length/3)}if(doFill){curContext.useProgram(programObject3D);uniformMatrix(programObject3D,"model",false,model.array());uniformMatrix(programObject3D,"view",false,view.array());uniformMatrix(programObject3D,"projection",false,proj.array());curContext.enable(curContext.POLYGON_OFFSET_FILL);curContext.polygonOffset(1,1);uniformf(programObject3D,"color",fillStyle);var v=new PMatrix3D();v.set(view);var m=new PMatrix3D();m.set(model);v.mult(m);var normalMatrix=new PMatrix3D();normalMatrix.set(v);normalMatrix.invert();normalMatrix.transpose();uniformMatrix(programObject3D,"normalTransform",false,normalMatrix.array());vertexAttribPointer(programObject3D,"Vertex",3,rectBuffer);vertexAttribPointer(programObject3D,"Normal",3,rectNormBuffer);curContext.drawArrays(curContext.TRIANGLE_FAN,0,rectVerts.length/3);curContext.disable(curContext.POLYGON_OFFSET_FILL)}}else{if(!width&&!height){return}if(doStroke&&!doFill&&lineWidth<=1&&curSketch.options.crispLines){var i,x2=x+width-1,y2=y+height-1;for(i=0;i<width;++i){p.set(x+i,y,currentStrokeColor);p.set(x+i,y2,currentStrokeColor)}for(i=0;i<height;++i){p.set(x,y+i,currentStrokeColor);p.set(x2,y+i,currentStrokeColor)}return}curContext.beginPath();var offsetStart=0;var offsetEnd=0;if(curRectMode===PConstants.CORNERS){width-=x;height-=y}if(curRectMode===PConstants.RADIUS){width*=2;height*=2}if(curRectMode===PConstants.CENTER||curRectMode===PConstants.RADIUS){x-=width/2;y-=height/2}curContext.rect(Math.round(x)-offsetStart,Math.round(y)-offsetStart,Math.round(width)+offsetEnd,Math.round(height)+offsetEnd);executeContextFill();executeContextStroke();curContext.closePath()}};p.ellipse=function ellipse(x,y,width,height){x=x||0;y=y||0;if(width<=0&&height<=0){return}if(curEllipseMode===PConstants.RADIUS){width*=2;height*=2}if(curEllipseMode===PConstants.CORNERS){width=width-x;height=height-y}if(curEllipseMode===PConstants.CORNER||curEllipseMode===PConstants.CORNERS){x+=width/2;y+=height/2}var offsetStart=0;if((!p.use3DContext)&&(width===height)){curContext.beginPath();curContext.arc(x-offsetStart,y-offsetStart,width/2,0,PConstants.TWO_PI,false);executeContextFill();executeContextStroke();curContext.closePath()}else{var w=width/2,h=height/2,C=0.5522847498307933;var c_x=C*w,c_y=C*h;if(!p.use3DContext){p.beginShape();p.vertex(x+w,y);p.bezierVertex(x+w,y-c_y,x+c_x,y-h,x,y-h);p.bezierVertex(x-c_x,y-h,x-w,y-c_y,x-w,y);p.bezierVertex(x-w,y+c_y,x-c_x,y+h,x,y+h);p.bezierVertex(x+c_x,y+h,x+w,y+c_y,x+w,y);p.endShape()}else{p.beginShape();p.vertex(x+w,y);p.bezierVertex(x+w,y-c_y,0,x+c_x,y-h,0,x,y-h,0);p.bezierVertex(x-c_x,y-h,0,x-w,y-c_y,0,x-w,y,0);p.bezierVertex(x-w,y+c_y,0,x-c_x,y+h,0,x,y+h,0);p.bezierVertex(x+c_x,y+h,0,x+w,y+c_y,0,x+w,y,0);p.endShape();var xAv=0,yAv=0,i,j;for(i=0;i<vertArray.length;i++){xAv+=vertArray[i][0];yAv+=vertArray[i][1]}xAv/=vertArray.length;yAv/=vertArray.length;var vert=[],fillVertArray=[],colorVertArray=[];vert[0]=xAv;vert[1]=yAv;vert[2]=0;vert[3]=0;vert[4]=0;vert[5]=fillStyle[0];vert[6]=fillStyle[1];vert[7]=fillStyle[2];vert[8]=fillStyle[3];vert[9]=strokeStyle[0];vert[10]=strokeStyle[1];vert[11]=strokeStyle[2];vert[12]=strokeStyle[3];vert[13]=normalX;vert[14]=normalY;vert[15]=normalZ;vertArray.unshift(vert);for(i=0;i<vertArray.length;i++){for(j=0;j<3;j++){fillVertArray.push(vertArray[i][j])}for(j=5;j<9;j++){colorVertArray.push(vertArray[i][j])}}fill3D(fillVertArray,"TRIANGLE_FAN",colorVertArray)}}};p.normal=function normal(nx,ny,nz){if(arguments.length!==3||!(typeof nx==="number"&&typeof ny==="number"&&typeof nz==="number")){throw"normal() requires three numeric arguments."}normalX=nx;normalY=ny;normalZ=nz;if(curShape!==0){if(normalMode===PConstants.NORMAL_MODE_AUTO){normalMode=PConstants.NORMAL_MODE_SHAPE}else{if(normalMode===PConstants.NORMAL_MODE_SHAPE){normalMode=PConstants.NORMAL_MODE_VERTEX}}}};p.save=function save(file,img){if(img!==undef){return window.open(img.toDataURL(),"_blank")}else{return window.open(p.externals.canvas.toDataURL(),"_blank")}};var utilityContext2d=document.createElement("canvas").getContext("2d");var canvasDataCache=[undef,undef,undef];function getCanvasData(obj,w,h){var canvasData=canvasDataCache.shift();if(canvasData===undef){canvasData={};canvasData.canvas=document.createElement("canvas");canvasData.context=canvasData.canvas.getContext("2d")}canvasDataCache.push(canvasData);var canvas=canvasData.canvas,context=canvasData.context,width=w||obj.width,height=h||obj.height;canvas.width=width;canvas.height=height;if(!obj){context.clearRect(0,0,width,height)}else{if("data" in obj){context.putImageData(obj,0,0)}else{context.clearRect(0,0,width,height);context.drawImage(obj,0,0,width,height)}}return canvasData}var PImage=function PImage(aWidth,aHeight,aFormat){this.get=function(x,y,w,h){if(!arguments.length){return p.get(this)}else{if(arguments.length===2){return p.get(x,y,this)}else{if(arguments.length===4){return p.get(x,y,w,h,this)}}}};this.set=function(x,y,c){p.set(x,y,c,this)};this.blend=function(srcImg,x,y,width,height,dx,dy,dwidth,dheight,MODE){if(arguments.length===9){p.blend(this,srcImg,x,y,width,height,dx,dy,dwidth,dheight,this)}else{if(arguments.length===10){p.blend(srcImg,x,y,width,height,dx,dy,dwidth,dheight,MODE,this)}}};this.copy=function(srcImg,sx,sy,swidth,sheight,dx,dy,dwidth,dheight){if(arguments.length===8){p.blend(this,srcImg,sx,sy,swidth,sheight,dx,dy,dwidth,PConstants.REPLACE,this)}else{if(arguments.length===9){p.blend(srcImg,sx,sy,swidth,sheight,dx,dy,dwidth,dheight,PConstants.REPLACE,this)}}};this.filter=function(mode,param){if(arguments.length===2){p.filter(mode,param,this)}else{if(arguments.length===1){p.filter(mode,null,this)}}};this.save=function(file){p.save(file,this)};this.resize=function(w,h){if(this.isRemote){throw"Image is loaded remotely. Cannot resize."}else{if(this.width!==0||this.height!==0){if(w===0&&h!==0){w=this.width/this.height*h}else{if(h===0&&w!==0){h=w/(this.width/this.height)}}var canvas=getCanvasData(this.imageData).canvas;var imageData=getCanvasData(canvas,w,h).context.getImageData(0,0,w,h);this.fromImageData(imageData)}}};this.mask=function(mask){this.__mask=undef;if(mask instanceof PImage){if(mask.width===this.width&&mask.height===this.height){this.__mask=mask}else{throw"mask must have the same dimensions as PImage."}}else{if(typeof mask==="object"&&mask.constructor===Array){if(this.pixels.length===mask.length){this.__mask=mask}else{throw"mask array must be the same length as PImage pixels array."}}}};this.pixels={getLength:(function(aImg){if(aImg.isRemote){throw"Image is loaded remotely. Cannot get length."}else{return function(){return aImg.imageData.data.length?aImg.imageData.data.length/4:0}}}(this)),getPixel:(function(aImg){if(aImg.isRemote){throw"Image is loaded remotely. Cannot get pixels."}else{return function(i){var offset=i*4;return p.color.toInt(aImg.imageData.data[offset],aImg.imageData.data[offset+1],aImg.imageData.data[offset+2],aImg.imageData.data[offset+3])}}}(this)),setPixel:(function(aImg){if(aImg.isRemote){throw"Image is loaded remotely. Cannot set pixel."}else{return function(i,c){var offset=i*4;aImg.imageData.data[offset+0]=(c&PConstants.RED_MASK)>>>16;aImg.imageData.data[offset+1]=(c&PConstants.GREEN_MASK)>>>8;aImg.imageData.data[offset+2]=(c&PConstants.BLUE_MASK);aImg.imageData.data[offset+3]=(c&PConstants.ALPHA_MASK)>>>24}}}(this)),set:function(arr){if(this.isRemote){throw"Image is loaded remotely. Cannot set pixels."}else{for(var i=0,aL=arr.length;i<aL;i++){this.setPixel(i,arr[i])}}}};this.loadPixels=function(){};this.updatePixels=function(){};this.toImageData=function(){if(this.isRemote){return this.sourceImg}else{var canvasData=getCanvasData(this.imageData);return canvasData.context.getImageData(0,0,this.width,this.height)}};this.toDataURL=function(){if(this.isRemote){throw"Image is loaded remotely. Cannot create dataURI."}else{var canvasData=getCanvasData(this.imageData);return canvasData.canvas.toDataURL()}};this.fromImageData=function(canvasImg){this.width=canvasImg.width;this.height=canvasImg.height;this.imageData=canvasImg;this.format=PConstants.ARGB};this.fromHTMLImageData=function(htmlImg){var canvasData=getCanvasData(htmlImg);try{var imageData=canvasData.context.getImageData(0,0,htmlImg.width,htmlImg.height);this.fromImageData(imageData)}catch(e){if(htmlImg.width&&htmlImg.height){this.isRemote=true;this.width=htmlImg.width;this.height=htmlImg.height}}this.sourceImg=htmlImg};if(arguments.length===1){this.fromHTMLImageData(arguments[0])}else{if(arguments.length===2||arguments.length===3){this.width=aWidth||1;this.height=aHeight||1;this.imageData=utilityContext2d.createImageData(this.width,this.height);this.format=(aFormat===PConstants.ARGB||aFormat===PConstants.ALPHA)?aFormat:PConstants.RGB}else{this.width=0;this.height=0;this.imageData=utilityContext2d.createImageData(1,1);this.format=PConstants.ARGB}}};p.PImage=PImage;p.createImage=function createImage(w,h,mode){return new PImage(w,h,mode)};p.loadImage=function loadImage(file,type,callback){if(type){file=file+"."+type}if(curSketch.imageCache.images[file]){return new PImage(curSketch.imageCache.images[file])}else{var pimg=new PImage(0,0,PConstants.ARGB);var img=document.createElement("img");pimg.sourceImg=img;img.onload=(function(aImage,aPImage,aCallback){var image=aImage;var pimg=aPImage;var callback=aCallback;return function(){pimg.fromHTMLImageData(image);pimg.loaded=true;if(callback){callback()}}}(img,pimg,callback));img.src=file;return pimg}};p.requestImage=p.loadImage;function get$0(){var c=new PImage(p.width,p.height,PConstants.RGB);c.fromImageData(curContext.getImageData(0,0,p.width,p.height));return c}function get$2(x,y){var data;if(x<p.width&&x>=0&&y>=0&&y<p.height){if(isContextReplaced){var offset=((0|x)+p.width*(0|y))*4;data=p.imageData.data;return p.color.toInt(data[offset],data[offset+1],data[offset+2],data[offset+3])}data=curContext.getImageData(0|x,0|y,1,1).data;return p.color.toInt(data[0],data[1],data[2],data[3])}else{return 0}}function get$3(x,y,img){if(img.isRemote){throw"Image is loaded remotely. Cannot get x,y."}else{var offset=y*img.width*4+(x*4);return p.color.toInt(img.imageData.data[offset],img.imageData.data[offset+1],img.imageData.data[offset+2],img.imageData.data[offset+3])}}function get$4(x,y,w,h){var c=new PImage(w,h,PConstants.RGB);c.fromImageData(curContext.getImageData(x,y,w,h));return c}function get$5(x,y,w,h,img){if(img.isRemote){throw"Image is loaded remotely. Cannot get x,y,w,h."}else{var start=y*img.width*4+(x*4);var end=(y+h)*img.width*4+((x+w)*4);var c=new PImage(w,h,PConstants.RGB);for(var i=start,j=0;i<end;i++,j++){c.imageData.data[j]=img.imageData.data[i];if((j+1)%(w*4)===0){i+=(img.width-w)*4}}return c}}p.get=function get(x,y,w,h,img){if(arguments.length===2){return get$2(x,y)}else{if(arguments.length===0){return get$0()}else{if(arguments.length===5){return get$5(x,y,w,h,img)}else{if(arguments.length===4){return get$4(x,y,w,h)}else{if(arguments.length===3){return get$3(x,y,w)}else{if(arguments.length===1){return x}}}}}}};p.createGraphics=function createGraphics(w,h,render){var canvas=document.createElement("canvas");var pg=new Processing(canvas);pg.size(w,h,render);pg.canvas=canvas;return pg};function resetContext(){if(isContextReplaced){curContext=originalContext;isContextReplaced=false;p.updatePixels()}}function SetPixelContextWrapper(){function wrapFunction(newContext,name){function wrapper(){resetContext();curContext[name].apply(curContext,arguments)}newContext[name]=wrapper}function wrapProperty(newContext,name){function getter(){resetContext();return curContext[name]}function setter(value){resetContext();curContext[name]=value}p.defineProperty(newContext,name,{get:getter,set:setter})}for(var n in curContext){if(typeof curContext[n]==="function"){wrapFunction(this,n)}else{wrapProperty(this,n)}}}function replaceContext(){if(isContextReplaced){return}p.loadPixels();if(proxyContext===null){originalContext=curContext;proxyContext=new SetPixelContextWrapper()}isContextReplaced=true;curContext=proxyContext;setPixelsCached=0}function set$3(x,y,c){if(x<p.width&&x>=0&&y>=0&&y<p.height){replaceContext();p.pixels.setPixel((0|x)+p.width*(0|y),c);if(++setPixelsCached>maxPixelsCached){resetContext()}}}function set$4(x,y,obj,img){if(img.isRemote){throw"Image is loaded remotely. Cannot set x,y."}else{var c=p.color.toArray(obj);var offset=y*img.width*4+(x*4);var data=img.imageData.data;data[offset]=c[0];data[offset+1]=c[1];data[offset+2]=c[2];data[offset+3]=c[3]}}p.set=function set(x,y,obj,img){var color,oldFill;if(arguments.length===3){if(typeof obj==="number"){set$3(x,y,obj)}else{if(obj instanceof PImage){p.image(obj,x,y)}}}else{if(arguments.length===4){set$4(x,y,obj,img)}}};p.imageData={};p.pixels={getLength:function(){return p.imageData.data.length?p.imageData.data.length/4:0},getPixel:function(i){var offset=i*4;return(p.imageData.data[offset+3]<<24)&4278190080|(p.imageData.data[offset+0]<<16)&16711680|(p.imageData.data[offset+1]<<8)&65280|p.imageData.data[offset+2]&255},setPixel:function(i,c){var offset=i*4;p.imageData.data[offset+0]=(c&16711680)>>>16;p.imageData.data[offset+1]=(c&65280)>>>8;p.imageData.data[offset+2]=(c&255);p.imageData.data[offset+3]=(c&4278190080)>>>24},set:function(arr){for(var i=0,aL=arr.length;i<aL;i++){this.setPixel(i,arr[i])}}};p.loadPixels=function(){p.imageData=curContext.getImageData(0,0,p.width,p.height)};p.updatePixels=function(){if(p.imageData){curContext.putImageData(p.imageData,0,0)}};p.hint=function hint(which){if(which===PConstants.DISABLE_DEPTH_TEST){curContext.disable(curContext.DEPTH_TEST);curContext.depthMask(false);curContext.clear(curContext.DEPTH_BUFFER_BIT)}else{if(which===PConstants.ENABLE_DEPTH_TEST){curContext.enable(curContext.DEPTH_TEST);curContext.depthMask(true)}}};p.background=function background(){var color,a,img;if(typeof arguments[0]==="number"){color=p.color.apply(this,arguments);if(!curSketch.options.isTransparent){color=color|PConstants.ALPHA_MASK}}else{if(arguments.length===1&&arguments[0] instanceof PImage){img=arguments[0];if(!img.pixels||img.width!==p.width||img.height!==p.height){throw"Background image must be the same dimensions as the canvas."}}else{throw"Incorrect background parameters."}}if(p.use3DContext){if(color!==undef){var c=p.color.toGLArray(color);refreshBackground=function(){curContext.clearColor(c[0],c[1],c[2],c[3]);curContext.clear(curContext.COLOR_BUFFER_BIT|curContext.DEPTH_BUFFER_BIT)}}else{refreshBackground=function(){}}}else{if(color!==undef){refreshBackground=function(){if(curSketch.options.isTransparent){curContext.clearRect(0,0,p.width,p.height)}curContext.fillStyle=p.color.toString(color);curContext.fillRect(0,0,p.width,p.height);isFillDirty=true}}else{refreshBackground=function(){p.image(img,0,0)}}}refreshBackground()};p.image=function image(img,x,y,w,h){if(img.width>0){var wid=w||img.width;var hgt=h||img.height;if(p.use3DContext){p.beginShape(p.QUADS);p.texture(img.externals.canvas);p.vertex(x,y,0,0,0);p.vertex(x,y+hgt,0,0,hgt);p.vertex(x+wid,y+hgt,0,wid,hgt);p.vertex(x+wid,y,0,wid,0);p.endShape()}else{var bounds=imageModeConvert(x||0,y||0,w||img.width,h||img.height,arguments.length<4);var obj=img.toImageData();if(img.__mask){var j,size;if(img.__mask instanceof PImage){var objMask=img.__mask.toImageData();for(j=2,size=img.width*img.height*4;j<size;j+=4){obj.data[j+1]=objMask.data[j]}}else{for(j=0,size=img.__mask.length;j<size;++j){obj.data[(j<<2)+3]=img.__mask[j]}}}curTint(obj);curContext.drawImage(getCanvasData(obj).canvas,0,0,img.width,img.height,bounds.x,bounds.y,bounds.w,bounds.h)}}};p.clear=function clear(x,y,width,height){if(arguments.length===0){curContext.clearRect(0,0,p.width,p.height)}else{curContext.clearRect(x,y,width,height)}};p.tint=function tint(){var tintColor=p.color.apply(this,arguments);var r=p.red(tintColor)/colorModeX;var g=p.green(tintColor)/colorModeY;var b=p.blue(tintColor)/colorModeZ;var a=p.alpha(tintColor)/colorModeA;curTint=function(obj){var data=obj.data,length=4*obj.width*obj.height;for(var i=0;i<length;){data[i++]*=r;data[i++]*=g;data[i++]*=b;data[i++]*=a}}};p.noTint=function noTint(){curTint=function(){}};p.copy=function copy(src,sx,sy,sw,sh,dx,dy,dw,dh){if(arguments.length===8){dh=dw;dw=dy;dy=dx;dx=sh;sh=sw;sw=sy;sy=sx;sx=src;src=p}p.blend(src,sx,sy,sw,sh,dx,dy,dw,dh,PConstants.REPLACE)};p.blend=function blend(src,sx,sy,sw,sh,dx,dy,dw,dh,mode,pimgdest){if(arguments.length===9){mode=dh;dh=dw;dw=dy;dy=dx;dx=sh;sh=sw;sw=sy;sy=sx;sx=src;src=p}var sx2=sx+sw;var sy2=sy+sh;var dx2=dx+dw;var dy2=dy+dh;var dest;if(src.isRemote){throw"Image is loaded remotely. Cannot blend image."}else{if(arguments.length===10||arguments.length===9){p.loadPixels();dest=p}else{if(arguments.length===11&&pimgdest&&pimgdest.imageData){dest=pimgdest}}if(src===p){if(p.intersect(sx,sy,sx2,sy2,dx,dy,dx2,dy2)){p.blit_resize(p.get(sx,sy,sx2-sx,sy2-sy),0,0,sx2-sx-1,sy2-sy-1,dest.imageData.data,dest.width,dest.height,dx,dy,dx2,dy2,mode)}else{p.blit_resize(src,sx,sy,sx2,sy2,dest.imageData.data,dest.width,dest.height,dx,dy,dx2,dy2,mode)}}else{src.loadPixels();p.blit_resize(src,sx,sy,sx2,sy2,dest.imageData.data,dest.width,dest.height,dx,dy,dx2,dy2,mode)}if(arguments.length===10){p.updatePixels()}}};var buildBlurKernel=function buildBlurKernel(r){var radius=p.floor(r*3.5),i,radiusi;radius=(radius<1)?1:((radius<248)?radius:248);if(p.shared.blurRadius!==radius){p.shared.blurRadius=radius;p.shared.blurKernelSize=1+(p.shared.blurRadius<<1);p.shared.blurKernel=new Array(p.shared.blurKernelSize);for(i=0;i<p.shared.blurKernelSize;i++){p.shared.blurKernel[i]=0}for(i=1,radiusi=radius-1;i<radius;i++){p.shared.blurKernel[radius+i]=p.shared.blurKernel[radiusi]=radiusi*radiusi}p.shared.blurKernel[radius]=radius*radius}};var blurARGB=function blurARGB(r,aImg){var sum,cr,cg,cb,ca,c,m;var read,ri,ym,ymi,bk0;var wh=aImg.pixels.getLength();var r2=new Array(wh);var g2=new Array(wh);var b2=new Array(wh);var a2=new Array(wh);var yi=0;var x,y,i;buildBlurKernel(r);for(y=0;y<aImg.height;y++){for(x=0;x<aImg.width;x++){cb=cg=cr=ca=sum=0;read=x-p.shared.blurRadius;if(read<0){bk0=-read;read=0}else{if(read>=aImg.width){break}bk0=0}for(i=bk0;i<p.shared.blurKernelSize;i++){if(read>=aImg.width){break}c=aImg.pixels.getPixel(read+yi);m=p.shared.blurKernel[i];ca+=m*((c&PConstants.ALPHA_MASK)>>>24);cr+=m*((c&PConstants.RED_MASK)>>16);cg+=m*((c&PConstants.GREEN_MASK)>>8);cb+=m*(c&PConstants.BLUE_MASK);sum+=m;read++}ri=yi+x;a2[ri]=ca/sum;r2[ri]=cr/sum;g2[ri]=cg/sum;b2[ri]=cb/sum}yi+=aImg.width}yi=0;ym=-p.shared.blurRadius;ymi=ym*aImg.width;for(y=0;y<aImg.height;y++){for(x=0;x<aImg.width;x++){cb=cg=cr=ca=sum=0;if(ym<0){bk0=ri=-ym;read=x}else{if(ym>=aImg.height){break}bk0=0;ri=ym;read=x+ymi}for(i=bk0;i<p.shared.blurKernelSize;i++){if(ri>=aImg.height){break}m=p.shared.blurKernel[i];ca+=m*a2[read];cr+=m*r2[read];cg+=m*g2[read];cb+=m*b2[read];sum+=m;ri++;read+=aImg.width}aImg.pixels.setPixel(x+yi,((ca/sum)<<24|(cr/sum)<<16|(cg/sum)<<8|(cb/sum)))}yi+=aImg.width;ymi+=aImg.width;ym++}};var dilate=function dilate(isInverted,aImg){var currIdx=0;var maxIdx=aImg.pixels.getLength();var out=new Array(maxIdx);var currRowIdx,maxRowIdx,colOrig,colOut,currLum;var idxRight,idxLeft,idxUp,idxDown,colRight,colLeft,colUp,colDown,lumRight,lumLeft,lumUp,lumDown;if(!isInverted){while(currIdx<maxIdx){currRowIdx=currIdx;maxRowIdx=currIdx+aImg.width;while(currIdx<maxRowIdx){colOrig=colOut=aImg.pixels.getPixel(currIdx);idxLeft=currIdx-1;idxRight=currIdx+1;idxUp=currIdx-aImg.width;idxDown=currIdx+aImg.width;if(idxLeft<currRowIdx){idxLeft=currIdx}if(idxRight>=maxRowIdx){idxRight=currIdx}if(idxUp<0){idxUp=0}if(idxDown>=maxIdx){idxDown=currIdx}colUp=aImg.pixels.getPixel(idxUp);colLeft=aImg.pixels.getPixel(idxLeft);colDown=aImg.pixels.getPixel(idxDown);colRight=aImg.pixels.getPixel(idxRight);currLum=77*(colOrig>>16&255)+151*(colOrig>>8&255)+28*(colOrig&255);lumLeft=77*(colLeft>>16&255)+151*(colLeft>>8&255)+28*(colLeft&255);lumRight=77*(colRight>>16&255)+151*(colRight>>8&255)+28*(colRight&255);lumUp=77*(colUp>>16&255)+151*(colUp>>8&255)+28*(colUp&255);lumDown=77*(colDown>>16&255)+151*(colDown>>8&255)+28*(colDown&255);if(lumLeft>currLum){colOut=colLeft;currLum=lumLeft}if(lumRight>currLum){colOut=colRight;currLum=lumRight}if(lumUp>currLum){colOut=colUp;currLum=lumUp}if(lumDown>currLum){colOut=colDown;currLum=lumDown}out[currIdx++]=colOut}}}else{while(currIdx<maxIdx){currRowIdx=currIdx;maxRowIdx=currIdx+aImg.width;while(currIdx<maxRowIdx){colOrig=colOut=aImg.pixels.getPixel(currIdx);idxLeft=currIdx-1;idxRight=currIdx+1;idxUp=currIdx-aImg.width;idxDown=currIdx+aImg.width;if(idxLeft<currRowIdx){idxLeft=currIdx}if(idxRight>=maxRowIdx){idxRight=currIdx}if(idxUp<0){idxUp=0}if(idxDown>=maxIdx){idxDown=currIdx}colUp=aImg.pixels.getPixel(idxUp);colLeft=aImg.pixels.getPixel(idxLeft);colDown=aImg.pixels.getPixel(idxDown);colRight=aImg.pixels.getPixel(idxRight);currLum=77*(colOrig>>16&255)+151*(colOrig>>8&255)+28*(colOrig&255);lumLeft=77*(colLeft>>16&255)+151*(colLeft>>8&255)+28*(colLeft&255);lumRight=77*(colRight>>16&255)+151*(colRight>>8&255)+28*(colRight&255);lumUp=77*(colUp>>16&255)+151*(colUp>>8&255)+28*(colUp&255);lumDown=77*(colDown>>16&255)+151*(colDown>>8&255)+28*(colDown&255);if(lumLeft<currLum){colOut=colLeft;currLum=lumLeft}if(lumRight<currLum){colOut=colRight;currLum=lumRight}if(lumUp<currLum){colOut=colUp;currLum=lumUp}if(lumDown<currLum){colOut=colDown;currLum=lumDown}out[currIdx++]=colOut}}}aImg.pixels.set(out)};p.filter=function filter(kind,param,aImg){var img,col,lum,i;if(arguments.length===3){aImg.loadPixels();img=aImg}else{p.loadPixels();img=p}if(param===undef){param=null}if(img.isRemote){throw"Image is loaded remotely. Cannot filter image."}else{var imglen=img.pixels.getLength();switch(kind){case PConstants.BLUR:var radius=param||1;blurARGB(radius,img);break;case PConstants.GRAY:if(img.format===PConstants.ALPHA){for(i=0;i<imglen;i++){col=255-img.pixels.getPixel(i);img.pixels.setPixel(i,(4278190080|(col<<16)|(col<<8)|col))}img.format=PConstants.RGB}else{for(i=0;i<imglen;i++){col=img.pixels.getPixel(i);lum=(77*(col>>16&255)+151*(col>>8&255)+28*(col&255))>>8;img.pixels.setPixel(i,((col&PConstants.ALPHA_MASK)|lum<<16|lum<<8|lum))}}break;case PConstants.INVERT:for(i=0;i<imglen;i++){img.pixels.setPixel(i,(img.pixels.getPixel(i)^16777215))}break;case PConstants.POSTERIZE:if(param===null){throw"Use filter(POSTERIZE, int levels) instead of filter(POSTERIZE)"}var levels=p.floor(param);if((levels<2)||(levels>255)){throw"Levels must be between 2 and 255 for filter(POSTERIZE, levels)"}var levels1=levels-1;for(i=0;i<imglen;i++){var rlevel=(img.pixels.getPixel(i)>>16)&255;var glevel=(img.pixels.getPixel(i)>>8)&255;var blevel=img.pixels.getPixel(i)&255;rlevel=(((rlevel*levels)>>8)*255)/levels1;glevel=(((glevel*levels)>>8)*255)/levels1;blevel=(((blevel*levels)>>8)*255)/levels1;img.pixels.setPixel(i,((4278190080&img.pixels.getPixel(i))|(rlevel<<16)|(glevel<<8)|blevel))}break;case PConstants.OPAQUE:for(i=0;i<imglen;i++){img.pixels.setPixel(i,(img.pixels.getPixel(i)|4278190080))}img.format=PConstants.RGB;break;case PConstants.THRESHOLD:if(param===null){param=0.5}if((param<0)||(param>1)){throw"Level must be between 0 and 1 for filter(THRESHOLD, level)"}var thresh=p.floor(param*255);for(i=0;i<imglen;i++){var max=p.max((img.pixels.getPixel(i)&PConstants.RED_MASK)>>16,p.max((img.pixels.getPixel(i)&PConstants.GREEN_MASK)>>8,(img.pixels.getPixel(i)&PConstants.BLUE_MASK)));img.pixels.setPixel(i,((img.pixels.getPixel(i)&PConstants.ALPHA_MASK)|((max<thresh)?0:16777215)))}break;case PConstants.ERODE:dilate(true,img);break;case PConstants.DILATE:dilate(false,img);break}img.updatePixels()}};p.shared={fracU:0,ifU:0,fracV:0,ifV:0,u1:0,u2:0,v1:0,v2:0,sX:0,sY:0,iw:0,iw1:0,ih1:0,ul:0,ll:0,ur:0,lr:0,cUL:0,cLL:0,cUR:0,cLR:0,srcXOffset:0,srcYOffset:0,r:0,g:0,b:0,a:0,srcBuffer:null,blurRadius:0,blurKernelSize:0,blurKernel:null};p.intersect=function intersect(sx1,sy1,sx2,sy2,dx1,dy1,dx2,dy2){var sw=sx2-sx1+1;var sh=sy2-sy1+1;var dw=dx2-dx1+1;var dh=dy2-dy1+1;if(dx1<sx1){dw+=dx1-sx1;if(dw>sw){dw=sw}}else{var w=sw+sx1-dx1;if(dw>w){dw=w}}if(dy1<sy1){dh+=dy1-sy1;if(dh>sh){dh=sh}}else{var h=sh+sy1-dy1;if(dh>h){dh=h}}return !(dw<=0||dh<=0)};p.filter_new_scanline=function filter_new_scanline(){p.shared.sX=p.shared.srcXOffset;p.shared.fracV=p.shared.srcYOffset&PConstants.PREC_MAXVAL;p.shared.ifV=PConstants.PREC_MAXVAL-p.shared.fracV;p.shared.v1=(p.shared.srcYOffset>>PConstants.PRECISIONB)*p.shared.iw;p.shared.v2=Math.min((p.shared.srcYOffset>>PConstants.PRECISIONB)+1,p.shared.ih1)*p.shared.iw};p.filter_bilinear=function filter_bilinear(){p.shared.fracU=p.shared.sX&PConstants.PREC_MAXVAL;p.shared.ifU=PConstants.PREC_MAXVAL-p.shared.fracU;p.shared.ul=(p.shared.ifU*p.shared.ifV)>>PConstants.PRECISIONB;p.shared.ll=(p.shared.ifU*p.shared.fracV)>>PConstants.PRECISIONB;p.shared.ur=(p.shared.fracU*p.shared.ifV)>>PConstants.PRECISIONB;p.shared.lr=(p.shared.fracU*p.shared.fracV)>>PConstants.PRECISIONB;p.shared.u1=(p.shared.sX>>PConstants.PRECISIONB);p.shared.u2=Math.min(p.shared.u1+1,p.shared.iw1);var cULoffset=(p.shared.v1+p.shared.u1)*4;var cURoffset=(p.shared.v1+p.shared.u2)*4;var cLLoffset=(p.shared.v2+p.shared.u1)*4;var cLRoffset=(p.shared.v2+p.shared.u2)*4;p.shared.cUL=p.color.toInt(p.shared.srcBuffer[cULoffset],p.shared.srcBuffer[cULoffset+1],p.shared.srcBuffer[cULoffset+2],p.shared.srcBuffer[cULoffset+3]);p.shared.cUR=p.color.toInt(p.shared.srcBuffer[cURoffset],p.shared.srcBuffer[cURoffset+1],p.shared.srcBuffer[cURoffset+2],p.shared.srcBuffer[cURoffset+3]);p.shared.cLL=p.color.toInt(p.shared.srcBuffer[cLLoffset],p.shared.srcBuffer[cLLoffset+1],p.shared.srcBuffer[cLLoffset+2],p.shared.srcBuffer[cLLoffset+3]);p.shared.cLR=p.color.toInt(p.shared.srcBuffer[cLRoffset],p.shared.srcBuffer[cLRoffset+1],p.shared.srcBuffer[cLRoffset+2],p.shared.srcBuffer[cLRoffset+3]);p.shared.r=((p.shared.ul*((p.shared.cUL&PConstants.RED_MASK)>>16)+p.shared.ll*((p.shared.cLL&PConstants.RED_MASK)>>16)+p.shared.ur*((p.shared.cUR&PConstants.RED_MASK)>>16)+p.shared.lr*((p.shared.cLR&PConstants.RED_MASK)>>16))<<PConstants.PREC_RED_SHIFT)&PConstants.RED_MASK;p.shared.g=((p.shared.ul*(p.shared.cUL&PConstants.GREEN_MASK)+p.shared.ll*(p.shared.cLL&PConstants.GREEN_MASK)+p.shared.ur*(p.shared.cUR&PConstants.GREEN_MASK)+p.shared.lr*(p.shared.cLR&PConstants.GREEN_MASK))>>>PConstants.PRECISIONB)&PConstants.GREEN_MASK;p.shared.b=(p.shared.ul*(p.shared.cUL&PConstants.BLUE_MASK)+p.shared.ll*(p.shared.cLL&PConstants.BLUE_MASK)+p.shared.ur*(p.shared.cUR&PConstants.BLUE_MASK)+p.shared.lr*(p.shared.cLR&PConstants.BLUE_MASK))>>>PConstants.PRECISIONB;p.shared.a=((p.shared.ul*((p.shared.cUL&PConstants.ALPHA_MASK)>>>24)+p.shared.ll*((p.shared.cLL&PConstants.ALPHA_MASK)>>>24)+p.shared.ur*((p.shared.cUR&PConstants.ALPHA_MASK)>>>24)+p.shared.lr*((p.shared.cLR&PConstants.ALPHA_MASK)>>>24))<<PConstants.PREC_ALPHA_SHIFT)&PConstants.ALPHA_MASK;return p.shared.a|p.shared.r|p.shared.g|p.shared.b};p.blit_resize=function blit_resize(img,srcX1,srcY1,srcX2,srcY2,destPixels,screenW,screenH,destX1,destY1,destX2,destY2,mode){var x,y;if(srcX1<0){srcX1=0}if(srcY1<0){srcY1=0}if(srcX2>=img.width){srcX2=img.width-1}if(srcY2>=img.height){srcY2=img.height-1}var srcW=srcX2-srcX1;var srcH=srcY2-srcY1;var destW=destX2-destX1;var destH=destY2-destY1;var smooth=true;if(!smooth){srcW++;srcH++}if(destW<=0||destH<=0||srcW<=0||srcH<=0||destX1>=screenW||destY1>=screenH||srcX1>=img.width||srcY1>=img.height){return}var dx=Math.floor(srcW/destW*PConstants.PRECISIONF);var dy=Math.floor(srcH/destH*PConstants.PRECISIONF);p.shared.srcXOffset=Math.floor(destX1<0?-destX1*dx:srcX1*PConstants.PRECISIONF);p.shared.srcYOffset=Math.floor(destY1<0?-destY1*dy:srcY1*PConstants.PRECISIONF);if(destX1<0){destW+=destX1;destX1=0}if(destY1<0){destH+=destY1;destY1=0}destW=Math.min(destW,screenW-destX1);destH=Math.min(destH,screenH-destY1);var destOffset=destY1*screenW+destX1;var destColor;p.shared.srcBuffer=img.imageData.data;if(smooth){p.shared.iw=img.width;p.shared.iw1=img.width-1;p.shared.ih1=img.height-1;switch(mode){case PConstants.BLEND:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.blend(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.ADD:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.add(destColor,p.filter_bilinear()));destColor=p.color.toArray(p.modes.add(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.SUBTRACT:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.subtract(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.LIGHTEST:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.lightest(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.DARKEST:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.darkest(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.REPLACE:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.filter_bilinear());destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.DIFFERENCE:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.difference(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.EXCLUSION:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.exclusion(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.MULTIPLY:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.multiply(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.SCREEN:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.screen(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.OVERLAY:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.overlay(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.HARD_LIGHT:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.hard_light(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.SOFT_LIGHT:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.soft_light(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.DODGE:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.dodge(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break;case PConstants.BURN:for(y=0;y<destH;y++){p.filter_new_scanline();for(x=0;x<destW;x++){destColor=p.color.toInt(destPixels[(destOffset+x)*4],destPixels[((destOffset+x)*4)+1],destPixels[((destOffset+x)*4)+2],destPixels[((destOffset+x)*4)+3]);destColor=p.color.toArray(p.modes.burn(destColor,p.filter_bilinear()));destPixels[(destOffset+x)*4]=destColor[0];destPixels[(destOffset+x)*4+1]=destColor[1];destPixels[(destOffset+x)*4+2]=destColor[2];destPixels[(destOffset+x)*4+3]=destColor[3];p.shared.sX+=dx}destOffset+=screenW;p.shared.srcYOffset+=dy}break}}};p.loadFont=function loadFont(name){if(name.indexOf(".svg")===-1){return{name:'"'+name+'", sans-serif',origName:name,width:function(str){if("measureText" in curContext){return curContext.measureText(typeof str==="number"?String.fromCharCode(str):str).width/curTextSize}else{if("mozMeasureText" in curContext){return curContext.mozMeasureText(typeof str==="number"?String.fromCharCode(str):str)/curTextSize}else{return 0}}}}}else{var font=p.loadGlyphs(name);return{name:name,glyph:true,units_per_em:font.units_per_em,horiz_adv_x:1/font.units_per_em*font.horiz_adv_x,ascent:font.ascent,descent:font.descent,width:function(str){var width=0;var len=str.length;for(var i=0;i<len;i++){try{width+=parseFloat(p.glyphLook(p.glyphTable[name],str[i]).horiz_adv_x)}catch(e){Processing.debug(e)}}return width/p.glyphTable[name].units_per_em}}}};p.createFont=function(name,size,smooth,charset){if(arguments.length===2){p.textSize(size);return p.loadFont(name)}else{if(arguments.length===3){p.textSize(size);return p.loadFont(name)}else{if(arguments.length===4){p.textSize(size);return p.loadFont(name)}else{throw ("incorrent number of parameters for createFont")}}}};p.textFont=function textFont(name,size){curTextFont=name;p.textSize(size)};p.textSize=function textSize(size){if(size){curTextSize=size}};p.textAlign=function textAlign(){if(arguments.length===1){horizontalTextAlignment=arguments[0]}else{if(arguments.length===2){horizontalTextAlignment=arguments[0];verticalTextAlignment=arguments[1]}}};p.textWidth=function textWidth(str){if(p.use3DContext){if(textcanvas===undef){textcanvas=document.createElement("canvas")}var oldContext=curContext;curContext=textcanvas.getContext("2d");curContext.font=curContext.mozTextStyle=curTextSize+"px "+curTextFont.name;if("fillText" in curContext){textcanvas.width=curContext.measureText(str).width}else{if("mozDrawText" in curContext){textcanvas.width=curContext.mozMeasureText(str)}}curContext=oldContext;return textcanvas.width}else{curContext.font=curTextSize+"px "+curTextFont.name;if("fillText" in curContext){return curContext.measureText(str).width}else{if("mozDrawText" in curContext){return curContext.mozMeasureText(str)}}}};p.textAscent=(function(){var oldTextSize=undef,oldTextFont=undef,ascent=undef,graphics=undef;return function textAscent(){if(oldTextFont!==curTextFont||oldTextSize!==curTextSize){oldTextFont=curTextFont;oldTextSize=curTextSize;var found=false,character="k",colour=p.color(0),top=0,bottom=curTextSize,yLoc=curTextSize/2;if(graphics!==undef){graphics.size(curTextSize,curTextSize)}else{graphics=p.createGraphics(curTextSize,curTextSize)}graphics.background(0);graphics.fill(255);graphics.textFont(curTextFont,curTextSize);graphics.text(character,0,curTextSize);while(yLoc!==bottom){for(var xLoc=0;xLoc<curTextSize;xLoc++){if(graphics.get(xLoc,yLoc)!==colour){found=true;xLoc=curTextSize}}if(found){bottom=yLoc;found=false}else{top=yLoc}yLoc=Math.ceil((bottom+top)/2)}ascent=((curTextSize-1)-yLoc)+1;return ascent}else{return ascent}}}());p.textDescent=(function(){var oldTextSize=undef,oldTextFont=undef,descent=undef,graphics=undef;return function textDescent(){if(oldTextFont!==curTextFont||oldTextSize!==curTextSize){oldTextFont=curTextFont;oldTextSize=curTextSize;var found=false,character="p",colour=p.color(0),top=0,bottom=curTextSize,yLoc=curTextSize/2;if(graphics!==undef){graphics.size(curTextSize,curTextSize)}else{graphics=p.createGraphics(curTextSize,curTextSize)}graphics.background(0);graphics.fill(255);graphics.textFont(curTextFont,curTextSize);graphics.text(character,0,0);while(yLoc!==bottom){for(var xLoc=0;xLoc<curTextSize;xLoc++){if(graphics.get(xLoc,yLoc)!==colour){found=true;xLoc=curTextSize}}if(found){top=yLoc;found=false}else{bottom=yLoc}yLoc=Math.ceil((bottom+top)/2)}descent=yLoc+1;return descent}else{return descent}}}());p.glyphLook=function glyphLook(font,chr){try{switch(chr){case"1":return font.one;case"2":return font.two;case"3":return font.three;case"4":return font.four;case"5":return font.five;case"6":return font.six;case"7":return font.seven;case"8":return font.eight;case"9":return font.nine;case"0":return font.zero;case" ":return font.space;case"$":return font.dollar;case"!":return font.exclam;case'"':return font.quotedbl;case"#":return font.numbersign;case"%":return font.percent;case"&":return font.ampersand;case"'":return font.quotesingle;case"(":return font.parenleft;case")":return font.parenright;case"*":return font.asterisk;case"+":return font.plus;case",":return font.comma;case"-":return font.hyphen;case".":return font.period;case"/":return font.slash;case"_":return font.underscore;case":":return font.colon;case";":return font.semicolon;case"<":return font.less;case"=":return font.equal;case">":return font.greater;case"?":return font.question;case"@":return font.at;case"[":return font.bracketleft;case"\\":return font.backslash;case"]":return font.bracketright;case"^":return font.asciicircum;case"`":return font.grave;case"{":return font.braceleft;case"|":return font.bar;case"}":return font.braceright;case"~":return font.asciitilde;default:return font[chr]}}catch(e){Processing.debug(e)}};function toP5String(obj){if(obj instanceof String){return obj}else{if(typeof obj==="number"){if(obj===(0|obj)){return obj.toString()}else{return p.nf(obj,0,3)}}else{if(obj===null||obj===undef){return""}else{return obj.toString()}}}}function text$line(str,x,y,z,align){var textWidth=0,xOffset=0;if(!curTextFont.glyph){if(str&&("fillText" in curContext||"mozDrawText" in curContext)){curContext.font=curContext.mozTextStyle=curTextSize+"px "+curTextFont.name;if(isFillDirty){curContext.fillStyle=p.color.toString(currentFillColor);isFillDirty=false}if(align===PConstants.RIGHT||align===PConstants.CENTER){if("fillText" in curContext){textWidth=curContext.measureText(str).width}else{if("mozDrawText" in curContext){textWidth=curContext.mozMeasureText(str)}}if(align===PConstants.RIGHT){xOffset=-textWidth}else{xOffset=-textWidth/2}}if("fillText" in curContext){curContext.fillText(str,x+xOffset,y)}else{if("mozDrawText" in curContext){saveContext();curContext.translate(x+xOffset,y);curContext.mozDrawText(str);restoreContext()}}}}else{var font=p.glyphTable[curTextFont.name];saveContext();curContext.translate(x,y+curTextSize);if(align===PConstants.RIGHT||align===PConstants.CENTER){textWidth=font.width(str);if(align===PConstants.RIGHT){xOffset=-textWidth}else{xOffset=-textWidth/2}}var upem=font.units_per_em,newScale=1/upem*curTextSize;curContext.scale(newScale,newScale);for(var i=0,len=str.length;i<len;i++){try{p.glyphLook(font,str[i]).draw()}catch(e){Processing.debug(e)}}restoreContext()}}function text$line$3d(str,x,y,z,align){if(textcanvas===undef){textcanvas=document.createElement("canvas")}var oldContext=curContext;curContext=textcanvas.getContext("2d");curContext.font=curContext.mozTextStyle=curTextSize+"px "+curTextFont.name;var textWidth=0;if("fillText" in curContext){textWidth=curContext.measureText(str).width}else{if("mozDrawText" in curContext){textWidth=curContext.mozMeasureText(str)}}textcanvas.width=textWidth;textcanvas.height=curTextSize;curContext=textcanvas.getContext("2d");curContext.font=curContext.mozTextStyle=curTextSize+"px "+curTextFont.name;curContext.textBaseline="top";text$line(str,0,0,0,PConstants.LEFT);var aspect=textcanvas.width/textcanvas.height;curContext=oldContext;executeTexImage2D(textcanvas);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_MAG_FILTER,curContext.LINEAR);curContext.texParameteri(curContext.TEXTURE_2D,curContext.TEXTURE_MIN_FILTER,curContext.LINEAR_MIPMAP_LINEAR);curContext.generateMipmap(curContext.TEXTURE_2D);var xOffset=0;if(align===PConstants.RIGHT){xOffset=-textWidth}else{if(align===PConstants.CENTER){xOffset=-textWidth/2}}var model=new PMatrix3D();var scalefactor=curTextSize*0.5;model.translate(x+xOffset-scalefactor/2,y-scalefactor,z);model.scale(-aspect*scalefactor,-scalefactor,scalefactor);model.translate(-1,-1,-1);model.transpose();var view=new PMatrix3D();view.scale(1,-1,1);view.apply(modelView.array());view.transpose();var proj=new PMatrix3D();proj.set(projection);proj.transpose();curContext.useProgram(programObject2D);vertexAttribPointer(programObject2D,"Vertex",3,textBuffer);vertexAttribPointer(programObject2D,"aTextureCoord",2,textureBuffer);uniformi(programObject2D,"uSampler",[0]);uniformi(programObject2D,"picktype",1);uniformMatrix(programObject2D,"model",false,model.array());uniformMatrix(programObject2D,"view",false,view.array());uniformMatrix(programObject2D,"projection",false,proj.array());uniformf(programObject2D,"color",fillStyle);curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER,indexBuffer);curContext.drawElements(curContext.TRIANGLES,6,curContext.UNSIGNED_SHORT,0)}function text$4(str,x,y,z){var lineFunction=p.use3DContext?text$line$3d:text$line;var lines,linesCount;if(str.indexOf("\n")<0){lines=[str];linesCount=1}else{lines=str.split(/\r?\n/g);linesCount=lines.length}var yOffset;if(verticalTextAlignment===PConstants.TOP){yOffset=(1-baselineOffset)*curTextSize}else{if(verticalTextAlignment===PConstants.CENTER){yOffset=(1-baselineOffset-linesCount/2)*curTextSize}else{if(verticalTextAlignment===PConstants.BOTTOM){yOffset=(1-baselineOffset-linesCount)*curTextSize}else{yOffset=(1-linesCount)*curTextSize}}}for(var i=0;i<linesCount;++i){var line=lines[i];lineFunction(line,x,y+yOffset,z,horizontalTextAlignment);yOffset+=curTextSize}}function text$6(str,x,y,width,height,z){if(str.length===0){return}if(curTextSize>height){return}var spaceMark=-1;var start=0;var lineWidth=0;var textboxWidth=width;var yOffset=0;curContext.font=curTextSize+"px "+curTextFont.name;var drawCommands=[];var hadSpaceBefore=false;for(var j=0,len=str.length;j<len;j++){var currentChar=str[j];var letterWidth;if("fillText" in curContext){letterWidth=curContext.measureText(currentChar).width}else{if("mozDrawText" in curContext){letterWidth=curContext.mozMeasureText(currentChar)}}if(currentChar!=="\n"&&(currentChar===" "||(hadSpaceBefore&&str[j+1]===" ")||lineWidth+2*letterWidth<textboxWidth)){if(currentChar===" "){spaceMark=j}lineWidth+=letterWidth}else{if(start===spaceMark+1){spaceMark=j}if(str[j]==="\n"){drawCommands.push({text:str.substring(start,j),width:lineWidth,offset:yOffset});start=j+1}else{drawCommands.push({text:str.substring(start,spaceMark+1),width:lineWidth,offset:yOffset});start=spaceMark+1}yOffset+=curTextSize;lineWidth=0;j=start-1}hadSpaceBefore=currentChar===" "}if(start<len){drawCommands.push({text:str.substring(start),width:lineWidth,offset:yOffset});yOffset+=curTextSize}var lineFunction=p.use3DContext?text$line$3d:text$line;var xOffset=0;if(horizontalTextAlignment===PConstants.CENTER){xOffset=width/2}else{if(horizontalTextAlignment===PConstants.RIGHT){xOffset=width}}var boxYOffset1=(1-baselineOffset)*curTextSize,boxYOffset2=0;if(verticalTextAlignment===PConstants.BOTTOM){boxYOffset2=height-yOffset}else{if(verticalTextAlignment===PConstants.CENTER){boxYOffset2=(height-yOffset)/2}}for(var il=0,ll=drawCommands.length;il<ll;++il){var command=drawCommands[il];if(command.offset+boxYOffset2<0){continue}if(command.offset+boxYOffset2+curTextSize>height){break}lineFunction(command.text,x+xOffset,y+command.offset+boxYOffset1+boxYOffset2,z,horizontalTextAlignment)}}p.text=function text(){if(tMode===PConstants.SCREEN){p.pushMatrix();p.resetMatrix();var asc=p.textAscent();var des=p.textDescent();var tWidth=p.textWidth(arguments[0]);var tHeight=asc+des;var font=p.loadFont(curTextFont.origName);var hud=p.createGraphics(tWidth,tHeight);hud.beginDraw();hud.fill(currentFillColor);hud.opaque=false;hud.background(0,0,0,0);hud.textFont(font);hud.textSize(curTextSize);hud.text(arguments[0],0,asc);hud.endDraw();if(arguments.length===5||arguments.length===6){p.image(hud,arguments[1],arguments[2]-asc,arguments[3],arguments[4])}else{p.image(hud,arguments[1],arguments[2]-asc)}p.popMatrix()}else{if(tMode===PConstants.SHAPE){}else{if(arguments.length===3){text$4(toP5String(arguments[0]),arguments[1],arguments[2],0)}else{if(arguments.length===4){text$4(toP5String(arguments[0]),arguments[1],arguments[2],arguments[3])}else{if(arguments.length===5){text$6(toP5String(arguments[0]),arguments[1],arguments[2],arguments[3],arguments[4],0)}else{if(arguments.length===6){text$6(toP5String(arguments[0]),arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])}}}}}}};p.textMode=function textMode(mode){tMode=mode};p.loadGlyphs=function loadGlyph(url){var x,y,cx,cy,nx,ny,d,a,lastCom,lenC,horiz_adv_x,getXY="[0-9\\-]+",path;var regex=function regex(needle,hay){var i=0,results=[],latest,regexp=new RegExp(needle,"g");latest=results[i]=regexp.exec(hay);while(latest){i++;latest=results[i]=regexp.exec(hay)}return results};var buildPath=function buildPath(d){var c=regex("[A-Za-z][0-9\\- ]+|Z",d);path="var path={draw:function(){saveContext();curContext.beginPath();";x=0;y=0;cx=0;cy=0;nx=0;ny=0;d=0;a=0;lastCom="";lenC=c.length-1;for(var j=0;j<lenC;j++){var com=c[j][0],xy=regex(getXY,com);switch(com[0]){case"M":x=parseFloat(xy[0][0]);y=parseFloat(xy[1][0]);path+="curContext.moveTo("+x+","+(-y)+");";break;case"L":x=parseFloat(xy[0][0]);y=parseFloat(xy[1][0]);path+="curContext.lineTo("+x+","+(-y)+");";break;case"H":x=parseFloat(xy[0][0]);path+="curContext.lineTo("+x+","+(-y)+");";break;case"V":y=parseFloat(xy[0][0]);path+="curContext.lineTo("+x+","+(-y)+");";break;case"T":nx=parseFloat(xy[0][0]);ny=parseFloat(xy[1][0]);if(lastCom==="Q"||lastCom==="T"){d=Math.sqrt(Math.pow(x-cx,2)+Math.pow(cy-y,2));a=Math.PI+Math.atan2(cx-x,cy-y);cx=x+(Math.sin(a)*(d));cy=y+(Math.cos(a)*(d))}else{cx=x;cy=y}path+="curContext.quadraticCurveTo("+cx+","+(-cy)+","+nx+","+(-ny)+");";x=nx;y=ny;break;case"Q":cx=parseFloat(xy[0][0]);cy=parseFloat(xy[1][0]);nx=parseFloat(xy[2][0]);ny=parseFloat(xy[3][0]);path+="curContext.quadraticCurveTo("+cx+","+(-cy)+","+nx+","+(-ny)+");";x=nx;y=ny;break;case"Z":path+="curContext.closePath();";break}lastCom=com[0]}path+="executeContextFill();executeContextStroke();";path+="restoreContext();";path+="curContext.translate("+horiz_adv_x+",0);";path+="}}";return path};var parseSVGFont=function parseSVGFontse(svg){var font=svg.getElementsByTagName("font");p.glyphTable[url].horiz_adv_x=font[0].getAttribute("horiz-adv-x");var font_face=svg.getElementsByTagName("font-face")[0];p.glyphTable[url].units_per_em=parseFloat(font_face.getAttribute("units-per-em"));p.glyphTable[url].ascent=parseFloat(font_face.getAttribute("ascent"));p.glyphTable[url].descent=parseFloat(font_face.getAttribute("descent"));var glyph=svg.getElementsByTagName("glyph"),len=glyph.length;for(var i=0;i<len;i++){var unicode=glyph[i].getAttribute("unicode");var name=glyph[i].getAttribute("glyph-name");horiz_adv_x=glyph[i].getAttribute("horiz-adv-x");if(horiz_adv_x===null){horiz_adv_x=p.glyphTable[url].horiz_adv_x}d=glyph[i].getAttribute("d");if(d!==undef){path=buildPath(d);eval(path);p.glyphTable[url][name]={name:name,unicode:unicode,horiz_adv_x:horiz_adv_x,draw:path.draw}}}};var loadXML=function loadXML(){var xmlDoc;try{xmlDoc=document.implementation.createDocument("","",null)}catch(e_fx_op){Processing.debug(e_fx_op.message);return}try{xmlDoc.async=false;xmlDoc.load(url);parseSVGFont(xmlDoc.getElementsByTagName("svg")[0])}catch(e_sf_ch){Processing.debug(e_sf_ch);try{var xmlhttp=new window.XMLHttpRequest();xmlhttp.open("GET",url,false);xmlhttp.send(null);parseSVGFont(xmlhttp.responseXML.documentElement)}catch(e){Processing.debug(e_sf_ch)}}};p.glyphTable[url]={};loadXML(url);return p.glyphTable[url]};p.extendClass=function extendClass(subClass,baseClass){function extendGetterSetter(propertyName){p.defineProperty(subClass,propertyName,{get:function(){return baseClass[propertyName]},set:function(v){baseClass[propertyName]=v}})}for(var propertyName in baseClass){if(subClass[propertyName]===undef){if(typeof baseClass[propertyName]==="function"){subClass[propertyName]=baseClass[propertyName]}else{extendGetterSetter(propertyName)}}}};p.addMethod=function addMethod(object,name,fn,superAccessor){if(object[name]){var args=fn.length,oldfn=object[name];object[name]=function(){if(arguments.length===args){return fn.apply(this,arguments)}else{return oldfn.apply(this,arguments)}}}else{object[name]=fn}};function attach(elem,type,fn){if(elem.addEventListener){elem.addEventListener(type,fn,false)}else{elem.attachEvent("on"+type,fn)}eventHandlers.push([elem,type,fn])}attach(curElement,"mousemove",function(e){var element=curElement,offsetX=0,offsetY=0;p.pmouseX=p.mouseX;p.pmouseY=p.mouseY;if(element.offsetParent){do{offsetX+=element.offsetLeft;offsetY+=element.offsetTop}while((element=element.offsetParent))}offsetX+=stylePaddingLeft;offsetY+=stylePaddingTop;offsetX+=styleBorderLeft;offsetY+=styleBorderTop;p.mouseX=e.pageX-offsetX;p.mouseY=e.pageY-offsetY;if(typeof p.mouseMoved==="function"&&!p.__mousePressed){p.mouseMoved()}if(typeof p.mouseDragged==="function"&&p.__mousePressed){p.mouseDragged();p.mouseDragging=true}});attach(curElement,"mouseout",function(e){});attach(curElement,"mousedown",function(e){p.__mousePressed=true;p.mouseDragging=false;switch(e.which){case 1:p.mouseButton=PConstants.LEFT;break;case 2:p.mouseButton=PConstants.CENTER;break;case 3:p.mouseButton=PConstants.RIGHT;break}if(typeof p.mousePressed==="function"){p.mousePressed()}});attach(curElement,"mouseup",function(e){p.__mousePressed=false;if(typeof p.mouseClicked==="function"&&!p.mouseDragging){p.mouseClicked()}if(typeof p.mouseReleased==="function"){p.mouseReleased()}});var mouseWheelHandler=function(e){var delta=0;if(e.wheelDelta){delta=e.wheelDelta/120;if(window.opera){delta=-delta}}else{if(e.detail){delta=-e.detail/3}}p.mouseScroll=delta;if(delta&&typeof p.mouseScrolled==="function"){p.mouseScrolled()}};attach(document,"DOMMouseScroll",mouseWheelHandler);attach(document,"mousewheel",mouseWheelHandler);function keyCodeMap(code,shift){if(code>=65&&code<=90){if(shift){return code}else{return code+32}}else{if(code>=48&&code<=57){if(shift){switch(code){case 49:return 33;case 50:return 64;case 51:return 35;case 52:return 36;case 53:return 37;case 54:return 94;case 55:return 38;case 56:return 42;case 57:return 40;case 48:return 41}}}else{if(codedKeys.indexOf(code)>=0){p.keyCode=code;return PConstants.CODED}else{if(shift){switch(code){case 107:return 43;case 219:return 123;case 221:return 125;case 222:return 34}}else{switch(code){case 188:return 44;case 109:return 45;case 190:return 46;case 191:return 47;case 192:return 96;case 219:return 91;case 220:return 92;case 221:return 93;case 222:return 39}}}}}return code}attach(document,"keydown",function(e){p.__keyPressed=true;p.keyCode=null;p.key=keyCodeMap(e.keyCode,e.shiftKey);if(typeof p.keyPressed==="function"){p.keyPressed()}});attach(document,"keyup",function(e){p.keyCode=null;p.key=keyCodeMap(e.keyCode,e.shiftKey);p.__keyPressed=false;if(typeof p.keyReleased==="function"){p.keyReleased()}});attach(document,"keypress",function(e){if(p.keyTyped){p.keyTyped()}});Processing.debug=function(e){};if(typeof curElement==="string"){curElement=document.getElementById(curElement)}if(aCode){if(aCode instanceof Processing.Sketch){curSketch=aCode}else{if(typeof aCode==="function"){curSketch=new Processing.Sketch(aCode)}else{curSketch=Processing.compile(aCode)}}p.externals.sketch=curSketch;p.use3DContext=curSketch.use3DContext;if("mozOpaque" in curElement){curElement.mozOpaque=!curSketch.options.isTransparent}if(curSketch.options.pauseOnBlur){p.externals.onfocus=function(){if(doLoop){p.loop()}};p.externals.onblur=function(){if(doLoop&&loopStarted){p.noLoop();doLoop=true}}}if(!curSketch.use3DContext){curContext=curElement.getContext("2d");p.externals.context=curContext;modelView=new PMatrix2D();curContext.translate(0.5,0.5);curContext.lineCap="round";p.stroke(0);p.fill(255);p.noSmooth();p.disableContextMenu()}for(var i in Processing.lib){if(Processing.lib.hasOwnProperty(i)){Processing.lib[i].call(this)}}var executeSketch=function(processing){if(!curSketch.imageCache.pending&&curSketch.fonts.pending()){curSketch.attach(processing,PConstants);if(processing.setup){processing.setup()}resetContext();if(processing.draw){if(!doLoop){processing.redraw()}else{processing.loop()}}}else{window.setTimeout(function(){executeSketch(processing)},10)}};executeSketch(p)}else{curSketch=new Processing.Sketch();curSketch.options.isTransparent=true}};function getGlobalMembers(){var names=["abs","acos","alpha","ambient","ambientLight","append","applyMatrix","arc","arrayCopy","ArrayList","asin","atan","atan2","background","beginCamera","beginDraw","beginShape","bezier","bezierDetail","bezierPoint","bezierTangent","bezierVertex","binary","blend","blendColor","blit_resize","blue","boolean","box","breakShape","brightness","byte","camera","ceil","char","Character","clear","color","colorMode","concat","console","constrain","copy","cos","createFont","createGraphics","createImage","cursor","curve","curveDetail","curvePoint","curveTangent","curveTightness","curveVertex","day","defaultColor","degrees","directionalLight","disableContextMenu","dist","draw","ellipse","ellipseMode","emissive","enableContextMenu","endCamera","endDraw","endShape","exit","exp","expand","externals","fill","filter","filter_bilinear","filter_new_scanline","float","floor","focused","frameCount","frameRate","frustum","get","glyphLook","glyphTable","green","HashMap","height","hex","hint","hour","hue","image","imageMode","Import","int","intersect","join","key","keyCode","keyPressed","keyReleased","keyTyped","lerp","lerpColor","lightFalloff","lights","lightSpecular","line","link","loadBytes","loadFont","loadGlyphs","loadImage","loadPixels","loadShape","loadStrings","log","loop","mag","map","match","matchAll","max","millis","min","minute","mix","modelX","modelY","modelZ","modes","month","mouseButton","mouseClicked","mouseDragged","mouseMoved","mousePressed","mouseReleased","mouseScroll","mouseScrolled","mouseX","mouseY","name","nf","nfc","nfp","nfs","noCursor","noFill","noise","noiseDetail","noiseSeed","noLights","noLoop","norm","normal","noSmooth","noStroke","noTint","ortho","peg","perspective","PImage","pixels","PMatrix2D","PMatrix3D","PMatrixStack","pmouseX","pmouseY","point","pointLight","popMatrix","popStyle","pow","print","printCamera","println","printMatrix","printProjection","PShape","pushMatrix","pushStyle","PVector","quad","radians","random","Random","randomSeed","rect","rectMode","red","redraw","requestImage","resetMatrix","reverse","rotate","rotateX","rotateY","rotateZ","round","saturation","save","saveStrings","scale","screenX","screenY","screenZ","second","set","setup","shape","shapeMode","shared","shininess","shorten","sin","size","smooth","sort","specular","sphere","sphereDetail","splice","split","splitTokens","spotLight","sq","sqrt","status","str","stroke","strokeCap","strokeJoin","strokeWeight","subset","tan","text","textAlign","textAscent","textDescent","textFont","textMode","textSize","texture","textureMode","textWidth","tint","translate","triangle","trim","unbinary","unhex","updatePixels","use3DContext","vertex","width","XMLElement","year","__frameRate","__keyPressed","__mousePressed"];var members={};var i,l;for(i=0,l=names.length;i<l;++i){members[names[i]]=null}for(var lib in Processing.lib){if(Processing.lib.hasOwnProperty(lib)){if(Processing.lib[lib].exports){var exportedNames=Processing.lib[lib].exports;for(i=0,l=exportedNames.length;i<l;++i){members[exportedNames[i]]=null}}}}return members}function parseProcessing(code){var globalMembers=getGlobalMembers();function splitToAtoms(code){var atoms=[];var items=code.split(/([\{\[\(\)\]\}])/);var result=items[0];var stack=[];for(var i=1;i<items.length;i+=2){var item=items[i];if(item==="["||item==="{"||item==="("){stack.push(result);result=item}else{if(item==="]"||item==="}"||item===")"){var kind=item==="}"?"A":item===")"?"B":"C";var index=atoms.length;atoms.push(result+item);result=stack.pop()+'"'+kind+(index+1)+'"'}}result+=items[i+1]}atoms.unshift(result);return atoms}function injectStrings(code,strings){return code.replace(/'(\d+)'/g,function(all,index){var val=strings[index];if(val.charAt(0)==="/"){return val}else{return(/^'((?:[^'\\\n])|(?:\\.[0-9A-Fa-f]*))'$/).test(val)?"(new processing.Character("+val+"))":val}})}function trimSpaces(string){var m1=/^\s*/.exec(string),result;if(m1[0].length===string.length){result={left:m1[0],middle:"",right:""}}else{var m2=/\s*$/.exec(string);result={left:m1[0],middle:string.substring(m1[0].length,m2.index),right:m2[0]}}result.untrim=function(t){return this.left+t+this.right};return result}function trim(string){return string.replace(/^\s+/,"").replace(/\s+$/,"")}function appendToLookupTable(table,array){for(var i=0,l=array.length;i<l;++i){table[array[i]]=null}return table}function isLookupTableEmpty(table){for(var i in table){if(table.hasOwnProperty(i)){return false}}return true}function getAtomIndex(templ){return templ.substring(2,templ.length-1)}var codeWoExtraCr=code.replace(/\r\n?|\n\r/g,"\n");var strings=[];var codeWoStrings=codeWoExtraCr.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g,function(all,quoted,aposed,regexCtx,prefix,regex,singleComment,comment){var index;if(quoted||aposed){index=strings.length;strings.push(all);return"'"+index+"'"}else{if(regexCtx){index=strings.length;strings.push(regex);return prefix+"'"+index+"'"}else{return comment!==""?" ":"\n"}}});var atoms=splitToAtoms(codeWoStrings);var replaceContext;var declaredClasses={},currentClassId,classIdSeed=0;function addAtom(text,type){var lastIndex=atoms.length;atoms.push(text);return'"'+type+lastIndex+'"'}function generateClassId(){return"class"+(++classIdSeed)}function appendClass(class_,classId,scopeId){class_.classId=classId;class_.scopeId=scopeId;declaredClasses[classId]=class_}var transformClassBody,transformStatementsBlock,transformStatements,transformMain,transformExpression;var classesRegex=/\b((?:(?:public|private|final|protected|static|abstract)\s+)*)(class|interface)\s+([A-Za-z_$][\w$]*\b)(\s+extends\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)?(\s+implements\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?\s*("A\d+")/g;var methodsRegex=/\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:else|new|return|throw|function|public|private|protected)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+"|;)/g;var fieldTest=/^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:else|new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*(?:"C\d+"\s*)*([=,]|$)/;var cstrsRegex=/\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+")/g;var attrAndTypeRegex=/^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*/;var functionsRegex=/\bfunction(?:\s+([A-Za-z_$][\w$]*))?\s*("B\d+")\s*("A\d+")/g;function extractClassesAndMethods(code){var s=code;s=s.replace(classesRegex,function(all){return addAtom(all,"E")});s=s.replace(methodsRegex,function(all){return addAtom(all,"D")});s=s.replace(functionsRegex,function(all){return addAtom(all,"H")});return s}function extractConstructors(code,className){var result=code.replace(cstrsRegex,function(all,attr,name,params,throws_,body){if(name!==className){return all}else{return addAtom(all,"G")}});return result}function AstParam(name){this.name=name}AstParam.prototype.toString=function(){return this.name};function AstParams(params){this.params=params}AstParams.prototype.getNames=function(){var names=[];for(var i=0,l=this.params.length;i<l;++i){names.push(this.params[i].name)}return names};AstParams.prototype.toString=function(){if(this.params.length===0){return"()"}var result="(";for(var i=0,l=this.params.length;i<l;++i){result+=this.params[i]+", "}return result.substring(0,result.length-2)+")"};function transformParams(params){var paramsWoPars=trim(params.substring(1,params.length-1));var result=[];if(paramsWoPars!==""){var paramList=paramsWoPars.split(",");for(var i=0;i<paramList.length;++i){var param=/\b([A-Za-z_$][\w$]*\b)\s*("[ABC][\d]*")?$/.exec(paramList[i]);result.push(new AstParam(param[1]))}}return new AstParams(result)}function preExpressionTransform(expr){var s=expr;s=s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"C\d+")+\s*("A\d+")/g,function(all,type,init){return init});s=s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"B\d+")\s*("A\d+")/g,function(all,type,init){return addAtom(all,"F")});s=s.replace(functionsRegex,function(all){return addAtom(all,"H")});s=s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*("C\d+"(?:\s*"C\d+")*)/g,function(all,type,index){var args=index.replace(/"C(\d+)"/g,function(all,j){return atoms[j]}).replace(/\[\s*\]/g,"[0]").replace(/\s*\]\s*\[\s*/g,", ");var arrayInitializer="("+args.substring(1,args.length-1)+")";return"new ArrayList"+addAtom(arrayInitializer,"B")});s=s.replace(/(\.\s*length)\s*"B\d+"/g,"$1");s=s.replace(/#([0-9A-Fa-f]{6})\b/g,function(all,digits){return"0xFF"+digits});s=s.replace(/"B(\d+)"(\s*(?:[\w$']|"B))/g,function(all,index,next){var atom=atoms[index];if(!/^\(\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\s*(?:"C\d+"\s*)*\)$/.test(atom)){return all}else{if(/^\(\s*int\s*\)$/.test(atom)){return"0|"+next}else{var indexParts=atom.split(/"C(\d+)"/g);if(indexParts.length>1){if(!/^\[\s*\]$/.test(atoms[indexParts[1]])){return all}}return""+next}}});s=s.replace(/\bsuper(\s*"B\d+")/g,"$$superCstr$1").replace(/\bsuper(\s*\.)/g,"$$super$1");s=s.replace(/\b(\.?\d+)[fF]/g,"$1");s=s.replace(/([^\s])%([^=\s])/g,"$1 % $2");s=s.replace(/\b(frameRate|keyPressed|mousePressed)\b(?!\s*"B)/g,"__$1");s=s.replace(/\bpixels\s*(("C(\d+)")|\.length)?(\s*=(?!=)([^,\]\)\}\?\:]+))?/g,function(all,indexOrLength,index,atomIndex,equalsPart,rightSide){if(index){var atom=atoms[atomIndex];if(equalsPart){return"pixels.setPixel"+addAtom("("+atom.substring(1,atom.length-1)+","+rightSide+")","B")}else{return"pixels.getPixel"+addAtom("("+atom.substring(1,atom.length-1)+")","B")}}else{if(indexOrLength){return"pixels.getLength"+addAtom("()","B")}else{if(equalsPart){return"pixels.set"+addAtom("("+rightSide+")","B")}else{return"pixels.toArray"+addAtom("()","B")}}}});s=s.replace(/\bthis(\s*"B\d+")/g,"$$constr$1");return s}function AstInlineClass(baseInterfaceName,body){this.baseInterfaceName=baseInterfaceName;this.body=body;body.owner=this}AstInlineClass.prototype.toString=function(){return"new (function() {\n"+this.body+"})"};function transformInlineClass(class_){var m=new RegExp(/\bnew\s*(Runnable)\s*"B\d+"\s*"A(\d+)"/).exec(class_);if(m===null){return"null"}else{var oldClassId=currentClassId,newClassId=generateClassId();currentClassId=newClassId;var inlineClass=new AstInlineClass("Runnable",transformClassBody(atoms[m[2]],m[1]));appendClass(inlineClass,newClassId,oldClassId);currentClassId=oldClassId;return inlineClass}}function AstFunction(name,params,body){this.name=name;this.params=params;this.body=body}AstFunction.prototype.toString=function(){var oldContext=replaceContext;var names=appendToLookupTable({"this":null},this.params.getNames());replaceContext=function(name){return names.hasOwnProperty(name)?name:oldContext(name)};var result="function";if(this.name){result+=" "+this.name}result+=this.params+" "+this.body;replaceContext=oldContext;return result};function transformFunction(class_){var m=new RegExp(/\b([A-Za-z_$][\w$]*)\s*"B(\d+)"\s*"A(\d+)"/).exec(class_);return new AstFunction(m[1]!=="function"?m[1]:null,transformParams(atoms[m[2]]),transformStatementsBlock(atoms[m[3]]))}function AstInlineObject(members){this.members=members}AstInlineObject.prototype.toString=function(){var oldContext=replaceContext;replaceContext=function(name){return name==="this"?name:oldContext(name)};var result="";for(var i=0,l=this.members.length;i<l;++i){if(this.members[i].label){result+=this.members[i].label+": "}result+=this.members[i].value.toString()+", "}replaceContext=oldContext;return result.substring(0,result.length-2)};function transformInlineObject(obj){var members=obj.split(",");for(var i=0;i<members.length;++i){var label=members[i].indexOf(":");if(label<0){members[i]={value:transformExpression(members[i])}}else{members[i]={label:trim(members[i].substring(0,label)),value:transformExpression(trim(members[i].substring(label+1)))}}}return new AstInlineObject(members)}function expandExpression(expr){if(expr.charAt(0)==="("||expr.charAt(0)==="["){return expr.charAt(0)+expandExpression(expr.substring(1,expr.length-1))+expr.charAt(expr.length-1)}else{if(expr.charAt(0)==="{"){if(/^\{\s*(?:[A-Za-z_$][\w$]*|'\d+')\s*:/.test(expr)){return"{"+addAtom(expr.substring(1,expr.length-1),"I")+"}"}else{return"["+expandExpression(expr.substring(1,expr.length-1))+"]"}}else{var trimmed=trimSpaces(expr);var result=preExpressionTransform(trimmed.middle);result=result.replace(/"[ABC](\d+)"/g,function(all,index){return expandExpression(atoms[index])});return trimmed.untrim(result)}}}function replaceContextInVars(expr){return expr.replace(/(\.\s*)?(\b[A-Za-z_$][\w$]*\b)/g,function(all,memberAccessSign,identifier){if(memberAccessSign){return all}else{return replaceContext(identifier)}})}function AstExpression(expr,transforms){this.expr=expr;this.transforms=transforms}AstExpression.prototype.toString=function(){var transforms=this.transforms;var expr=replaceContextInVars(this.expr);return expr.replace(/"!(\d+)"/g,function(all,index){return transforms[index].toString()})};transformExpression=function(expr){var transforms=[];var s=expandExpression(expr);s=s.replace(/"H(\d+)"/g,function(all,index){transforms.push(transformFunction(atoms[index]));return'"!'+(transforms.length-1)+'"'});s=s.replace(/"F(\d+)"/g,function(all,index){transforms.push(transformInlineClass(atoms[index]));return'"!'+(transforms.length-1)+'"'});s=s.replace(/"I(\d+)"/g,function(all,index){transforms.push(transformInlineObject(atoms[index]));return'"!'+(transforms.length-1)+'"'});return new AstExpression(s,transforms)};function AstVarDefinition(name,value,isDefault){this.name=name;this.value=value;this.isDefault=isDefault}AstVarDefinition.prototype.toString=function(){return this.name+" = "+this.value};function transformVarDefinition(def,defaultTypeValue){var eqIndex=def.indexOf("=");var name,value,isDefault;if(eqIndex<0){name=def;value=defaultTypeValue;isDefault=true}else{name=def.substring(0,eqIndex);value=transformExpression(def.substring(eqIndex+1));isDefault=false}return new AstVarDefinition(trim(name.replace(/(\s*"C\d+")+/g,"")),value,isDefault)}function getDefaultValueForType(type){if(type==="int"||type==="float"){return"0"}else{if(type==="boolean"){return"false"}else{if(type==="color"){return"0x00000000"}else{return"null"}}}}function AstVar(definitions,varType){this.definitions=definitions;this.varType=varType}AstVar.prototype.getNames=function(){var names=[];for(var i=0,l=this.definitions.length;i<l;++i){names.push(this.definitions[i].name)}return names};AstVar.prototype.toString=function(){return"var "+this.definitions.join(",")};function AstStatement(expression){this.expression=expression}AstStatement.prototype.toString=function(){return this.expression.toString()};function transformStatement(statement){if(fieldTest.test(statement)){var attrAndType=attrAndTypeRegex.exec(statement);var definitions=statement.substring(attrAndType[0].length).split(",");var defaultTypeValue=getDefaultValueForType(attrAndType[2]);for(var i=0;i<definitions.length;++i){definitions[i]=transformVarDefinition(definitions[i],defaultTypeValue)}return new AstVar(definitions,attrAndType[2])}else{return new AstStatement(transformExpression(statement))}}function AstForExpression(initStatement,condition,step){this.initStatement=initStatement;this.condition=condition;this.step=step}AstForExpression.prototype.toString=function(){return"("+this.initStatement+"; "+this.condition+"; "+this.step+")"};function AstForInExpression(initStatement,container){this.initStatement=initStatement;this.container=container}AstForInExpression.prototype.toString=function(){var init=this.initStatement.toString();if(init.indexOf("=")>=0){init=init.substring(0,init.indexOf("="))}return"("+init+" in "+this.container+")"};function transformForExpression(expr){var content;if(/\bin\b/.test(expr)){content=expr.substring(1,expr.length-1).split(/\bin\b/g);return new AstForInExpression(transformStatement(trim(content[0])),transformExpression(content[1]))}else{content=expr.substring(1,expr.length-1).split(";");return new AstForExpression(transformStatement(trim(content[0])),transformExpression(content[1]),transformExpression(content[2]))}}function AstInnerInterface(name){this.name=name}AstInnerInterface.prototype.toString=function(){return"this."+this.name+" = function "+this.name+"() { throw 'This is an interface'; };"};function AstInnerClass(name,body){this.name=name;this.body=body;body.owner=this}AstInnerClass.prototype.toString=function(){return"this."+this.name+" = function "+this.name+"() {\n"+this.body+"};"};function transformInnerClass(class_){var m=classesRegex.exec(class_);classesRegex.lastIndex=0;var body=atoms[getAtomIndex(m[6])];if(m[2]==="interface"){return new AstInnerInterface(m[3])}else{var oldClassId=currentClassId,newClassId=generateClassId();currentClassId=newClassId;var innerClass=new AstInnerClass(m[3],transformClassBody(body,m[3],m[4],m[5]));appendClass(innerClass,newClassId,oldClassId);currentClassId=oldClassId;return innerClass}}function AstClassMethod(name,params,body){this.name=name;this.params=params;this.body=body}AstClassMethod.prototype.toString=function(){var thisReplacement=replaceContext("this");var paramNames=appendToLookupTable({},this.params.getNames());var oldContext=replaceContext;replaceContext=function(name){return paramNames.hasOwnProperty(name)?name:oldContext(name)};var result="processing.addMethod("+thisReplacement+", '"+this.name+"', function "+this.params+" "+this.body+");";replaceContext=oldContext;return result};function transformClassMethod(method){var m=methodsRegex.exec(method);methodsRegex.lastIndex=0;return new AstClassMethod(m[3],transformParams(atoms[getAtomIndex(m[4])]),transformStatementsBlock(atoms[getAtomIndex(m[6])]))}function AstClassField(definitions,fieldType,isStatic){this.definitions=definitions;this.fieldType=fieldType;this.isStatic=isStatic}AstClassField.prototype.getNames=function(){var names=[];for(var i=0,l=this.definitions.length;i<l;++i){names.push(this.definitions[i].name)}return names};AstClassField.prototype.toString=function(){var thisPrefix=replaceContext("this")+".";if(this.isStatic){var className=this.owner.name;var staticDeclarations=[];for(var i=0,l=this.definitions.length;i<l;++i){var definition=this.definitions[i];var name=definition.name,staticName=className+"."+name;var declaration="if("+staticName+" === void(0)) {\n "+staticName+" = "+definition.value+"; }\nprocessing.defineProperty("+replaceContext("this")+", '"+name+"', { get: function(){return "+staticName+";}, set: function(val){"+staticName+" = val;} });\n";staticDeclarations.push(declaration)}return staticDeclarations.join("")}else{return thisPrefix+this.definitions.join("; "+thisPrefix)}};function transformClassField(statement){var attrAndType=attrAndTypeRegex.exec(statement);var isStatic=attrAndType[1].indexOf("static")>=0;var definitions=statement.substring(attrAndType[0].length).split(/,\s*/g);var defaultTypeValue=getDefaultValueForType(attrAndType[2]);for(var i=0;i<definitions.length;++i){definitions[i]=transformVarDefinition(definitions[i],defaultTypeValue)}return new AstClassField(definitions,attrAndType[2],isStatic)}function AstConstructor(params,body){this.params=params;this.body=body}AstConstructor.prototype.toString=function(){var paramNames=appendToLookupTable({},this.params.getNames());var oldContext=replaceContext;replaceContext=function(name){return paramNames.hasOwnProperty(name)?name:oldContext(name)};var prefix="function $constr_"+this.params.params.length+this.params.toString();var body=this.body.toString();if(!/\$(superCstr|constr)\b/.test(body)){body="{\n$superCstr();\n"+body.substring(1)}replaceContext=oldContext;return prefix+body+"\n"};function transformConstructor(cstr){var m=new RegExp(/"B(\d+)"\s*"A(\d+)"/).exec(cstr);var params=transformParams(atoms[m[1]]);return new AstConstructor(params,transformStatementsBlock(atoms[m[2]]))}function AstClassBody(name,baseClassName,functions,methods,fields,cstrs,innerClasses,misc){var i,l;this.name=name;this.baseClassName=baseClassName;this.functions=functions;this.methods=methods;this.fields=fields;this.cstrs=cstrs;this.innerClasses=innerClasses;this.misc=misc;for(i=0,l=fields.length;i<l;++i){fields[i].owner=this}}AstClassBody.prototype.getMembers=function(){var members;if(this.owner.base){members=this.owner.base.body.getMembers()}else{members={fields:[],methods:[],innerClasses:[]}}var i,j,l,m;for(i=0,l=this.fields.length;i<l;++i){members.fields=members.fields.concat(this.fields[i].getNames())}for(i=0,l=this.methods.length;i<l;++i){var method=this.methods[i];members.methods.push(method.name)}for(i=0,l=this.innerClasses.length;i<l;++i){var innerClass=this.innerClasses[i];members.innerClasses.push(innerClass.name)}return members};AstClassBody.prototype.toString=function(){function getScopeLevel(p){var i=0;while(p){++i;p=p.scope}return i}var scopeLevel=getScopeLevel(this.owner);var selfId="$this_"+scopeLevel;var result="var "+selfId+" = this;\n";var members=this.getMembers();var thisClassFields=appendToLookupTable({},members.fields),thisClassMethods=appendToLookupTable({},members.methods),thisClassInners=appendToLookupTable({},members.innerClasses);var oldContext=replaceContext;replaceContext=function(name){if(name==="this"){return selfId}else{if(thisClassFields.hasOwnProperty(name)||thisClassInners.hasOwnProperty(name)){return selfId+"."+name}else{if(thisClassMethods.hasOwnProperty(name)){return"this."+name}}}return oldContext(name)};if(this.baseClassName){result+="var $super = {};\n";result+="function $superCstr(){\n"+this.baseClassName+".prototype.constructor.apply($super, arguments);\nprocessing.extendClass("+selfId+", $super); }\n"}else{result+="function $superCstr() { }\n"}result+=this.functions.join("\n")+"\n";result+=this.innerClasses.join("\n");result+=this.fields.join(";\n")+";\n";result+=this.methods.join("\n")+"\n";result+=this.misc.tail;result+=this.cstrs.join("\n")+"\n";result+="function $constr() {\n";var cstrsIfs=[];for(var i=0,l=this.cstrs.length;i<l;++i){var paramsLength=this.cstrs[i].params.params.length;cstrsIfs.push("if(arguments.length === "+paramsLength+") { $constr_"+paramsLength+".apply("+selfId+", arguments); }")}if(cstrsIfs.length>0){result+=cstrsIfs.join(" else ")+" else "}result+="$superCstr(); }\n";result+="$constr.apply(null, arguments);\n";replaceContext=oldContext;return result};transformClassBody=function(body,name,baseName,impls){var declarations=body.substring(1,body.length-1);declarations=extractClassesAndMethods(declarations);declarations=extractConstructors(declarations,name);var methods=[],classes=[],cstrs=[],functions=[];declarations=declarations.replace(/"([DEGH])(\d+)"/g,function(all,type,index){if(type==="D"){methods.push(index)}else{if(type==="E"){classes.push(index)}else{if(type==="H"){functions.push(index)}else{cstrs.push(index)}}}return""});var fields=declarations.split(";");var baseClassName;var i;if(baseName!==undef){baseClassName=baseName.replace(/^\s*extends\s+([A-Za-z_$][\w$]*)\s*$/g,"$1")}for(i=0;i<functions.length;++i){functions[i]=transformFunction(atoms[functions[i]])}for(i=0;i<methods.length;++i){methods[i]=transformClassMethod(atoms[methods[i]])}for(i=0;i<fields.length-1;++i){var field=trimSpaces(fields[i]);fields[i]=transformClassField(field.middle)}var tail=fields.pop();for(i=0;i<cstrs.length;++i){cstrs[i]=transformConstructor(atoms[cstrs[i]])}for(i=0;i<classes.length;++i){classes[i]=transformInnerClass(atoms[classes[i]])}return new AstClassBody(name,baseClassName,functions,methods,fields,cstrs,classes,{tail:tail})};function AstInterface(name){this.name=name}AstInterface.prototype.toString=function(){return"function "+this.name+"() {  throw 'This is an interface'; }\nprocessing."+this.name+" = "+this.name+";"};function AstClass(name,body){this.name=name;this.body=body;body.owner=this}AstClass.prototype.toString=function(){var staticVars="";for(var i=0,l=this.body.fields.length;i<l;i++){if(this.body.fields[i].isStatic){for(var x=0,xl=this.body.fields[i].definitions.length;x<xl;x++){staticVars+="var "+this.body.fields[i].definitions[x].name+" = "+this.body.name+"."+this.body.fields[i].definitions[x]+";"}}}return"function "+this.name+"() {\n"+this.body+"}\n"+staticVars+"\nprocessing."+this.name+" = "+this.name+";"};function transformGlobalClass(class_){var m=classesRegex.exec(class_);classesRegex.lastIndex=0;var body=atoms[getAtomIndex(m[6])];if(m[2]==="interface"){return new AstInterface(m[3])}else{var oldClassId=currentClassId,newClassId=generateClassId();currentClassId=newClassId;var globalClass=new AstClass(m[3],transformClassBody(body,m[3],m[4],m[5]));appendClass(globalClass,newClassId,oldClassId);currentClassId=oldClassId;return globalClass}}function AstMethod(name,params,body){this.name=name;this.params=params;this.body=body}AstMethod.prototype.toString=function(){var paramNames=appendToLookupTable({},this.params.getNames());var oldContext=replaceContext;replaceContext=function(name){return paramNames.hasOwnProperty(name)?name:oldContext(name)};var result="function "+this.name+this.params+" "+this.body+"\nprocessing."+this.name+" = "+this.name+";";replaceContext=oldContext;return result};function transformGlobalMethod(method){var m=methodsRegex.exec(method);var result=methodsRegex.lastIndex=0;return new AstMethod(m[3],transformParams(atoms[getAtomIndex(m[4])]),transformStatementsBlock(atoms[getAtomIndex(m[6])]))}function preStatementsTransform(statements){var s=statements;s=s.replace(/\b(catch\s*"B\d+"\s*"A\d+")(\s*catch\s*"B\d+"\s*"A\d+")+/g,"$1");return s}function AstForStatement(argument,misc){this.argument=argument;this.misc=misc}AstForStatement.prototype.toString=function(){return this.misc.prefix+this.argument.toString()};function AstCatchStatement(argument,misc){this.argument=argument;this.misc=misc}AstCatchStatement.prototype.toString=function(){return this.misc.prefix+this.argument.toString()};function AstPrefixStatement(name,argument,misc){this.name=name;this.argument=argument;this.misc=misc}AstPrefixStatement.prototype.toString=function(){var result=this.misc.prefix;if(this.argument!==undef){result+=this.argument.toString()}return result};function AstLabel(label){this.label=label}AstLabel.prototype.toString=function(){return this.label};transformStatements=function(statements,transformMethod,transformClass){var nextStatement=new RegExp(/\b(catch|for|if|switch|while|with)\s*"B(\d+)"|\b(do|else|finally|return|throw|try|break|continue)\b|("[ADEH](\d+)")|\b((?:case\s[^:]+|[A-Za-z_$][\w$]*\s*):)|(;)/g);var res=[];statements=preStatementsTransform(statements);var lastIndex=0,m,space;while((m=nextStatement.exec(statements))!==null){if(m[1]!==undef){var i=statements.lastIndexOf('"B',nextStatement.lastIndex);var statementsPrefix=statements.substring(lastIndex,i);if(m[1]==="for"){res.push(new AstForStatement(transformForExpression(atoms[m[2]]),{prefix:statementsPrefix}))}else{if(m[1]==="catch"){res.push(new AstCatchStatement(transformParams(atoms[m[2]]),{prefix:statementsPrefix}))}else{res.push(new AstPrefixStatement(m[1],transformExpression(atoms[m[2]]),{prefix:statementsPrefix}))}}}else{if(m[3]!==undef){res.push(new AstPrefixStatement(m[3],undef,{prefix:statements.substring(lastIndex,nextStatement.lastIndex)}))}else{if(m[4]!==undef){space=statements.substring(lastIndex,nextStatement.lastIndex-m[4].length);if(trim(space).length!==0){continue}res.push(space);var kind=m[4].charAt(1),atomIndex=m[5];if(kind==="D"){res.push(transformMethod(atoms[atomIndex]))}else{if(kind==="E"){res.push(transformClass(atoms[atomIndex]))}else{if(kind==="H"){res.push(transformFunction(atoms[atomIndex]))}else{res.push(transformStatementsBlock(atoms[atomIndex]))}}}}else{if(m[6]!==undef){space=statements.substring(lastIndex,nextStatement.lastIndex-m[6].length);if(trim(space).length!==0){continue}res.push(new AstLabel(statements.substring(lastIndex,nextStatement.lastIndex)))}else{var statement=trimSpaces(statements.substring(lastIndex,nextStatement.lastIndex-1));res.push(statement.left);res.push(transformStatement(statement.middle));res.push(statement.right+";")}}}}lastIndex=nextStatement.lastIndex}var statementsTail=trimSpaces(statements.substring(lastIndex));res.push(statementsTail.left);if(statementsTail.middle!==""){res.push(transformStatement(statementsTail.middle));res.push(";"+statementsTail.right)}return res};function getLocalNames(statements){var localNames=[];for(var i=0,l=statements.length;i<l;++i){var statement=statements[i];if(statement instanceof AstVar){localNames=localNames.concat(statement.getNames())}else{if(statement instanceof AstForStatement&&statement.argument.initStatement instanceof AstVar){localNames=localNames.concat(statement.argument.initStatement.getNames())}else{if(statement instanceof AstInnerInterface||statement instanceof AstInnerClass||statement instanceof AstInterface||statement instanceof AstClass||statement instanceof AstMethod||statement instanceof AstFunction){localNames.push(statement.name)}}}}return appendToLookupTable({},localNames)}function AstStatementsBlock(statements){this.statements=statements}AstStatementsBlock.prototype.toString=function(){var localNames=getLocalNames(this.statements);var oldContext=replaceContext;if(!isLookupTableEmpty(localNames)){replaceContext=function(name){return localNames.hasOwnProperty(name)?name:oldContext(name)}}var result="{\n"+this.statements.join("")+"\n}";replaceContext=oldContext;return result};transformStatementsBlock=function(block){var content=trimSpaces(block.substring(1,block.length-1));return new AstStatementsBlock(transformStatements(content.middle))};function AstRoot(statements){this.statements=statements}AstRoot.prototype.toString=function(){var localNames=getLocalNames(this.statements);replaceContext=function(name){if(localNames.hasOwnProperty(name)){return name}else{if(globalMembers.hasOwnProperty(name)){return"processing."+name}else{if(PConstants.hasOwnProperty(name)){return"$constants."+name}}}return name};var result="// this code was autogenerated from PJS\n(function(processing, $constants) {\n"+this.statements.join("")+"\n})";replaceContext=null;return result};transformMain=function(){var statements=extractClassesAndMethods(atoms[0]);statements=statements.replace(/\bimport\s+[^;]+;/g,"");return new AstRoot(transformStatements(statements,transformGlobalMethod,transformGlobalClass))};function generateMetadata(ast){var globalScope={};var id,class_;for(id in declaredClasses){if(declaredClasses.hasOwnProperty(id)){class_=declaredClasses[id];var scopeId=class_.scopeId,name=class_.name;if(scopeId){var scope=declaredClasses[scopeId];class_.scope=scope;if(scope.inScope===undef){scope.inScope={}}scope.inScope[name]=class_}else{globalScope[name]=class_}}}function findInScopes(class_,name){var parts=name.split(".");var currentScope=class_.scope,found;while(currentScope){if(currentScope.hasOwnProperty(parts[0])){found=currentScope[parts[0]];break}currentScope=currentScope.scope}if(found===undef){found=globalScope[parts[0]]}for(var i=1,l=parts.length;i<l&&found;++i){found=found.inScope[parts[i]]}return found}for(id in declaredClasses){if(declaredClasses.hasOwnProperty(id)){class_=declaredClasses[id];var baseClassName=class_.body.baseClassName;if(baseClassName){class_.base=findInScopes(class_,baseClassName)}}}}var transformed=transformMain();generateMetadata(transformed);var redendered=transformed.toString();redendered=redendered.replace(/\s*\n(?:[\t ]*\n)+/g,"\n\n");return injectStrings(redendered,strings)}function preprocessCode(aCode,sketch){var dm=new RegExp(/\/\*\s*@pjs\s+((?:[^\*]|\*+[^\*\/])*)\*\//g).exec(aCode);if(dm&&dm.length===2){var jsonItems=[],directives=dm.splice(1,2)[0].replace(/\{([\s\S]*?)\}/g,(function(){return function(all,item){jsonItems.push(item);return"{"+(jsonItems.length-1)+"}"}}())).replace("\n","").replace("\r","").split(";");var clean=function(s){return s.replace(/^\s*["']?/,"").replace(/["']?\s*$/,"")};for(var i=0,dl=directives.length;i<dl;i++){var pair=directives[i].split("=");if(pair&&pair.length===2){var key=clean(pair[0]),value=clean(pair[1]),list=[];if(key==="preload"){list=value.split(",");for(var j=0,jl=list.length;j<jl;j++){var imageName=clean(list[j]);sketch.imageCache.add(imageName)}}else{if(key==="transparent"){sketch.options.isTransparent=value==="true"}else{if(key==="font"){list=value.split(",");for(var x=0,xl=list.length;x<xl;x++){var fontName=clean(list[x]),index=/^\{(\d*?)\}$/.exec(fontName);sketch.fonts.add(index?JSON.parse("{"+jsonItems[index[1]]+"}"):fontName)}}else{if(key==="crisp"){sketch.options.crispLines=value==="true"}else{if(key==="pauseOnBlur"){sketch.options.pauseOnBlur=value==="true"}else{sketch.options[key]=value}}}}}}}}var codeWoStrings=aCode.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g,"");if(codeWoStrings.match(/\bsize\((?:.+),(?:.+),\s*(OPENGL|P3D)\s*\);/)){sketch.use3DContext=true}return aCode}Processing.compile=function(pdeCode){var sketch=new Processing.Sketch();var code=preprocessCode(pdeCode,sketch);var compiledPde=parseProcessing(code);sketch.sourceCode=compiledPde;return sketch};Error.prototype.printStackTrace=function(){return this.toString()};Processing.version="@VERSION@";Processing.lib={};Processing.instances=[];Processing.instanceIds={};Processing.removeInstance=function(id){Processing.instances.splice(Processing.instanceIds[id],1);delete Processing.instanceIds[id]};Processing.addInstance=function(processing){if(processing.externals.canvas.id===undef||!processing.externals.canvas.id.length){processing.externals.canvas.id="__processing"+Processing.instances.length}Processing.instanceIds[processing.externals.canvas.id]=Processing.instances.length;Processing.instances.push(processing)};Processing.getInstanceById=function(name){return Processing.instances[Processing.instanceIds[name]]};Processing.Sketch=function(attachFunction){this.attachFunction=attachFunction;this.use3DContext=false;this.options={isTransparent:false,crispLines:false,pauseOnBlur:false};this.imageCache={pending:0,images:{},add:function(href){var img=new Image();img.onload=(function(owner){return function(){owner.pending--}}(this));this.pending++;this.images[href]=img;img.src=href}};this.fonts={template:(function(){var element=document.createElement("p");element.style.fontFamily="serif";element.style.fontSize="72px";element.style.visibility="hidden";element.innerHTML="abcmmmmmmmmmmlll";document.getElementsByTagName("body")[0].appendChild(element);return element}()),attempt:0,pending:function(){var r=true;for(var i=0;i<this.fontList.length;i++){if(this.fontList[i].offsetWidth===this.template.offsetWidth&&this.fontList[i].offsetHeight===this.template.offsetHeight){r=false;this.attempt++}else{document.getElementsByTagName("body")[0].removeChild(this.fontList[i]);this.fontList.splice(i--,1);this.attempt=0}}if(this.attempt>=30){r=true;for(var j=0;j<this.fontList.length;j++){document.getElementsByTagName("body")[0].removeChild(this.fontList[j]);this.fontList.splice(j--,1)}}if(r){document.getElementsByTagName("body")[0].removeChild(this.template)}return r},fontList:[],fontFamily:"",style:document.createElement("style"),add:function(fontSrc){var fontName=(typeof fontSrc==="object"?fontSrc.fontFace:fontSrc),fontUrl=(typeof fontSrc==="object"?fontSrc.url:fontSrc);this.fontFamily+="@font-face{\n  font-family: '"+fontName+"';\n  src:  url('"+fontUrl+"');\n}\n";this.style.innerHTML=this.fontFamily;document.getElementsByTagName("head")[0].appendChild(this.style);var preLoader=document.createElement("p");preLoader.style.fontFamily="'"+fontName+"', serif";preLoader.style.fontSize="72px";preLoader.style.visibility="hidden";preLoader.innerHTML="abcmmmmmmmmmmlll";document.getElementsByTagName("body")[0].appendChild(preLoader);this.fontList.push(preLoader)}};this.sourceCode=undefined;this.attach=function(processing,constants){if(typeof this.attachFunction==="function"){this.attachFunction(processing,constants)}else{if(this.sourceCode){var func=eval(this.sourceCode);func(processing,constants);this.attachFunction=func}else{throw"Unable to attach sketch to the processing instance"}}};this.toString=function(){return this.sourceCode||"[attach: "+this.attachFunction+"]"};this.onblur=function(){};this.onfocus=function(){}};var init=function(){var canvas=document.getElementsByTagName("canvas");for(var i=0,l=canvas.length;i<l;i++){var processingSources=canvas[i].getAttribute("data-processing-sources");if(processingSources===null){processingSources=canvas[i].getAttribute("data-src");if(processingSources===null){processingSources=canvas[i].getAttribute("datasrc")}}if(processingSources){var filenames=processingSources.split(" ");var code="";for(var j=0,fl=filenames.length;j<fl;j++){if(filenames[j]){var block=ajax(filenames[j]);if(block!==false){code+=";\n"+block}}}Processing.addInstance(new Processing(canvas[i],code))}}};document.addEventListener("DOMContentLoaded",function(){init()},false);window.addEventListener("blur",function(){for(var i=0;i<Processing.instances.length;i++){Processing.instances[i].externals.onblur()}},false);window.addEventListener("focus",function(){for(var i=0;i<Processing.instances.length;i++){Processing.instances[i].externals.onfocus()}},false)}());
\ No newline at end of file
diff --git a/lib/qunit/qunit.css b/lib/qunit/qunit.css
new file mode 100644 (file)
index 0000000..e9404f5
--- /dev/null
@@ -0,0 +1,155 @@
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+       font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
+       margin: 0;
+       padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+       padding: 0.5em 0 0.5em 1em;
+       
+       color: #fff;
+       text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
+       background-color: #0d3349;
+       
+       border-radius: 15px 15px 0 0;
+       -moz-border-radius: 15px 15px 0 0;
+       -webkit-border-top-right-radius: 15px;
+       -webkit-border-top-left-radius: 15px;
+}
+
+#qunit-header a {
+       text-decoration: none;
+       color: white;
+}
+
+#qunit-banner {
+       height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+       padding: 0em 0 0.5em 2em;
+}
+
+#qunit-userAgent {
+       padding: 0.5em 0 0.5em 2.5em;
+       background-color: #2b81af;
+       color: #fff;
+       text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+       list-style-position: inside;
+}
+
+#qunit-tests li {
+       padding: 0.4em 0.5em 0.4em 2.5em;
+       border-bottom: 1px solid #fff;
+       list-style-position: inside;
+}
+
+#qunit-tests li strong {
+       cursor: pointer;
+}
+
+#qunit-tests ol {
+       margin-top: 0.5em;
+       padding: 0.5em;
+       
+       background-color: #fff;
+       
+       border-radius: 15px;
+       -moz-border-radius: 15px;
+       -webkit-border-radius: 15px;
+       
+       box-shadow: inset 0px 2px 13px #999;
+       -moz-box-shadow: inset 0px 2px 13px #999;
+       -webkit-box-shadow: inset 0px 2px 13px #999;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts                       { color: black; }
+#qunit-tests b.passed                       { color: #5E740B; }
+#qunit-tests b.failed                       { color: #710909; }
+
+#qunit-tests li li {
+       margin: 0.5em;
+       padding: 0.4em 0.5em 0.4em 0.5em;
+       background-color: #fff;
+       border-bottom: none;
+       list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+       color: #5E740B;
+       background-color: #fff;
+       border-left: 26px solid #C6E746;
+}
+
+#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name               { color: #366097; }
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected           { color: #999999; }
+
+#qunit-banner.qunit-pass                    { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+       color: #710909;
+       background-color: #fff;
+       border-left: 26px solid #EE5757;
+}
+
+#qunit-tests .fail                          { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name             { color: #000000; }
+
+#qunit-tests .fail .test-actual             { color: #EE5757; }
+#qunit-tests .fail .test-expected           { color: green;   }
+
+#qunit-banner.qunit-fail, 
+#qunit-testrunner-toolbar                   { background-color: #EE5757; }
+
+
+/** Footer */
+
+#qunit-testresult {
+       padding: 0.5em 0.5em 0.5em 2.5em;
+
+       color: #2b81af;
+       background-color: #D2E0E6;
+
+       border-radius: 0 0 15px 15px;
+       -moz-border-radius: 0 0 15px 15px;
+       -webkit-border-bottom-right-radius: 15px;
+       -webkit-border-bottom-left-radius: 15px;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+       position: absolute;
+       top: -10000px;
+       left: -10000px;
+}
diff --git a/lib/qunit/qunit.js b/lib/qunit/qunit.js
new file mode 100644 (file)
index 0000000..070f6b7
--- /dev/null
@@ -0,0 +1,1261 @@
+/*
+ * QUnit - A JavaScript Unit Testing Framework
+ * 
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2009 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ */
+
+(function(window) {
+
+var QUnit = {
+
+       // call on start of module test to prepend name to all tests
+       module: function(name, testEnvironment) {
+               config.currentModule = name;
+
+               synchronize(function() {
+                       if ( config.currentModule ) {
+                               QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
+                       }
+
+                       config.currentModule = name;
+                       config.moduleTestEnvironment = testEnvironment;
+                       config.moduleStats = { all: 0, bad: 0 };
+
+                       QUnit.moduleStart( name, testEnvironment );
+               });
+       },
+
+       asyncTest: function(testName, expected, callback) {
+               if ( arguments.length === 2 ) {
+                       callback = expected;
+                       expected = 0;
+               }
+
+               QUnit.test(testName, expected, callback, true);
+       },
+       
+       test: function(testName, expected, callback, async) {
+               var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg;
+
+               if ( arguments.length === 2 ) {
+                       callback = expected;
+                       expected = null;
+               }
+               // is 2nd argument a testEnvironment?
+               if ( expected && typeof expected === 'object') {
+                       testEnvironmentArg =  expected;
+                       expected = null;
+               }
+
+               if ( config.currentModule ) {
+                       name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
+               }
+
+               if ( !validTest(config.currentModule + ": " + testName) ) {
+                       return;
+               }
+
+               synchronize(function() {
+
+                       testEnvironment = extend({
+                               setup: function() {},
+                               teardown: function() {}
+                       }, config.moduleTestEnvironment);
+                       if (testEnvironmentArg) {
+                               extend(testEnvironment,testEnvironmentArg);
+                       }
+
+                       QUnit.testStart( testName, testEnvironment );
+
+                       // allow utility functions to access the current test environment
+                       QUnit.current_testEnvironment = testEnvironment;
+                       
+                       config.assertions = [];
+                       config.expected = expected;
+                       
+                       var tests = id("qunit-tests");
+                       if (tests) {
+                               var b = document.createElement("strong");
+                                       b.innerHTML = "Running " + name;
+                               var li = document.createElement("li");
+                                       li.appendChild( b );
+                                       li.id = "current-test-output";
+                               tests.appendChild( li )
+                       }
+
+                       try {
+                               if ( !config.pollution ) {
+                                       saveGlobal();
+                               }
+
+                               testEnvironment.setup.call(testEnvironment);
+                       } catch(e) {
+                               QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
+                       }
+           });
+       
+           synchronize(function() {
+                       if ( async ) {
+                               QUnit.stop();
+                       }
+
+                       try {
+                               callback.call(testEnvironment);
+                       } catch(e) {
+                               fail("Test " + name + " died, exception and test follows", e, callback);
+                               QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
+                               // else next test will carry the responsibility
+                               saveGlobal();
+
+                               // Restart the tests if they're blocking
+                               if ( config.blocking ) {
+                                       start();
+                               }
+                       }
+               });
+
+               synchronize(function() {
+                       try {
+                               checkPollution();
+                               testEnvironment.teardown.call(testEnvironment);
+                       } catch(e) {
+                               QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
+                       }
+           });
+       
+           synchronize(function() {
+                       try {
+                               QUnit.reset();
+                       } catch(e) {
+                               fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset);
+                       }
+
+                       if ( config.expected && config.expected != config.assertions.length ) {
+                               QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
+                       }
+
+                       var good = 0, bad = 0,
+                               tests = id("qunit-tests");
+
+                       config.stats.all += config.assertions.length;
+                       config.moduleStats.all += config.assertions.length;
+
+                       if ( tests ) {
+                               var ol  = document.createElement("ol");
+
+                               for ( var i = 0; i < config.assertions.length; i++ ) {
+                                       var assertion = config.assertions[i];
+
+                                       var li = document.createElement("li");
+                                       li.className = assertion.result ? "pass" : "fail";
+                                       li.innerHTML = assertion.message || "(no message)";
+                                       ol.appendChild( li );
+
+                                       if ( assertion.result ) {
+                                               good++;
+                                       } else {
+                                               bad++;
+                                               config.stats.bad++;
+                                               config.moduleStats.bad++;
+                                       }
+                               }
+                               if (bad == 0) {
+                                       ol.style.display = "none";
+                               }
+
+                               var b = document.createElement("strong");
+                               b.innerHTML = name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + config.assertions.length + ")</b>";
+                               
+                               addEvent(b, "click", function() {
+                                       var next = b.nextSibling, display = next.style.display;
+                                       next.style.display = display === "none" ? "block" : "none";
+                               });
+                               
+                               addEvent(b, "dblclick", function(e) {
+                                       var target = e && e.target ? e.target : window.event.srcElement;
+                                       if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+                                               target = target.parentNode;
+                                       }
+                                       if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+                                               window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
+                                       }
+                               });
+
+                               var li = id("current-test-output");
+                               li.id = "";
+                               li.className = bad ? "fail" : "pass";
+                               li.removeChild( li.firstChild );
+                               li.appendChild( b );
+                               li.appendChild( ol );
+
+                               if ( bad ) {
+                                       var toolbar = id("qunit-testrunner-toolbar");
+                                       if ( toolbar ) {
+                                               toolbar.style.display = "block";
+                                               id("qunit-filter-pass").disabled = null;
+                                               id("qunit-filter-missing").disabled = null;
+                                       }
+                               }
+
+                       } else {
+                               for ( var i = 0; i < config.assertions.length; i++ ) {
+                                       if ( !config.assertions[i].result ) {
+                                               bad++;
+                                               config.stats.bad++;
+                                               config.moduleStats.bad++;
+                                       }
+                               }
+                       }
+
+                       QUnit.testDone( testName, bad, config.assertions.length );
+
+                       if ( !window.setTimeout && !config.queue.length ) {
+                               done();
+                       }
+               });
+
+               synchronize( done );
+       },
+       
+       /**
+        * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+        */
+       expect: function(asserts) {
+               config.expected = asserts;
+       },
+
+       /**
+        * Asserts true.
+        * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+        */
+       ok: function(a, msg) {
+               msg = escapeHtml(msg);
+               QUnit.log(a, msg);
+
+               config.assertions.push({
+                       result: !!a,
+                       message: msg
+               });
+       },
+
+       /**
+        * Checks that the first two arguments are equal, with an optional message.
+        * Prints out both actual and expected values.
+        *
+        * Prefered to ok( actual == expected, message )
+        *
+        * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
+        *
+        * @param Object actual
+        * @param Object expected
+        * @param String message (optional)
+        */
+       equal: function(actual, expected, message) {
+               push(expected == actual, actual, expected, message);
+       },
+
+       notEqual: function(actual, expected, message) {
+               push(expected != actual, actual, expected, message);
+       },
+       
+       deepEqual: function(actual, expected, message) {
+               push(QUnit.equiv(actual, expected), actual, expected, message);
+       },
+
+       notDeepEqual: function(actual, expected, message) {
+               push(!QUnit.equiv(actual, expected), actual, expected, message);
+       },
+
+       strictEqual: function(actual, expected, message) {
+               push(expected === actual, actual, expected, message);
+       },
+
+       notStrictEqual: function(actual, expected, message) {
+               push(expected !== actual, actual, expected, message);
+       },
+
+       raises: function(fn,  message) {
+               try {
+                       fn();
+                       ok( false, message );
+               }
+               catch (e) {
+                       ok( true, message );
+               }
+       },
+
+       start: function() {
+               // A slight delay, to avoid any current callbacks
+               if ( window.setTimeout ) {
+                       window.setTimeout(function() {
+                               if ( config.timeout ) {
+                                       clearTimeout(config.timeout);
+                               }
+
+                               config.blocking = false;
+                               process();
+                       }, 13);
+               } else {
+                       config.blocking = false;
+                       process();
+               }
+       },
+       
+       stop: function(timeout) {
+               config.blocking = true;
+
+               if ( timeout && window.setTimeout ) {
+                       config.timeout = window.setTimeout(function() {
+                               QUnit.ok( false, "Test timed out" );
+                               QUnit.start();
+                       }, timeout);
+               }
+       }
+
+};
+
+// Backwards compatibility, deprecated
+QUnit.equals = QUnit.equal;
+QUnit.same = QUnit.deepEqual;
+
+// Maintain internal state
+var config = {
+       // The queue of tests to run
+       queue: [],
+
+       // block until document ready
+       blocking: true
+};
+
+// Load paramaters
+(function() {
+       var location = window.location || { search: "", protocol: "file:" },
+               GETParams = location.search.slice(1).split('&');
+
+       for ( var i = 0; i < GETParams.length; i++ ) {
+               GETParams[i] = decodeURIComponent( GETParams[i] );
+               if ( GETParams[i] === "noglobals" ) {
+                       GETParams.splice( i, 1 );
+                       i--;
+                       config.noglobals = true;
+               } else if ( GETParams[i].search('=') > -1 ) {
+                       GETParams.splice( i, 1 );
+                       i--;
+               }
+       }
+       
+       // restrict modules/tests by get parameters
+       config.filters = GETParams;
+       
+       // Figure out if we're running the tests from a server or not
+       QUnit.isLocal = !!(location.protocol === 'file:');
+})();
+
+// Expose the API as global variables, unless an 'exports'
+// object exists, in that case we assume we're in CommonJS
+if ( typeof exports === "undefined" || typeof require === "undefined" ) {
+       extend(window, QUnit);
+       window.QUnit = QUnit;
+} else {
+       extend(exports, QUnit);
+       exports.QUnit = QUnit;
+}
+
+// define these after exposing globals to keep them in these QUnit namespace only
+extend(QUnit, {
+       config: config,
+
+       // Initialize the configuration options
+       init: function() {
+               extend(config, {
+                       stats: { all: 0, bad: 0 },
+                       moduleStats: { all: 0, bad: 0 },
+                       started: +new Date,
+                       updateRate: 1000,
+                       blocking: false,
+                       autostart: true,
+                       autorun: false,
+                       assertions: [],
+                       filters: [],
+                       queue: []
+               });
+
+               var tests = id("qunit-tests"),
+                       banner = id("qunit-banner"),
+                       result = id("qunit-testresult");
+
+               if ( tests ) {
+                       tests.innerHTML = "";
+               }
+
+               if ( banner ) {
+                       banner.className = "";
+               }
+
+               if ( result ) {
+                       result.parentNode.removeChild( result );
+               }
+       },
+       
+       /**
+        * Resets the test setup. Useful for tests that modify the DOM.
+        */
+       reset: function() {
+               if ( window.jQuery ) {
+                       jQuery("#main, #qunit-fixture").html( config.fixture );
+               }
+       },
+       
+       /**
+        * Trigger an event on an element.
+        *
+        * @example triggerEvent( document.body, "click" );
+        *
+        * @param DOMElement elem
+        * @param String type
+        */
+       triggerEvent: function( elem, type, event ) {
+               if ( document.createEvent ) {
+                       event = document.createEvent("MouseEvents");
+                       event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+                               0, 0, 0, 0, 0, false, false, false, false, 0, null);
+                       elem.dispatchEvent( event );
+
+               } else if ( elem.fireEvent ) {
+                       elem.fireEvent("on"+type);
+               }
+       },
+       
+       // Safe object type checking
+       is: function( type, obj ) {
+               return QUnit.objectType( obj ) == type;
+       },
+       
+       objectType: function( obj ) {
+               if (typeof obj === "undefined") {
+                               return "undefined";
+
+               // consider: typeof null === object
+               }
+               if (obj === null) {
+                               return "null";
+               }
+
+               var type = Object.prototype.toString.call( obj )
+                       .match(/^\[object\s(.*)\]$/)[1] || '';
+
+               switch (type) {
+                               case 'Number':
+                                               if (isNaN(obj)) {
+                                                               return "nan";
+                                               } else {
+                                                               return "number";
+                                               }
+                               case 'String':
+                               case 'Boolean':
+                               case 'Array':
+                               case 'Date':
+                               case 'RegExp':
+                               case 'Function':
+                                               return type.toLowerCase();
+               }
+               if (typeof obj === "object") {
+                               return "object";
+               }
+               return undefined;
+       },
+       
+       // Logging callbacks
+       begin: function() {},
+       done: function(failures, total) {},
+       log: function(result, message) {},
+       testStart: function(name, testEnvironment) {},
+       testDone: function(name, failures, total) {},
+       moduleStart: function(name, testEnvironment) {},
+       moduleDone: function(name, failures, total) {}
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+       config.autorun = true;
+}
+
+addEvent(window, "load", function() {
+       QUnit.begin();
+       
+       // Initialize the config, saving the execution queue
+       var oldconfig = extend({}, config);
+       QUnit.init();
+       extend(config, oldconfig);
+
+       config.blocking = false;
+
+       var userAgent = id("qunit-userAgent");
+       if ( userAgent ) {
+               userAgent.innerHTML = navigator.userAgent;
+       }
+       var banner = id("qunit-header");
+       if ( banner ) {
+               banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>'; 
+       }
+       
+       var toolbar = id("qunit-testrunner-toolbar");
+       if ( toolbar ) {
+               toolbar.style.display = "none";
+               
+               var filter = document.createElement("input");
+               filter.type = "checkbox";
+               filter.id = "qunit-filter-pass";
+               filter.disabled = true;
+               addEvent( filter, "click", function() {
+                       var li = document.getElementsByTagName("li");
+                       for ( var i = 0; i < li.length; i++ ) {
+                               if ( li[i].className.indexOf("pass") > -1 ) {
+                                       li[i].style.display = filter.checked ? "none" : "";
+                               }
+                       }
+               });
+               toolbar.appendChild( filter );
+
+               var label = document.createElement("label");
+               label.setAttribute("for", "qunit-filter-pass");
+               label.innerHTML = "Hide passed tests";
+               toolbar.appendChild( label );
+
+               var missing = document.createElement("input");
+               missing.type = "checkbox";
+               missing.id = "qunit-filter-missing";
+               missing.disabled = true;
+               addEvent( missing, "click", function() {
+                       var li = document.getElementsByTagName("li");
+                       for ( var i = 0; i < li.length; i++ ) {
+                               if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
+                                       li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
+                               }
+                       }
+               });
+               toolbar.appendChild( missing );
+
+               label = document.createElement("label");
+               label.setAttribute("for", "qunit-filter-missing");
+               label.innerHTML = "Hide missing tests (untested code is broken code)";
+               toolbar.appendChild( label );
+       }
+
+       var main = id('main') || id('qunit-fixture');
+       if ( main ) {
+               config.fixture = main.innerHTML;
+       }
+
+       if (config.autostart) {
+               QUnit.start();
+       }
+});
+
+function done() {
+       if ( config.doneTimer && window.clearTimeout ) {
+               window.clearTimeout( config.doneTimer );
+               config.doneTimer = null;
+       }
+
+       if ( config.queue.length ) {
+               config.doneTimer = window.setTimeout(function(){
+                       if ( !config.queue.length ) {
+                               done();
+                       } else {
+                               synchronize( done );
+                       }
+               }, 13);
+
+               return;
+       }
+
+       config.autorun = true;
+
+       // Log the last module results
+       if ( config.currentModule ) {
+               QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
+       }
+
+       var banner = id("qunit-banner"),
+               tests = id("qunit-tests"),
+               html = ['Tests completed in ',
+               +new Date - config.started, ' milliseconds.<br/>',
+               '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
+
+       if ( banner ) {
+               banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
+       }
+
+       if ( tests ) {  
+               var result = id("qunit-testresult");
+
+               if ( !result ) {
+                       result = document.createElement("p");
+                       result.id = "qunit-testresult";
+                       result.className = "result";
+                       tests.parentNode.insertBefore( result, tests.nextSibling );
+               }
+
+               result.innerHTML = html;
+       }
+
+       QUnit.done( config.stats.bad, config.stats.all );
+}
+
+function validTest( name ) {
+       var i = config.filters.length,
+               run = false;
+
+       if ( !i ) {
+               return true;
+       }
+       
+       while ( i-- ) {
+               var filter = config.filters[i],
+                       not = filter.charAt(0) == '!';
+
+               if ( not ) {
+                       filter = filter.slice(1);
+               }
+
+               if ( name.indexOf(filter) !== -1 ) {
+                       return !not;
+               }
+
+               if ( not ) {
+                       run = true;
+               }
+       }
+
+       return run;
+}
+
+function escapeHtml(s) {
+       s = s === null ? "" : s + "";
+       return s.replace(/[\&"<>\\]/g, function(s) {
+               switch(s) {
+                       case "&": return "&amp;";
+                       case "\\": return "\\\\";
+                       case '"': return '\"';
+                       case "<": return "&lt;";
+                       case ">": return "&gt;";
+                       default: return s;
+               }
+       });
+}
+
+function push(result, actual, expected, message) {
+       message = escapeHtml(message) || (result ? "okay" : "failed");
+       message = '<span class="test-message">' + message + "</span>";
+       expected = escapeHtml(QUnit.jsDump.parse(expected));
+       actual = escapeHtml(QUnit.jsDump.parse(actual));
+       var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
+       if (actual != expected) {
+               output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
+       }
+       
+       // can't use ok, as that would double-escape messages
+       QUnit.log(result, output);
+       config.assertions.push({
+               result: !!result,
+               message: output
+       });
+}
+
+function synchronize( callback ) {
+       config.queue.push( callback );
+
+       if ( config.autorun && !config.blocking ) {
+               process();
+       }
+}
+
+function process() {
+       var start = (new Date()).getTime();
+
+       while ( config.queue.length && !config.blocking ) {
+               if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
+                       config.queue.shift()();
+
+               } else {
+                       setTimeout( process, 13 );
+                       break;
+               }
+       }
+}
+
+function saveGlobal() {
+       config.pollution = [];
+       
+       if ( config.noglobals ) {
+               for ( var key in window ) {
+                       config.pollution.push( key );
+               }
+       }
+}
+
+function checkPollution( name ) {
+       var old = config.pollution;
+       saveGlobal();
+       
+       var newGlobals = diff( old, config.pollution );
+       if ( newGlobals.length > 0 ) {
+               ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
+               config.expected++;
+       }
+
+       var deletedGlobals = diff( config.pollution, old );
+       if ( deletedGlobals.length > 0 ) {
+               ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
+               config.expected++;
+       }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+       var result = a.slice();
+       for ( var i = 0; i < result.length; i++ ) {
+               for ( var j = 0; j < b.length; j++ ) {
+                       if ( result[i] === b[j] ) {
+                               result.splice(i, 1);
+                               i--;
+                               break;
+                       }
+               }
+       }
+       return result;
+}
+
+function fail(message, exception, callback) {
+       if ( typeof console !== "undefined" && console.error && console.warn ) {
+               console.error(message);
+               console.error(exception);
+               console.warn(callback.toString());
+
+       } else if ( window.opera && opera.postError ) {
+               opera.postError(message, exception, callback.toString);
+       }
+}
+
+function extend(a, b) {
+       for ( var prop in b ) {
+               a[prop] = b[prop];
+       }
+
+       return a;
+}
+
+function addEvent(elem, type, fn) {
+       if ( elem.addEventListener ) {
+               elem.addEventListener( type, fn, false );
+       } else if ( elem.attachEvent ) {
+               elem.attachEvent( "on" + type, fn );
+       } else {
+               fn();
+       }
+}
+
+function id(name) {
+       return !!(typeof document !== "undefined" && document && document.getElementById) &&
+               document.getElementById( name );
+}
+
+// Test for equality any JavaScript type.
+// Discussions and reference: http://philrathe.com/articles/equiv
+// Test suites: http://philrathe.com/tests/equiv
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = function () {
+
+    var innerEquiv; // the real equiv function
+    var callers = []; // stack to decide between skip/abort functions
+    var parents = []; // stack to avoiding loops from circular referencing
+
+    // Call the o related callback with the given arguments.
+    function bindCallbacks(o, callbacks, args) {
+        var prop = QUnit.objectType(o);
+        if (prop) {
+            if (QUnit.objectType(callbacks[prop]) === "function") {
+                return callbacks[prop].apply(callbacks, args);
+            } else {
+                return callbacks[prop]; // or undefined
+            }
+        }
+    }
+    
+    var callbacks = function () {
+
+        // for string, boolean, number and null
+        function useStrictEquality(b, a) {
+            if (b instanceof a.constructor || a instanceof b.constructor) {
+                // to catch short annotaion VS 'new' annotation of a declaration
+                // e.g. var i = 1;
+                //      var j = new Number(1);
+                return a == b;
+            } else {
+                return a === b;
+            }
+        }
+
+        return {
+            "string": useStrictEquality,
+            "boolean": useStrictEquality,
+            "number": useStrictEquality,
+            "null": useStrictEquality,
+            "undefined": useStrictEquality,
+
+            "nan": function (b) {
+                return isNaN(b);
+            },
+
+            "date": function (b, a) {
+                return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
+            },
+
+            "regexp": function (b, a) {
+                return QUnit.objectType(b) === "regexp" &&
+                    a.source === b.source && // the regex itself
+                    a.global === b.global && // and its modifers (gmi) ...
+                    a.ignoreCase === b.ignoreCase &&
+                    a.multiline === b.multiline;
+            },
+
+            // - skip when the property is a method of an instance (OOP)
+            // - abort otherwise,
+            //   initial === would have catch identical references anyway
+            "function": function () {
+                var caller = callers[callers.length - 1];
+                return caller !== Object &&
+                        typeof caller !== "undefined";
+            },
+
+            "array": function (b, a) {
+                var i, j, loop;
+                var len;
+
+                // b could be an object literal here
+                if ( ! (QUnit.objectType(b) === "array")) {
+                    return false;
+                }   
+                
+                len = a.length;
+                if (len !== b.length) { // safe and faster
+                    return false;
+                }
+                
+                //track reference to avoid circular references
+                parents.push(a);
+                for (i = 0; i < len; i++) {
+                    loop = false;
+                    for(j=0;j<parents.length;j++){
+                        if(parents[j] === a[i]){
+                            loop = true;//dont rewalk array
+                        }
+                    }
+                    if (!loop && ! innerEquiv(a[i], b[i])) {
+                        parents.pop();
+                        return false;
+                    }
+                }
+                parents.pop();
+                return true;
+            },
+
+            "object": function (b, a) {
+                var i, j, loop;
+                var eq = true; // unless we can proove it
+                var aProperties = [], bProperties = []; // collection of strings
+
+                // comparing constructors is more strict than using instanceof
+                if ( a.constructor !== b.constructor) {
+                    return false;
+                }
+
+                // stack constructor before traversing properties
+                callers.push(a.constructor);
+                //track reference to avoid circular references
+                parents.push(a);
+                
+                for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
+                    loop = false;
+                    for(j=0;j<parents.length;j++){
+                        if(parents[j] === a[i])
+                            loop = true; //don't go down the same path twice
+                    }
+                    aProperties.push(i); // collect a's properties
+
+                    if (!loop && ! innerEquiv(a[i], b[i])) {
+                        eq = false;
+                        break;
+                    }
+                }
+
+                callers.pop(); // unstack, we are done
+                parents.pop();
+
+                for (i in b) {
+                    bProperties.push(i); // collect b's properties
+                }
+
+                // Ensures identical properties name
+                return eq && innerEquiv(aProperties.sort(), bProperties.sort());
+            }
+        };
+    }();
+
+    innerEquiv = function () { // can take multiple arguments
+        var args = Array.prototype.slice.apply(arguments);
+        if (args.length < 2) {
+            return true; // end transition
+        }
+
+        return (function (a, b) {
+            if (a === b) {
+                return true; // catch the most you can
+            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
+                return false; // don't lose time with error prone cases
+            } else {
+                return bindCallbacks(a, callbacks, [b, a]);
+            }
+
+        // apply transition with (1..n) arguments
+        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
+    };
+
+    return innerEquiv;
+
+}();
+
+/**
+ * jsDump
+ * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
+ * Date: 5/15/2008
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+       function quote( str ) {
+               return '"' + str.toString().replace(/"/g, '\\"') + '"';
+       };
+       function literal( o ) {
+               return o + '';  
+       };
+       function join( pre, arr, post ) {
+               var s = jsDump.separator(),
+                       base = jsDump.indent(),
+                       inner = jsDump.indent(1);
+               if ( arr.join )
+                       arr = arr.join( ',' + s + inner );
+               if ( !arr )
+                       return pre + post;
+               return [ pre, inner + arr, base + post ].join(s);
+       };
+       function array( arr ) {
+               var i = arr.length,     ret = Array(i);                                 
+               this.up();
+               while ( i-- )
+                       ret[i] = this.parse( arr[i] );                          
+               this.down();
+               return join( '[', ret, ']' );
+       };
+       
+       var reName = /^function (\w+)/;
+       
+       var jsDump = {
+               parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
+                       var     parser = this.parsers[ type || this.typeOf(obj) ];
+                       type = typeof parser;                   
+                       
+                       return type == 'function' ? parser.call( this, obj ) :
+                                  type == 'string' ? parser :
+                                  this.parsers.error;
+               },
+               typeOf:function( obj ) {
+                       var type;
+                       if ( obj === null ) {
+                               type = "null";
+                       } else if (typeof obj === "undefined") {
+                               type = "undefined";
+                       } else if (QUnit.is("RegExp", obj)) {
+                               type = "regexp";
+                       } else if (QUnit.is("Date", obj)) {
+                               type = "date";
+                       } else if (QUnit.is("Function", obj)) {
+                               type = "function";
+                       } else if (obj.setInterval && obj.document && !obj.nodeType) {
+                               type = "window";
+                       } else if (obj.nodeType === 9) {
+                               type = "document";
+                       } else if (obj.nodeType) {
+                               type = "node";
+                       } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
+                               type = "array";
+                       } else {
+                               type = typeof obj;
+                       }
+                       return type;
+               },
+               separator:function() {
+                       return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
+               },
+               indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+                       if ( !this.multiline )
+                               return '';
+                       var chr = this.indentChar;
+                       if ( this.HTML )
+                               chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
+                       return Array( this._depth_ + (extra||0) ).join(chr);
+               },
+               up:function( a ) {
+                       this._depth_ += a || 1;
+               },
+               down:function( a ) {
+                       this._depth_ -= a || 1;
+               },
+               setParser:function( name, parser ) {
+                       this.parsers[name] = parser;
+               },
+               // The next 3 are exposed so you can use them
+               quote:quote, 
+               literal:literal,
+               join:join,
+               //
+               _depth_: 1,
+               // This is the list of parsers, to modify them, use jsDump.setParser
+               parsers:{
+                       window: '[Window]',
+                       document: '[Document]',
+                       error:'[ERROR]', //when no parser is found, shouldn't happen
+                       unknown: '[Unknown]',
+                       'null':'null',
+                       undefined:'undefined',
+                       'function':function( fn ) {
+                               var ret = 'function',
+                                       name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
+                               if ( name )
+                                       ret += ' ' + name;
+                               ret += '(';
+                               
+                               ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
+                               return join( ret, this.parse(fn,'functionCode'), '}' );
+                       },
+                       array: array,
+                       nodelist: array,
+                       arguments: array,
+                       object:function( map ) {
+                               var ret = [ ];
+                               this.up();
+                               for ( var key in map )
+                                       ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
+                               this.down();
+                               return join( '{', ret, '}' );
+                       },
+                       node:function( node ) {
+                               var open = this.HTML ? '&lt;' : '<',
+                                       close = this.HTML ? '&gt;' : '>';
+                                       
+                               var tag = node.nodeName.toLowerCase(),
+                                       ret = open + tag;
+                                       
+                               for ( var a in this.DOMAttrs ) {
+                                       var val = node[this.DOMAttrs[a]];
+                                       if ( val )
+                                               ret += ' ' + a + '=' + this.parse( val, 'attribute' );
+                               }
+                               return ret + close + open + '/' + tag + close;
+                       },
+                       functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
+                               var l = fn.length;
+                               if ( !l ) return '';                            
+                               
+                               var args = Array(l);
+                               while ( l-- )
+                                       args[l] = String.fromCharCode(97+l);//97 is 'a'
+                               return ' ' + args.join(', ') + ' ';
+                       },
+                       key:quote, //object calls it internally, the key part of an item in a map
+                       functionCode:'[code]', //function calls it internally, it's the content of the function
+                       attribute:quote, //node calls it internally, it's an html attribute value
+                       string:quote,
+                       date:quote,
+                       regexp:literal, //regex
+                       number:literal,
+                       'boolean':literal
+               },
+               DOMAttrs:{//attributes to dump from nodes, name=>realName
+                       id:'id',
+                       name:'name',
+                       'class':'className'
+               },
+               HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
+               indentChar:'   ',//indentation unit
+               multiline:false //if true, items in a collection, are separated by a \n, else just a space.
+       };
+
+       return jsDump;
+})();
+
+// from Sizzle.js
+function getText( elems ) {
+       var ret = "", elem;
+
+       for ( var i = 0; elems[i]; i++ ) {
+               elem = elems[i];
+
+               // Get the text from text nodes and CDATA nodes
+               if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+                       ret += elem.nodeValue;
+
+               // Traverse everything else, except comment nodes
+               } else if ( elem.nodeType !== 8 ) {
+                       ret += getText( elem.childNodes );
+               }
+       }
+
+       return ret;
+};
+
+/*
+ * Javascript Diff Algorithm
+ *  By John Resig (http://ejohn.org/)
+ *  Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ *  http://ejohn.org/projects/javascript-diff-algorithm/
+ *  
+ * Usage: QUnit.diff(expected, actual)
+ * 
+ * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
+ */
+QUnit.diff = (function() {
+       function diff(o, n){
+               var ns = new Object();
+               var os = new Object();
+               
+               for (var i = 0; i < n.length; i++) {
+                       if (ns[n[i]] == null) 
+                               ns[n[i]] = {
+                                       rows: new Array(),
+                                       o: null
+                               };
+                       ns[n[i]].rows.push(i);
+               }
+               
+               for (var i = 0; i < o.length; i++) {
+                       if (os[o[i]] == null) 
+                               os[o[i]] = {
+                                       rows: new Array(),
+                                       n: null
+                               };
+                       os[o[i]].rows.push(i);
+               }
+               
+               for (var i in ns) {
+                       if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
+                               n[ns[i].rows[0]] = {
+                                       text: n[ns[i].rows[0]],
+                                       row: os[i].rows[0]
+                               };
+                               o[os[i].rows[0]] = {
+                                       text: o[os[i].rows[0]],
+                                       row: ns[i].rows[0]
+                               };
+                       }
+               }
+               
+               for (var i = 0; i < n.length - 1; i++) {
+                       if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
+                       n[i + 1] == o[n[i].row + 1]) {
+                               n[i + 1] = {
+                                       text: n[i + 1],
+                                       row: n[i].row + 1
+                               };
+                               o[n[i].row + 1] = {
+                                       text: o[n[i].row + 1],
+                                       row: i + 1
+                               };
+                       }
+               }
+               
+               for (var i = n.length - 1; i > 0; i--) {
+                       if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
+                       n[i - 1] == o[n[i].row - 1]) {
+                               n[i - 1] = {
+                                       text: n[i - 1],
+                                       row: n[i].row - 1
+                               };
+                               o[n[i].row - 1] = {
+                                       text: o[n[i].row - 1],
+                                       row: i - 1
+                               };
+                       }
+               }
+               
+               return {
+                       o: o,
+                       n: n
+               };
+       }
+       
+       return function(o, n){
+               o = o.replace(/\s+$/, '');
+               n = n.replace(/\s+$/, '');
+               var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
+
+               var str = "";
+               
+               var oSpace = o.match(/\s+/g);
+               if (oSpace == null) {
+                       oSpace = [" "];
+               }
+               else {
+                       oSpace.push(" ");
+               }
+               var nSpace = n.match(/\s+/g);
+               if (nSpace == null) {
+                       nSpace = [" "];
+               }
+               else {
+                       nSpace.push(" ");
+               }
+               
+               if (out.n.length == 0) {
+                       for (var i = 0; i < out.o.length; i++) {
+                               str += '<del>' + out.o[i] + oSpace[i] + "</del>";
+                       }
+               }
+               else {
+                       if (out.n[0].text == null) {
+                               for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
+                                       str += '<del>' + out.o[n] + oSpace[n] + "</del>";
+                               }
+                       }
+                       
+                       for (var i = 0; i < out.n.length; i++) {
+                               if (out.n[i].text == null) {
+                                       str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
+                               }
+                               else {
+                                       var pre = "";
+                                       
+                                       for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
+                                               pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
+                                       }
+                                       str += " " + out.n[i].text + nSpace[i] + pre;
+                               }
+                       }
+               }
+               
+               return str;
+       }
+})();
+
+})(this);
diff --git a/lib/raphael-min.js b/lib/raphael-min.js
new file mode 100644 (file)
index 0000000..e5e7126
--- /dev/null
@@ -0,0 +1,7 @@
+/*
+ * Raphael 1.5.2 - JavaScript Vector Library
+ *
+ * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com)
+ * Licensed under the MIT (http://raphaeljs.com/license.html) license.
+ */
+(function(){function a(){if(a.is(arguments[0],G)){var b=arguments[0],d=bV[m](a,b.splice(0,3+a.is(b[0],E))),e=d.set();for(var g=0,h=b[w];g<h;g++){var i=b[g]||{};c[f](i.type)&&e[L](d[i.type]().attr(i))}return e}return bV[m](a,arguments)}a.version="1.5.2";var b=/[, ]+/,c={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},d=/\{(\d+)\}/g,e="prototype",f="hasOwnProperty",g=document,h=window,i={was:Object[e][f].call(h,"Raphael"),is:h.Raphael},j=function(){this.customAttributes={}},k,l="appendChild",m="apply",n="concat",o="createTouch"in g,p="",q=" ",r=String,s="split",t="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend orientationchange touchcancel gesturestart gesturechange gestureend"[s](q),u={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},v="join",w="length",x=r[e].toLowerCase,y=Math,z=y.max,A=y.min,B=y.abs,C=y.pow,D=y.PI,E="number",F="string",G="array",H="toString",I="fill",J=Object[e][H],K={},L="push",M=/^url\(['"]?([^\)]+?)['"]?\)$/i,N=/^\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,O={"NaN":1,Infinity:1,"-Infinity":1},P=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,Q=y.round,R="setAttribute",S=parseFloat,T=parseInt,U=" progid:DXImageTransform.Microsoft",V=r[e].toUpperCase,W={blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:"10px \"Arial\"","font-family":"\"Arial\"","font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/",opacity:1,path:"M0,0",r:0,rotation:0,rx:0,ry:0,scale:"1 1",src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",translation:"0 0",width:0,x:0,y:0},X={along:"along",blur:E,"clip-rect":"csv",cx:E,cy:E,fill:"colour","fill-opacity":E,"font-size":E,height:E,opacity:E,path:"path",r:E,rotation:"csv",rx:E,ry:E,scale:"csv",stroke:"colour","stroke-opacity":E,"stroke-width":E,translation:"csv",width:E,x:E,y:E},Y="replace",Z=/^(from|to|\d+%?)$/,$=/\s*,\s*/,_={hs:1,rg:1},ba=/,?([achlmqrstvxz]),?/gi,bb=/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,bc=/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,bd=/^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/,be=function(a,b){return a.key-b.key};a.type=h.SVGAngle||g.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML";if(a.type=="VML"){var bf=g.createElement("div"),bg;bf.innerHTML="<v:shape adj=\"1\"/>";bg=bf.firstChild;bg.style.behavior="url(#default#VML)";if(!(bg&&typeof bg.adj=="object"))return a.type=null;bf=null}a.svg=!(a.vml=a.type=="VML");j[e]=a[e];k=j[e];a._id=0;a._oid=0;a.fn={};a.is=function(a,b){b=x.call(b);if(b=="finite")return!O[f](+a);return b=="null"&&a===null||b==typeof a||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||J.call(a).slice(8,-1).toLowerCase()==b};a.angle=function(b,c,d,e,f,g){{if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return((h<0)*180+y.atan(-i/-h)*180/D+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)}};a.rad=function(a){return a%360*D/180};a.deg=function(a){return a*180/D%360};a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,G)){var e=b.length;while(e--)if(B(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(f<d)return c-f;if(f>b-d)return c-f+b}return c};function bh(){var a=[],b=0;for(;b<32;b++)a[b]=(~(~(y.random()*16)))[H](16);a[12]=4;a[16]=(a[16]&3|8)[H](16);return"r-"+a[v]("")}a.setWindow=function(a){h=a;g=h.document};var bi=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write("<body>");e.close();d=e.body}catch(a){d=createPopup().document.body}var f=d.createTextRange();bi=bm(function(a){try{d.style.color=r(a)[Y](c,p);var b=f.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b[H](16)).slice(-6)}catch(a){return"none"}})}else{var h=g.createElement("i");h.title="Raphaël Colour Picker";h.style.display="none";g.body[l](h);bi=bm(function(a){h.style.color=a;return g.defaultView.getComputedStyle(h,p).getPropertyValue("color")})}return bi(b)},bj=function(){return"hsb("+[this.h,this.s,this.b]+")"},bk=function(){return"hsl("+[this.h,this.s,this.l]+")"},bl=function(){return this.hex};a.hsb2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b){d=b.b;c=b.s;b=b.h;e=b.o}return a.hsl2rgb(b,c,d/2,e)};a.hsl2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b){d=b.l;c=b.s;b=b.h}if(b>1||c>1||d>1){b/=360;c/=100;d/=100}var f={},g=["r","g","b"],h,i,j,k,l,m;if(c){d<0.5?h=d*(1+c):h=d+c-d*c;i=2*d-h;for(var n=0;n<3;n++){j=b+1/3*-(n-1);j<0&&j++;j>1&&j--;j*6<1?f[g[n]]=i+(h-i)*6*j:j*2<1?f[g[n]]=h:j*3<2?f[g[n]]=i+(h-i)*(2/3-j)*6:f[g[n]]=i}}else f={r:d,g:d,b:d};f.r*=255;f.g*=255;f.b*=255;f.hex="#"+(16777216|f.b|f.g<<8|f.r<<16).toString(16).slice(1);a.is(e,"finite")&&(f.opacity=e);f.toString=bl;return f};a.rgb2hsb=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=f;{if(g==f)return{h:0,s:0,b:f,toString:bj};var k=f-g;i=k/f;b==f?h=(c-d)/k:c==f?h=2+(d-b)/k:h=4+(b-c)/k;h/=6;h<0&&h++;h>1&&h--}return{h:h,s:i,b:j,toString:bj}};a.rgb2hsl=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=(f+g)/2,k;if(g==f)k={h:0,s:0,l:j};else{var l=f-g;i=j<0.5?l/(f+g):l/(2-f-g);b==f?h=(c-d)/l:c==f?h=2+(d-b)/l:h=4+(b-c)/l;h/=6;h<0&&h++;h>1&&h--;k={h:h,s:i,l:j}}k.toString=bk;return k};a._path2string=function(){return this.join(",")[Y](ba,"$1")};function bm(a,b,c){function d(){var g=Array[e].slice.call(arguments,0),h=g[v]("►"),i=d.cache=d.cache||{},j=d.count=d.count||[];if(i[f](h))return c?c(i[h]):i[h];j[w]>=1000&&delete i[j.shift()];j[L](h);i[h]=a[m](b,g);return c?c(i[h]):i[h]}return d}a.getRGB=bm(function(b){if(!b||!(!((b=r(b)).indexOf("-")+1)))return{r:-1,g:-1,b:-1,hex:"none",error:1};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none"};!(_[f](b.toLowerCase().substring(0,2))||b.charAt()=="#")&&(b=bi(b));var c,d,e,g,h,i,j,k=b.match(N);if(k){if(k[2]){g=T(k[2].substring(5),16);e=T(k[2].substring(3,5),16);d=T(k[2].substring(1,3),16)}if(k[3]){g=T((i=k[3].charAt(3))+i,16);e=T((i=k[3].charAt(2))+i,16);d=T((i=k[3].charAt(1))+i,16)}if(k[4]){j=k[4][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);k[1].toLowerCase().slice(0,4)=="rgba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100)}if(k[5]){j=k[5][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,g,h)}if(k[6]){j=k[6][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsla"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,g,h)}k={r:d,g:e,b:g};k.hex="#"+(16777216|g|e<<8|d<<16).toString(16).slice(1);a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1}},a);a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||0.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=0.075;if(b.h>1){b.h=0;b.s-=0.2;b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})}return c.hex};a.getColor.reset=function(){delete this.start};a.parsePathString=bm(function(b){if(!b)return null;var c={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},d=[];a.is(b,G)&&a.is(b[0],G)&&(d=bo(b));d[w]||r(b)[Y](bb,function(a,b,e){var f=[],g=x.call(b);e[Y](bc,function(a,b){b&&f[L](+b)});if(g=="m"&&f[w]>2){d[L]([b][n](f.splice(0,2)));g="l";b=b=="m"?"l":"L"}while(f[w]>=c[g]){d[L]([b][n](f.splice(0,c[g])));if(!c[g])break}});d[H]=a._path2string;return d});a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=C(j,3)*a+C(j,2)*3*i*c+j*3*i*i*e+C(i,3)*g,l=C(j,3)*b+C(j,2)*3*i*d+j*3*i*i*f+C(i,3)*h,m=a+2*i*(c-a)+i*i*(e-2*c+a),n=b+2*i*(d-b)+i*i*(f-2*d+b),o=c+2*i*(e-c)+i*i*(g-2*e+c),p=d+2*i*(f-d)+i*i*(h-2*f+d),q=(1-i)*a+i*c,r=(1-i)*b+i*d,s=(1-i)*e+i*g,t=(1-i)*f+i*h,u=90-y.atan((m-o)/(n-p))*180/D;(m>o||n<p)&&(u+=180);return{x:k,y:l,m:{x:m,y:n},n:{x:o,y:p},start:{x:q,y:r},end:{x:s,y:t},alpha:u}};var bn=bm(function(a){if(!a)return{x:0,y:0,width:0,height:0};a=bw(a);var b=0,c=0,d=[],e=[],f;for(var g=0,h=a[w];g<h;g++){f=a[g];if(f[0]=="M"){b=f[1];c=f[2];d[L](b);e[L](c)}else{var i=bv(b,c,f[1],f[2],f[3],f[4],f[5],f[6]);d=d[n](i.min.x,i.max.x);e=e[n](i.min.y,i.max.y);b=f[5];c=f[6]}}var j=A[m](0,d),k=A[m](0,e);return{x:j,y:k,width:z[m](0,d)-j,height:z[m](0,e)-k}}),bo=function(b){var c=[];if(!a.is(b,G)||!a.is(b&&b[0],G))b=a.parsePathString(b);for(var d=0,e=b[w];d<e;d++){c[d]=[];for(var f=0,g=b[d][w];f<g;f++)c[d][f]=b[d][f]}c[H]=a._path2string;return c},bp=bm(function(b){if(!a.is(b,G)||!a.is(b&&b[0],G))b=a.parsePathString(b);var c=[],d=0,e=0,f=0,g=0,h=0;if(b[0][0]=="M"){d=b[0][1];e=b[0][2];f=d;g=e;h++;c[L](["M",d,e])}for(var i=h,j=b[w];i<j;i++){var k=c[i]=[],l=b[i];if(l[0]!=x.call(l[0])){k[0]=x.call(l[0]);switch(k[0]){case"a":k[1]=l[1];k[2]=l[2];k[3]=l[3];k[4]=l[4];k[5]=l[5];k[6]=+(l[6]-d).toFixed(3);k[7]=+(l[7]-e).toFixed(3);break;case"v":k[1]=+(l[1]-e).toFixed(3);break;case"m":f=l[1];g=l[2];default:for(var m=1,n=l[w];m<n;m++)k[m]=+(l[m]-(m%2?d:e)).toFixed(3)}}else{k=c[i]=[];if(l[0]=="m"){f=l[1]+d;g=l[2]+e}for(var o=0,p=l[w];o<p;o++)c[i][o]=l[o]}var q=c[i][w];switch(c[i][0]){case"z":d=f;e=g;break;case"h":d+=+c[i][q-1];break;case"v":e+=+c[i][q-1];break;default:d+=+c[i][q-2];e+=+c[i][q-1]}}c[H]=a._path2string;return c},0,bo),bq=bm(function(b){if(!a.is(b,G)||!a.is(b&&b[0],G))b=a.parsePathString(b);var c=[],d=0,e=0,f=0,g=0,h=0;if(b[0][0]=="M"){d=+b[0][1];e=+b[0][2];f=d;g=e;h++;c[0]=["M",d,e]}for(var i=h,j=b[w];i<j;i++){var k=c[i]=[],l=b[i];if(l[0]!=V.call(l[0])){k[0]=V.call(l[0]);switch(k[0]){case"A":k[1]=l[1];k[2]=l[2];k[3]=l[3];k[4]=l[4];k[5]=l[5];k[6]=+(l[6]+d);k[7]=+(l[7]+e);break;case"V":k[1]=+l[1]+e;break;case"H":k[1]=+l[1]+d;break;case"M":f=+l[1]+d;g=+l[2]+e;default:for(var m=1,n=l[w];m<n;m++)k[m]=+l[m]+(m%2?d:e)}}else for(var o=0,p=l[w];o<p;o++)c[i][o]=l[o];switch(k[0]){case"Z":d=f;e=g;break;case"H":d=k[1];break;case"V":e=k[1];break;case"M":f=c[i][c[i][w]-2];g=c[i][c[i][w]-1];default:d=c[i][c[i][w]-2];e=c[i][c[i][w]-1]}}c[H]=a._path2string;return c},null,bo),br=function(a,b,c,d){return[a,b,c,d,c,d]},bs=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},bt=function(a,b,c,d,e,f,g,h,i,j){var k=D*120/180,l=D/180*(+e||0),m=[],o,p=bm(function(a,b,c){var d=a*y.cos(c)-b*y.sin(c),e=a*y.sin(c)+b*y.cos(c);return{x:d,y:e}});if(j){G=j[0];H=j[1];E=j[2];F=j[3]}else{o=p(a,b,-l);a=o.x;b=o.y;o=p(h,i,-l);h=o.x;i=o.y;var q=y.cos(D/180*e),r=y.sin(D/180*e),t=(a-h)/2,u=(b-i)/2,x=t*t/(c*c)+u*u/(d*d);if(x>1){x=y.sqrt(x);c=x*c;d=x*d}var z=c*c,A=d*d,C=(f==g?-1:1)*y.sqrt(B((z*A-z*u*u-A*t*t)/(z*u*u+A*t*t))),E=C*c*u/d+(a+h)/2,F=C*-d*t/c+(b+i)/2,G=y.asin(((b-F)/d).toFixed(9)),H=y.asin(((i-F)/d).toFixed(9));G=a<E?D-G:G;H=h<E?D-H:H;G<0&&(G=D*2+G);H<0&&(H=D*2+H);g&&G>H&&(G=G-D*2);!g&&H>G&&(H=H-D*2)}var I=H-G;if(B(I)>k){var J=H,K=h,L=i;H=G+k*(g&&H>G?1:-1);h=E+c*y.cos(H);i=F+d*y.sin(H);m=bt(h,i,c,d,e,0,g,K,L,[H,J,E,F])}I=H-G;var M=y.cos(G),N=y.sin(G),O=y.cos(H),P=y.sin(H),Q=y.tan(I/4),R=4/3*c*Q,S=4/3*d*Q,T=[a,b],U=[a+R*N,b-S*M],V=[h+R*P,i-S*O],W=[h,i];U[0]=2*T[0]-U[0];U[1]=2*T[1]-U[1];{if(j)return[U,V,W][n](m);m=[U,V,W][n](m)[v]()[s](",");var X=[];for(var Y=0,Z=m[w];Y<Z;Y++)X[Y]=Y%2?p(m[Y-1],m[Y],l).y:p(m[Y],m[Y+1],l).x;return X}},bu=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:C(j,3)*a+C(j,2)*3*i*c+j*3*i*i*e+C(i,3)*g,y:C(j,3)*b+C(j,2)*3*i*d+j*3*i*i*f+C(i,3)*h}},bv=bm(function(a,b,c,d,e,f,g,h){var i=e-2*c+a-(g-2*e+c),j=2*(c-a)-2*(e-c),k=a-c,l=(-j+y.sqrt(j*j-4*i*k))/2/i,n=(-j-y.sqrt(j*j-4*i*k))/2/i,o=[b,h],p=[a,g],q;B(l)>"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}i=f-2*d+b-(h-2*f+d);j=2*(d-b)-2*(f-d);k=b-d;l=(-j+y.sqrt(j*j-4*i*k))/2/i;n=(-j-y.sqrt(j*j-4*i*k))/2/i;B(l)>"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}return{min:{x:A[m](0,p),y:A[m](0,o)},max:{x:z[m](0,p),y:z[m](0,o)}}}),bw=bm(function(a,b){var c=bq(a),d=b&&bq(b),e={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1];b.Y=a[2];break;case"A":a=["C"][n](bt[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x));d=b.y+(b.y-(b.by||b.y));a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x));b.qy=b.y+(b.y-(b.qy||b.y));a=["C"][n](bs(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1];b.qy=a[2];a=["C"][n](bs(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](br(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](br(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](br(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](br(b.x,b.y,b.X,b.Y));break}return a},h=function(a,b){if(a[b][w]>7){a[b].shift();var e=a[b];while(e[w])a.splice(b++,0,["C"][n](e.splice(0,6)));a.splice(b,1);k=z(c[w],d&&d[w]||0)}},i=function(a,b,e,f,g){if(a&&b&&a[g][0]=="M"&&b[g][0]!="M"){b.splice(g,0,["M",f.x,f.y]);e.bx=0;e.by=0;e.x=a[g][1];e.y=a[g][2];k=z(c[w],d&&d[w]||0)}};for(var j=0,k=z(c[w],d&&d[w]||0);j<k;j++){c[j]=g(c[j],e);h(c,j);d&&(d[j]=g(d[j],f));d&&h(d,j);i(c,d,e,f,j);i(d,c,f,e,j);var l=c[j],o=d&&d[j],p=l[w],q=d&&o[w];e.x=l[p-2];e.y=l[p-1];e.bx=S(l[p-4])||e.x;e.by=S(l[p-3])||e.y;f.bx=d&&(S(o[q-4])||f.x);f.by=d&&(S(o[q-3])||f.y);f.x=d&&o[q-2];f.y=d&&o[q-1]}return d?[c,d]:c},null,bo),bx=bm(function(b){var c=[];for(var d=0,e=b[w];d<e;d++){var f={},g=b[d].match(/^([^:]*):?([\d\.]*)/);f.color=a.getRGB(g[1]);if(f.color.error)return null;f.color=f.color.hex;g[2]&&(f.offset=g[2]+"%");c[L](f)}for(d=1,e=c[w]-1;d<e;d++){if(!c[d].offset){var h=S(c[d-1].offset||0),i=0;for(var j=d+1;j<e;j++){if(c[j].offset){i=c[j].offset;break}}if(!i){i=100;j=e}i=S(i);var k=(i-h)/(j-d+1);for(;d<j;d++){h+=k;c[d].offset=h+"%"}}}return c}),by=function(b,c,d,e){var f;if(a.is(b,F)||a.is(b,"object")){f=a.is(b,F)?g.getElementById(b):b;if(f.tagName)return c==null?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:c,height:d}}else return{container:1,x:b,y:c,width:d,height:e}},bz=function(a,b){var c=this;for(var d in b){if(b[f](d)&&!(d in a))switch(typeof b[d]){case"function":(function(b){a[d]=a===c?b:function(){return b[m](c,arguments)}})(b[d]);break;case"object":a[d]=a[d]||{};bz.call(this,a[d],b[d]);break;default:a[d]=b[d];break}}},bA=function(a,b){a==b.top&&(b.top=a.prev);a==b.bottom&&(b.bottom=a.next);a.next&&(a.next.prev=a.prev);a.prev&&(a.prev.next=a.next)},bB=function(a,b){if(b.top===a)return;bA(a,b);a.next=null;a.prev=b.top;b.top.next=a;b.top=a},bC=function(a,b){if(b.bottom===a)return;bA(a,b);a.next=b.bottom;a.prev=null;b.bottom.prev=a;b.bottom=a},bD=function(a,b,c){bA(a,c);b==c.top&&(c.top=a);b.next&&(b.next.prev=a);a.next=b.next;a.prev=b;b.next=a},bE=function(a,b,c){bA(a,c);b==c.bottom&&(c.bottom=a);b.prev&&(b.prev.next=a);a.prev=b.prev;b.prev=a;a.next=b},bF=function(a){return function(){throw new Error("Raphaël: you are calling to method “"+a+"” of removed object")}};a.pathToRelative=bp;if(a.svg){k.svgns="http://www.w3.org/2000/svg";k.xlink="http://www.w3.org/1999/xlink";Q=function(a){return+a+(~(~a)===a)*0.5};var bG=function(a,b){if(b)for(var c in b)b[f](c)&&a[R](c,r(b[c]));else{a=g.createElementNS(k.svgns,a);a.style.webkitTapHighlightColor="rgba(0,0,0,0)";return a}};a[H]=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var bH=function(a,b){var c=bG("path");b.canvas&&b.canvas[l](c);var d=new bN(c,b);d.type="path";bK(d,{fill:"none",stroke:"#000",path:a});return d},bI=function(a,b,c){var d="linear",e=0.5,f=0.5,h=a.style;b=r(b)[Y](bd,function(a,b,c){d="radial";if(b&&c){e=S(b);f=S(c);var g=(f>0.5)*2-1;C(e-0.5,2)+C(f-0.5,2)>0.25&&(f=y.sqrt(0.25-C(e-0.5,2))*g+0.5)&&f!=0.5&&(f=f.toFixed(5)-0.00001*g)}return p});b=b[s](/\s*\-\s*/);if(d=="linear"){var i=b.shift();i=-S(i);if(isNaN(i))return null;var j=[0,0,y.cos(i*D/180),y.sin(i*D/180)],k=1/(z(B(j[2]),B(j[3]))||1);j[2]*=k;j[3]*=k;if(j[2]<0){j[0]=-j[2];j[2]=0}if(j[3]<0){j[1]=-j[3];j[3]=0}}var m=bx(b);if(!m)return null;var n=a.getAttribute(I);n=n.match(/^url\(#(.*)\)$/);n&&c.defs.removeChild(g.getElementById(n[1]));var o=bG(d+"Gradient");o.id=bh();bG(o,d=="radial"?{fx:e,fy:f}:{x1:j[0],y1:j[1],x2:j[2],y2:j[3]});c.defs[l](o);for(var q=0,t=m[w];q<t;q++){var u=bG("stop");bG(u,{offset:m[q].offset?m[q].offset:q?"100%":"0%","stop-color":m[q].color||"#fff"});o[l](u)}bG(a,{fill:"url(#"+o.id+")",opacity:1,"fill-opacity":1});h.fill=p;h.opacity=1;h.fillOpacity=1;return 1},bJ=function(b){var c=b.getBBox();bG(b.pattern,{patternTransform:a.format("translate({0},{1})",c.x,c.y)})},bK=function(c,d){var e={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},h=c.node,i=c.attrs,j=c.rotate(),k=function(a,b){b=e[x.call(b)];if(b){var c=a.attrs["stroke-width"]||"1",f=({round:c,square:c,butt:0})[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],i=b[w];while(i--)g[i]=b[i]*c+(i%2?1:-1)*f;bG(h,{"stroke-dasharray":g[v](",")})}};d[f]("rotation")&&(j=d.rotation);var m=r(j)[s](b);if(m.length-1){m[1]=+m[1];m[2]=+m[2]}else m=null;S(j)&&c.rotate(0,true);for(var n in d){if(d[f](n)){if(!W[f](n))continue;var o=d[n];i[n]=o;switch(n){case"blur":c.blur(o);break;case"rotation":c.rotate(o,true);break;case"href":case"title":case"target":var t=h.parentNode;if(x.call(t.tagName)!="a"){var u=bG("a");t.insertBefore(u,h);u[l](h);t=u}n=="target"&&o=="blank"?t.setAttributeNS(c.paper.xlink,"show","new"):t.setAttributeNS(c.paper.xlink,n,o);break;case"cursor":h.style.cursor=o;break;case"clip-rect":var y=r(o)[s](b);if(y[w]==4){c.clip&&c.clip.parentNode.parentNode.removeChild(c.clip.parentNode);var z=bG("clipPath"),A=bG("rect");z.id=bh();bG(A,{x:y[0],y:y[1],width:y[2],height:y[3]});z[l](A);c.paper.defs[l](z);bG(h,{"clip-path":"url(#"+z.id+")"});c.clip=A}if(!o){var B=g.getElementById(h.getAttribute("clip-path")[Y](/(^url\(#|\)$)/g,p));B&&B.parentNode.removeChild(B);bG(h,{"clip-path":p});delete c.clip}break;case"path":c.type=="path"&&bG(h,{d:o?i.path=bq(o):"M0,0"});break;case"width":h[R](n,o);if(i.fx){n="x";o=i.x}else break;case"x":i.fx&&(o=-i.x-(i.width||0));case"rx":if(n=="rx"&&c.type=="rect")break;case"cx":m&&(n=="x"||n=="cx")&&(m[1]+=o-i[n]);h[R](n,o);c.pattern&&bJ(c);break;case"height":h[R](n,o);if(i.fy){n="y";o=i.y}else break;case"y":i.fy&&(o=-i.y-(i.height||0));case"ry":if(n=="ry"&&c.type=="rect")break;case"cy":m&&(n=="y"||n=="cy")&&(m[2]+=o-i[n]);h[R](n,o);c.pattern&&bJ(c);break;case"r":c.type=="rect"?bG(h,{rx:o,ry:o}):h[R](n,o);break;case"src":c.type=="image"&&h.setAttributeNS(c.paper.xlink,"href",o);break;case"stroke-width":h.style.strokeWidth=o;h[R](n,o);i["stroke-dasharray"]&&k(c,i["stroke-dasharray"]);break;case"stroke-dasharray":k(c,o);break;case"translation":var C=r(o)[s](b);C[0]=+C[0]||0;C[1]=+C[1]||0;if(m){m[1]+=C[0];m[2]+=C[1]}cz.call(c,C[0],C[1]);break;case"scale":C=r(o)[s](b);c.scale(+C[0]||1,+C[1]||+C[0]||1,isNaN(S(C[2]))?null:+C[2],isNaN(S(C[3]))?null:+C[3]);break;case I:var D=r(o).match(M);if(D){z=bG("pattern");var E=bG("image");z.id=bh();bG(z,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1});bG(E,{x:0,y:0});E.setAttributeNS(c.paper.xlink,"href",D[1]);z[l](E);var F=g.createElement("img");F.style.cssText="position:absolute;left:-9999em;top-9999em";F.onload=function(){bG(z,{width:this.offsetWidth,height:this.offsetHeight});bG(E,{width:this.offsetWidth,height:this.offsetHeight});g.body.removeChild(this);c.paper.safari()};g.body[l](F);F.src=D[1];c.paper.defs[l](z);h.style.fill="url(#"+z.id+")";bG(h,{fill:"url(#"+z.id+")"});c.pattern=z;c.pattern&&bJ(c);break}var G=a.getRGB(o);if(G.error)if((({circle:1,ellipse:1})[f](c.type)||r(o).charAt()!="r")&&bI(h,o,c.paper)){i.gradient=o;i.fill="none";break}else{delete d.gradient;delete i.gradient;!a.is(i.opacity,"undefined")&&a.is(d.opacity,"undefined")&&bG(h,{opacity:i.opacity});!a.is(i["fill-opacity"],"undefined")&&a.is(d["fill-opacity"],"undefined")&&bG(h,{"fill-opacity":i["fill-opacity"]})}G[f]("opacity")&&bG(h,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(o);h[R](n,G.hex);n=="stroke"&&G[f]("opacity")&&bG(h,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity});break;case"gradient":(({circle:1,ellipse:1})[f](c.type)||r(o).charAt()!="r")&&bI(h,o,c.paper);break;case"opacity":i.gradient&&!i[f]("stroke-opacity")&&bG(h,{"stroke-opacity":o>1?o/100:o});case"fill-opacity":if(i.gradient){var H=g.getElementById(h.getAttribute(I)[Y](/^url\(#|\)$/g,p));if(H){var J=H.getElementsByTagName("stop");J[J[w]-1][R]("stop-opacity",o)}break}default:n=="font-size"&&(o=T(o,10)+"px");var K=n[Y](/(\-.)/g,function(a){return V.call(a.substring(1))});h.style[K]=o;h[R](n,o);break}}}bM(c,d);m?c.rotate(m.join(q)):S(j)&&c.rotate(j,true)},bL=1.2,bM=function(b,c){if(b.type!="text"||!(c[f]("text")||c[f]("font")||c[f]("font-size")||c[f]("x")||c[f]("y")))return;var d=b.attrs,e=b.node,h=e.firstChild?T(g.defaultView.getComputedStyle(e.firstChild,p).getPropertyValue("font-size"),10):10;if(c[f]("text")){d.text=c.text;while(e.firstChild)e.removeChild(e.firstChild);var i=r(c.text)[s]("\n");for(var j=0,k=i[w];j<k;j++)if(i[j]){var m=bG("tspan");j&&bG(m,{dy:h*bL,x:d.x});m[l](g.createTextNode(i[j]));e[l](m)}}else{i=e.getElementsByTagName("tspan");for(j=0,k=i[w];j<k;j++)j&&bG(i[j],{dy:h*bL,x:d.x})}bG(e,{y:d.y});var n=b.getBBox(),o=d.y-(n.y+n.height/2);o&&a.is(o,"finite")&&bG(e,{y:d.y+o})},bN=function(b,c){var d=0,e=0;this[0]=b;this.id=a._oid++;this.node=b;b.raphael=this;this.paper=c;this.attrs=this.attrs||{};this.transformations=[];this._={tx:0,ty:0,rt:{deg:0,cx:0,cy:0},sx:1,sy:1};!c.bottom&&(c.bottom=this);this.prev=c.top;c.top&&(c.top.next=this);c.top=this;this.next=null},bO=bN[e];bN[e].rotate=function(c,d,e){if(this.removed)return this;if(c==null){if(this._.rt.cx)return[this._.rt.deg,this._.rt.cx,this._.rt.cy][v](q);return this._.rt.deg}var f=this.getBBox();c=r(c)[s](b);if(c[w]-1){d=S(c[1]);e=S(c[2])}c=S(c[0]);d!=null&&d!==false?this._.rt.deg=c:this._.rt.deg+=c;e==null&&(d=null);this._.rt.cx=d;this._.rt.cy=e;d=d==null?f.x+f.width/2:d;e=e==null?f.y+f.height/2:e;if(this._.rt.deg){this.transformations[0]=a.format("rotate({0} {1} {2})",this._.rt.deg,d,e);this.clip&&bG(this.clip,{transform:a.format("rotate({0} {1} {2})",-this._.rt.deg,d,e)})}else{this.transformations[0]=p;this.clip&&bG(this.clip,{transform:p})}bG(this.node,{transform:this.transformations[v](q)});return this};bN[e].hide=function(){!this.removed&&(this.node.style.display="none");return this};bN[e].show=function(){!this.removed&&(this.node.style.display="");return this};bN[e].remove=function(){if(this.removed)return;bA(this,this.paper);this.node.parentNode.removeChild(this.node);for(var a in this)delete this[a];this.removed=true};bN[e].getBBox=function(){if(this.removed)return this;if(this.type=="path")return bn(this.attrs.path);if(this.node.style.display=="none"){this.show();var a=true}var b={};try{b=this.node.getBBox()}catch(a){}finally{b=b||{}}if(this.type=="text"){b={x:b.x,y:Infinity,width:0,height:0};for(var c=0,d=this.node.getNumberOfChars();c<d;c++){var e=this.node.getExtentOfChar(c);e.y<b.y&&(b.y=e.y);e.y+e.height-b.y>b.height&&(b.height=e.y+e.height-b.y);e.x+e.width-b.x>b.width&&(b.width=e.x+e.width-b.x)}}a&&this.hide();return b};bN[e].attr=function(b,c){if(this.removed)return this;if(b==null){var d={};for(var e in this.attrs)this.attrs[f](e)&&(d[e]=this.attrs[e]);this._.rt.deg&&(d.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(d.scale=this.scale());d.gradient&&d.fill=="none"&&(d.fill=d.gradient)&&delete d.gradient;return d}if(c==null&&a.is(b,F)){if(b=="translation")return cz.call(this);if(b=="rotation")return this.rotate();if(b=="scale")return this.scale();if(b==I&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[b]}if(c==null&&a.is(b,G)){var g={};for(var h=0,i=b.length;h<i;h++)g[b[h]]=this.attr(b[h]);return g}if(c!=null){var j={};j[b]=c}else b!=null&&a.is(b,"object")&&(j=b);for(var k in this.paper.customAttributes)if(this.paper.customAttributes[f](k)&&j[f](k)&&a.is(this.paper.customAttributes[k],"function")){var l=this.paper.customAttributes[k].apply(this,[][n](j[k]));this.attrs[k]=j[k];for(var m in l)l[f](m)&&(j[m]=l[m])}bK(this,j);return this};bN[e].toFront=function(){if(this.removed)return this;this.node.parentNode[l](this.node);var a=this.paper;a.top!=this&&bB(this,a);return this};bN[e].toBack=function(){if(this.removed)return this;if(this.node.parentNode.firstChild!=this.node){this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild);bC(this,this.paper);var a=this.paper}return this};bN[e].insertAfter=function(a){if(this.removed)return this;var b=a.node||a[a.length-1].node;b.nextSibling?b.parentNode.insertBefore(this.node,b.nextSibling):b.parentNode[l](this.node);bD(this,a,this.paper);return this};bN[e].insertBefore=function(a){if(this.removed)return this;var b=a.node||a[0].node;b.parentNode.insertBefore(this.node,b);bE(this,a,this.paper);return this};bN[e].blur=function(a){var b=this;if(+a!==0){var c=bG("filter"),d=bG("feGaussianBlur");b.attrs.blur=a;c.id=bh();bG(d,{stdDeviation:+a||1.5});c.appendChild(d);b.paper.defs.appendChild(c);b._blur=c;bG(b.node,{filter:"url(#"+c.id+")"})}else{if(b._blur){b._blur.parentNode.removeChild(b._blur);delete b._blur;delete b.attrs.blur}b.node.removeAttribute("filter")}};var bP=function(a,b,c,d){var e=bG("circle");a.canvas&&a.canvas[l](e);var f=new bN(e,a);f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"};f.type="circle";bG(e,f.attrs);return f},bQ=function(a,b,c,d,e,f){var g=bG("rect");a.canvas&&a.canvas[l](g);var h=new bN(g,a);h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"};h.type="rect";bG(g,h.attrs);return h},bR=function(a,b,c,d,e){var f=bG("ellipse");a.canvas&&a.canvas[l](f);var g=new bN(f,a);g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"};g.type="ellipse";bG(f,g.attrs);return g},bS=function(a,b,c,d,e,f){var g=bG("image");bG(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"});g.setAttributeNS(a.xlink,"href",b);a.canvas&&a.canvas[l](g);var h=new bN(g,a);h.attrs={x:c,y:d,width:e,height:f,src:b};h.type="image";return h},bT=function(a,b,c,d){var e=bG("text");bG(e,{x:b,y:c,"text-anchor":"middle"});a.canvas&&a.canvas[l](e);var f=new bN(e,a);f.attrs={x:b,y:c,"text-anchor":"middle",text:d,font:W.font,stroke:"none",fill:"#000"};f.type="text";bK(f,f.attrs);return f},bU=function(a,b){this.width=a||this.width;this.height=b||this.height;this.canvas[R]("width",this.width);this.canvas[R]("height",this.height);return this},bV=function(){var b=by[m](0,arguments),c=b&&b.container,d=b.x,e=b.y,f=b.width,h=b.height;if(!c)throw new Error("SVG container not found.");var i=bG("svg");d=d||0;e=e||0;f=f||512;h=h||342;bG(i,{xmlns:"http://www.w3.org/2000/svg",version:1.1,width:f,height:h});if(c==1){i.style.cssText="position:absolute;left:"+d+"px;top:"+e+"px";g.body[l](i)}else c.firstChild?c.insertBefore(i,c.firstChild):c[l](i);c=new j;c.width=f;c.height=h;c.canvas=i;bz.call(c,c,a.fn);c.clear();return c};k.clear=function(){var a=this.canvas;while(a.firstChild)a.removeChild(a.firstChild);this.bottom=this.top=null;(this.desc=bG("desc"))[l](g.createTextNode("Created with Raphaël"));a[l](this.desc);a[l](this.defs=bG("defs"))};k.remove=function(){this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=bF(a)}}if(a.vml){var bW={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},bX=/([clmz]),?([^clmz]*)/gi,bY=/ progid:\S+Blur\([^\)]+\)/g,bZ=/-?[^,\s-]+/g,b$=1000+q+1000,b_=10,ca={path:1,rect:1},cb=function(a){var b=/[ahqstv]/ig,c=bq;r(a).match(b)&&(c=bw);b=/[clmz]/g;if(c==bq&&!r(a).match(b)){var d=r(a)[Y](bX,function(a,b,c){var d=[],e=x.call(b)=="m",f=bW[b];c[Y](bZ,function(a){if(e&&d[w]==2){f+=d+bW[b=="m"?"l":"L"];d=[]}d[L](Q(a*b_))});return f+d});return d}var e=c(a),f,g;d=[];for(var h=0,i=e[w];h<i;h++){f=e[h];g=x.call(e[h][0]);g=="z"&&(g="x");for(var j=1,k=f[w];j<k;j++)g+=Q(f[j]*b_)+(j!=k-1?",":p);d[L](g)}return d[v](q)};a[H]=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};bH=function(a,b){var c=cd("group");c.style.cssText="position:absolute;left:0;top:0;width:"+b.width+"px;height:"+b.height+"px";c.coordsize=b.coordsize;c.coordorigin=b.coordorigin;var d=cd("shape"),e=d.style;e.width=b.width+"px";e.height=b.height+"px";d.coordsize=b$;d.coordorigin=b.coordorigin;c[l](d);var f=new bN(d,c,b),g={fill:"none",stroke:"#000"};a&&(g.path=a);f.type="path";f.path=[];f.Path=p;bK(f,g);b.canvas[l](c);return f};bK=function(c,d){c.attrs=c.attrs||{};var e=c.node,h=c.attrs,i=e.style,j,k=(d.x!=h.x||d.y!=h.y||d.width!=h.width||d.height!=h.height||d.r!=h.r)&&c.type=="rect",m=c;for(var n in d)d[f](n)&&(h[n]=d[n]);if(k){h.path=cc(h.x,h.y,h.width,h.height,h.r);c.X=h.x;c.Y=h.y;c.W=h.width;c.H=h.height}d.href&&(e.href=d.href);d.title&&(e.title=d.title);d.target&&(e.target=d.target);d.cursor&&(i.cursor=d.cursor);"blur"in d&&c.blur(d.blur);if(d.path&&c.type=="path"||k)e.path=cb(h.path);d.rotation!=null&&c.rotate(d.rotation,true);if(d.translation){j=r(d.translation)[s](b);cz.call(c,j[0],j[1]);if(c._.rt.cx!=null){c._.rt.cx+=+j[0];c._.rt.cy+=+j[1];c.setBox(c.attrs,j[0],j[1])}}if(d.scale){j=r(d.scale)[s](b);c.scale(+j[0]||1,+j[1]||+j[0]||1,+j[2]||null,+j[3]||null)}if("clip-rect"in d){var o=r(d["clip-rect"])[s](b);if(o[w]==4){o[2]=+o[2]+ +o[0];o[3]=+o[3]+ +o[1];var q=e.clipRect||g.createElement("div"),t=q.style,u=e.parentNode;t.clip=a.format("rect({1}px {2}px {3}px {0}px)",o);if(!e.clipRect){t.position="absolute";t.top=0;t.left=0;t.width=c.paper.width+"px";t.height=c.paper.height+"px";u.parentNode.insertBefore(q,u);q[l](u);e.clipRect=q}}d["clip-rect"]||e.clipRect&&(e.clipRect.style.clip=p)}c.type=="image"&&d.src&&(e.src=d.src);if(c.type=="image"&&d.opacity){e.filterOpacity=U+".Alpha(opacity="+d.opacity*100+")";i.filter=(e.filterMatrix||p)+(e.filterOpacity||p)}d.font&&(i.font=d.font);d["font-family"]&&(i.fontFamily="\""+d["font-family"][s](",")[0][Y](/^['"]+|['"]+$/g,p)+"\"");d["font-size"]&&(i.fontSize=d["font-size"]);d["font-weight"]&&(i.fontWeight=d["font-weight"]);d["font-style"]&&(i.fontStyle=d["font-style"]);if(d.opacity!=null||d["stroke-width"]!=null||d.fill!=null||d.stroke!=null||d["stroke-width"]!=null||d["stroke-opacity"]!=null||d["fill-opacity"]!=null||d["stroke-dasharray"]!=null||d["stroke-miterlimit"]!=null||d["stroke-linejoin"]!=null||d["stroke-linecap"]!=null){e=c.shape||e;var v=e.getElementsByTagName(I)&&e.getElementsByTagName(I)[0],x=false;!v&&(x=v=cd(I));if("fill-opacity"in d||"opacity"in d){var y=((+h["fill-opacity"]+1||2)-1)*((+h.opacity+1||2)-1)*((+a.getRGB(d.fill).o+1||2)-1);y=A(z(y,0),1);v.opacity=y}d.fill&&(v.on=true);if(v.on==null||d.fill=="none")v.on=false;if(v.on&&d.fill){var B=d.fill.match(M);if(B){v.src=B[1];v.type="tile"}else{v.color=a.getRGB(d.fill).hex;v.src=p;v.type="solid";if(a.getRGB(d.fill).error&&(m.type in{circle:1,ellipse:1}||r(d.fill).charAt()!="r")&&bI(m,d.fill)){h.fill="none";h.gradient=d.fill}}}x&&e[l](v);var C=e.getElementsByTagName("stroke")&&e.getElementsByTagName("stroke")[0],D=false;!C&&(D=C=cd("stroke"));if(d.stroke&&d.stroke!="none"||d["stroke-width"]||d["stroke-opacity"]!=null||d["stroke-dasharray"]||d["stroke-miterlimit"]||d["stroke-linejoin"]||d["stroke-linecap"])C.on=true;(d.stroke=="none"||C.on==null||d.stroke==0||d["stroke-width"]==0)&&(C.on=false);var E=a.getRGB(d.stroke);C.on&&d.stroke&&(C.color=E.hex);y=((+h["stroke-opacity"]+1||2)-1)*((+h.opacity+1||2)-1)*((+E.o+1||2)-1);var F=(S(d["stroke-width"])||1)*0.75;y=A(z(y,0),1);d["stroke-width"]==null&&(F=h["stroke-width"]);d["stroke-width"]&&(C.weight=F);F&&F<1&&(y*=F)&&(C.weight=1);C.opacity=y;d["stroke-linejoin"]&&(C.joinstyle=d["stroke-linejoin"]||"miter");C.miterlimit=d["stroke-miterlimit"]||8;d["stroke-linecap"]&&(C.endcap=d["stroke-linecap"]=="butt"?"flat":d["stroke-linecap"]=="square"?"square":"round");if(d["stroke-dasharray"]){var G={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};C.dashstyle=G[f](d["stroke-dasharray"])?G[d["stroke-dasharray"]]:p}D&&e[l](C)}if(m.type=="text"){i=m.paper.span.style;h.font&&(i.font=h.font);h["font-family"]&&(i.fontFamily=h["font-family"]);h["font-size"]&&(i.fontSize=h["font-size"]);h["font-weight"]&&(i.fontWeight=h["font-weight"]);h["font-style"]&&(i.fontStyle=h["font-style"]);m.node.string&&(m.paper.span.innerHTML=r(m.node.string)[Y](/</g,"&#60;")[Y](/&/g,"&#38;")[Y](/\n/g,"<br>"));m.W=h.w=m.paper.span.offsetWidth;m.H=h.h=m.paper.span.offsetHeight;m.X=h.x;m.Y=h.y+Q(m.H/2);switch(h["text-anchor"]){case"start":m.node.style["v-text-align"]="left";m.bbx=Q(m.W/2);break;case"end":m.node.style["v-text-align"]="right";m.bbx=-Q(m.W/2);break;default:m.node.style["v-text-align"]="center";break}}};bI=function(a,b){a.attrs=a.attrs||{};var c=a.attrs,d,e="linear",f=".5 .5";a.attrs.gradient=b;b=r(b)[Y](bd,function(a,b,c){e="radial";if(b&&c){b=S(b);c=S(c);C(b-0.5,2)+C(c-0.5,2)>0.25&&(c=y.sqrt(0.25-C(b-0.5,2))*((c>0.5)*2-1)+0.5);f=b+q+c}return p});b=b[s](/\s*\-\s*/);if(e=="linear"){var g=b.shift();g=-S(g);if(isNaN(g))return null}var h=bx(b);if(!h)return null;a=a.shape||a.node;d=a.getElementsByTagName(I)[0]||cd(I);!d.parentNode&&a.appendChild(d);if(h[w]){d.on=true;d.method="none";d.color=h[0].color;d.color2=h[h[w]-1].color;var i=[];for(var j=0,k=h[w];j<k;j++)h[j].offset&&i[L](h[j].offset+q+h[j].color);d.colors&&(d.colors.value=i[w]?i[v]():"0% "+d.color);if(e=="radial"){d.type="gradientradial";d.focus="100%";d.focussize=f;d.focusposition=f}else{d.type="gradient";d.angle=(270-g)%360}}return 1};bN=function(b,c,d){var e=0,f=0,g=0,h=1;this[0]=b;this.id=a._oid++;this.node=b;b.raphael=this;this.X=0;this.Y=0;this.attrs={};this.Group=c;this.paper=d;this._={tx:0,ty:0,rt:{deg:0},sx:1,sy:1};!d.bottom&&(d.bottom=this);this.prev=d.top;d.top&&(d.top.next=this);d.top=this;this.next=null};bO=bN[e];bO.rotate=function(a,c,d){if(this.removed)return this;if(a==null){if(this._.rt.cx)return[this._.rt.deg,this._.rt.cx,this._.rt.cy][v](q);return this._.rt.deg}a=r(a)[s](b);if(a[w]-1){c=S(a[1]);d=S(a[2])}a=S(a[0]);c!=null?this._.rt.deg=a:this._.rt.deg+=a;d==null&&(c=null);this._.rt.cx=c;this._.rt.cy=d;this.setBox(this.attrs,c,d);this.Group.style.rotation=this._.rt.deg;return this};bO.setBox=function(a,b,c){if(this.removed)return this;var d=this.Group.style,e=this.shape&&this.shape.style||this.node.style;a=a||{};for(var g in a)a[f](g)&&(this.attrs[g]=a[g]);b=b||this._.rt.cx;c=c||this._.rt.cy;var h=this.attrs,i,j,k,l;switch(this.type){case"circle":i=h.cx-h.r;j=h.cy-h.r;k=l=h.r*2;break;case"ellipse":i=h.cx-h.rx;j=h.cy-h.ry;k=h.rx*2;l=h.ry*2;break;case"image":i=+h.x;j=+h.y;k=h.width||0;l=h.height||0;break;case"text":this.textpath.v=["m",Q(h.x),", ",Q(h.y-2),"l",Q(h.x)+1,", ",Q(h.y-2)][v](p);i=h.x-Q(this.W/2);j=h.y-this.H/2;k=this.W;l=this.H;break;case"rect":case"path":if(this.attrs.path){var m=bn(this.attrs.path);i=m.x;j=m.y;k=m.width;l=m.height}else{i=0;j=0;k=this.paper.width;l=this.paper.height}break;default:i=0;j=0;k=this.paper.width;l=this.paper.height;break}b=b==null?i+k/2:b;c=c==null?j+l/2:c;var n=b-this.paper.width/2,o=c-this.paper.height/2,q;d.left!=(q=n+"px")&&(d.left=q);d.top!=(q=o+"px")&&(d.top=q);this.X=ca[f](this.type)?-n:i;this.Y=ca[f](this.type)?-o:j;this.W=k;this.H=l;if(ca[f](this.type)){e.left!=(q=-n*b_+"px")&&(e.left=q);e.top!=(q=-o*b_+"px")&&(e.top=q)}else if(this.type=="text"){e.left!=(q=-n+"px")&&(e.left=q);e.top!=(q=-o+"px")&&(e.top=q)}else{d.width!=(q=this.paper.width+"px")&&(d.width=q);d.height!=(q=this.paper.height+"px")&&(d.height=q);e.left!=(q=i-n+"px")&&(e.left=q);e.top!=(q=j-o+"px")&&(e.top=q);e.width!=(q=k+"px")&&(e.width=q);e.height!=(q=l+"px")&&(e.height=q)}};bO.hide=function(){!this.removed&&(this.Group.style.display="none");return this};bO.show=function(){!this.removed&&(this.Group.style.display="block");return this};bO.getBBox=function(){if(this.removed)return this;if(ca[f](this.type))return bn(this.attrs.path);return{x:this.X+(this.bbx||0),y:this.Y,width:this.W,height:this.H}};bO.remove=function(){if(this.removed)return;bA(this,this.paper);this.node.parentNode.removeChild(this.node);this.Group.parentNode.removeChild(this.Group);this.shape&&this.shape.parentNode.removeChild(this.shape);for(var a in this)delete this[a];this.removed=true};bO.attr=function(b,c){if(this.removed)return this;if(b==null){var d={};for(var e in this.attrs)this.attrs[f](e)&&(d[e]=this.attrs[e]);this._.rt.deg&&(d.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(d.scale=this.scale());d.gradient&&d.fill=="none"&&(d.fill=d.gradient)&&delete d.gradient;return d}if(c==null&&a.is(b,"string")){if(b=="translation")return cz.call(this);if(b=="rotation")return this.rotate();if(b=="scale")return this.scale();if(b==I&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[b]}if(this.attrs&&c==null&&a.is(b,G)){var g,h={};for(e=0,g=b[w];e<g;e++)h[b[e]]=this.attr(b[e]);return h}var i;if(c!=null){i={};i[b]=c}c==null&&a.is(b,"object")&&(i=b);if(i){for(var j in this.paper.customAttributes)if(this.paper.customAttributes[f](j)&&i[f](j)&&a.is(this.paper.customAttributes[j],"function")){var k=this.paper.customAttributes[j].apply(this,[][n](i[j]));this.attrs[j]=i[j];for(var l in k)k[f](l)&&(i[l]=k[l])}i.text&&this.type=="text"&&(this.node.string=i.text);bK(this,i);i.gradient&&(({circle:1,ellipse:1})[f](this.type)||r(i.gradient).charAt()!="r")&&bI(this,i.gradient);(!ca[f](this.type)||this._.rt.deg)&&this.setBox(this.attrs)}return this};bO.toFront=function(){!this.removed&&this.Group.parentNode[l](this.Group);this.paper.top!=this&&bB(this,this.paper);return this};bO.toBack=function(){if(this.removed)return this;if(this.Group.parentNode.firstChild!=this.Group){this.Group.parentNode.insertBefore(this.Group,this.Group.parentNode.firstChild);bC(this,this.paper)}return this};bO.insertAfter=function(a){if(this.removed)return this;a.constructor==cC&&(a=a[a.length-1]);a.Group.nextSibling?a.Group.parentNode.insertBefore(this.Group,a.Group.nextSibling):a.Group.parentNode[l](this.Group);bD(this,a,this.paper);return this};bO.insertBefore=function(a){if(this.removed)return this;a.constructor==cC&&(a=a[0]);a.Group.parentNode.insertBefore(this.Group,a.Group);bE(this,a,this.paper);return this};bO.blur=function(b){var c=this.node.runtimeStyle,d=c.filter;d=d.replace(bY,p);if(+b!==0){this.attrs.blur=b;c.filter=d+q+U+".Blur(pixelradius="+(+b||1.5)+")";c.margin=a.format("-{0}px 0 0 -{0}px",Q(+b||1.5))}else{c.filter=d;c.margin=0;delete this.attrs.blur}};bP=function(a,b,c,d){var e=cd("group"),f=cd("oval"),g=f.style;e.style.cssText="position:absolute;left:0;top:0;width:"+a.width+"px;height:"+a.height+"px";e.coordsize=b$;e.coordorigin=a.coordorigin;e[l](f);var h=new bN(f,e,a);h.type="circle";bK(h,{stroke:"#000",fill:"none"});h.attrs.cx=b;h.attrs.cy=c;h.attrs.r=d;h.setBox({x:b-d,y:c-d,width:d*2,height:d*2});a.canvas[l](e);return h};function cc(b,c,d,e,f){return f?a.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z",b+f,c,d-f*2,f,-f,e-f*2,f*2-d,f*2-e):a.format("M{0},{1}l{2},0,0,{3},{4},0z",b,c,d,e,-d)}bQ=function(a,b,c,d,e,f){var g=cc(b,c,d,e,f),h=a.path(g),i=h.attrs;h.X=i.x=b;h.Y=i.y=c;h.W=i.width=d;h.H=i.height=e;i.r=f;i.path=g;h.type="rect";return h};bR=function(a,b,c,d,e){var f=cd("group"),g=cd("oval"),h=g.style;f.style.cssText="position:absolute;left:0;top:0;width:"+a.width+"px;height:"+a.height+"px";f.coordsize=b$;f.coordorigin=a.coordorigin;f[l](g);var i=new bN(g,f,a);i.type="ellipse";bK(i,{stroke:"#000"});i.attrs.cx=b;i.attrs.cy=c;i.attrs.rx=d;i.attrs.ry=e;i.setBox({x:b-d,y:c-e,width:d*2,height:e*2});a.canvas[l](f);return i};bS=function(a,b,c,d,e,f){var g=cd("group"),h=cd("image");g.style.cssText="position:absolute;left:0;top:0;width:"+a.width+"px;height:"+a.height+"px";g.coordsize=b$;g.coordorigin=a.coordorigin;h.src=b;g[l](h);var i=new bN(h,g,a);i.type="image";i.attrs.src=b;i.attrs.x=c;i.attrs.y=d;i.attrs.w=e;i.attrs.h=f;i.setBox({x:c,y:d,width:e,height:f});a.canvas[l](g);return i};bT=function(b,c,d,e){var f=cd("group"),g=cd("shape"),h=g.style,i=cd("path"),j=i.style,k=cd("textpath");f.style.cssText="position:absolute;left:0;top:0;width:"+b.width+"px;height:"+b.height+"px";f.coordsize=b$;f.coordorigin=b.coordorigin;i.v=a.format("m{0},{1}l{2},{1}",Q(c*10),Q(d*10),Q(c*10)+1);i.textpathok=true;h.width=b.width;h.height=b.height;k.string=r(e);k.on=true;g[l](k);g[l](i);f[l](g);var m=new bN(k,f,b);m.shape=g;m.textpath=i;m.type="text";m.attrs.text=e;m.attrs.x=c;m.attrs.y=d;m.attrs.w=1;m.attrs.h=1;bK(m,{font:W.font,stroke:"none",fill:"#000"});m.setBox();b.canvas[l](f);return m};bU=function(a,b){var c=this.canvas.style;a==+a&&(a+="px");b==+b&&(b+="px");c.width=a;c.height=b;c.clip="rect(0 "+a+" "+b+" 0)";return this};var cd;g.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!g.namespaces.rvml&&g.namespaces.add("rvml","urn:schemas-microsoft-com:vml");cd=function(a){return g.createElement("<rvml:"+a+" class=\"rvml\">")}}catch(a){cd=function(a){return g.createElement("<"+a+" xmlns=\"urn:schemas-microsoft.com:vml\" class=\"rvml\">")}}bV=function(){var b=by[m](0,arguments),c=b.container,d=b.height,e,f=b.width,h=b.x,i=b.y;if(!c)throw new Error("VML container not found.");var k=new j,n=k.canvas=g.createElement("div"),o=n.style;h=h||0;i=i||0;f=f||512;d=d||342;f==+f&&(f+="px");d==+d&&(d+="px");k.width=1000;k.height=1000;k.coordsize=b_*1000+q+b_*1000;k.coordorigin="0 0";k.span=g.createElement("span");k.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";n[l](k.span);o.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d);if(c==1){g.body[l](n);o.left=h+"px";o.top=i+"px";o.position="absolute"}else c.firstChild?c.insertBefore(n,c.firstChild):c[l](n);bz.call(k,k,a.fn);return k};k.clear=function(){this.canvas.innerHTML=p;this.span=g.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[l](this.span);this.bottom=this.top=null};k.remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=bF(a);return true}}var ce=navigator.userAgent.match(/Version\\x2f(.*?)\s/);navigator.vendor=="Apple Computer, Inc."&&(ce&&ce[1]<4||navigator.platform.slice(0,2)=="iP")?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});h.setTimeout(function(){a.remove()})}:k.safari=function(){};var cf=function(){this.returnValue=false},cg=function(){return this.originalEvent.preventDefault()},ch=function(){this.cancelBubble=true},ci=function(){return this.originalEvent.stopPropagation()},cj=(function(){{if(g.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,g=function(e){if(o&&u[f](b))for(var g=0,h=e.targetTouches&&e.targetTouches.length;g<h;g++){if(e.targetTouches[g].target==a){var i=e;e=e.targetTouches[g];e.originalEvent=i;e.preventDefault=cg;e.stopPropagation=ci;break}}return c.call(d,e)};a.addEventListener(e,g,false);return function(){a.removeEventListener(e,g,false);return true}};if(g.attachEvent)return function(a,b,c,d){var e=function(a){a=a||h.event;a.preventDefault=a.preventDefault||cf;a.stopPropagation=a.stopPropagation||ch;return c.call(d,a)};a.attachEvent("on"+b,e);var f=function(){a.detachEvent("on"+b,e);return true};return f}}})(),ck=[],cl=function(a){var b=a.clientX,c=a.clientY,d=g.documentElement.scrollTop||g.body.scrollTop,e=g.documentElement.scrollLeft||g.body.scrollLeft,f,h=ck.length;while(h--){f=ck[h];if(o){var i=a.touches.length,j;while(i--){j=a.touches[i];if(j.identifier==f.el._drag.id){b=j.clientX;c=j.clientY;(a.originalEvent?a.originalEvent:a).preventDefault();break}}}else a.preventDefault();b+=e;c+=d;f.move&&f.move.call(f.move_scope||f.el,b-f.el._drag.x,c-f.el._drag.y,b,c,a)}},cm=function(b){a.unmousemove(cl).unmouseup(cm);var c=ck.length,d;while(c--){d=ck[c];d.el._drag={};d.end&&d.end.call(d.end_scope||d.start_scope||d.move_scope||d.el,b)}ck=[]};for(var cn=t[w];cn--;)(function(b){a[b]=bN[e][b]=function(c,d){if(a.is(c,"function")){this.events=this.events||[];this.events.push({name:b,f:c,unbind:cj(this.shape||this.node||g,b,c,d||this)})}return this};a["un"+b]=bN[e]["un"+b]=function(a){var c=this.events,d=c[w];while(d--)if(c[d].name==b&&c[d].f==a){c[d].unbind();c.splice(d,1);!c.length&&delete this.events;return this}return this}})(t[cn]);bO.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)};bO.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};bO.drag=function(b,c,d,e,f,h){this._drag={};this.mousedown(function(i){(i.originalEvent||i).preventDefault();var j=g.documentElement.scrollTop||g.body.scrollTop,k=g.documentElement.scrollLeft||g.body.scrollLeft;this._drag.x=i.clientX+k;this._drag.y=i.clientY+j;this._drag.id=i.identifier;c&&c.call(f||e||this,i.clientX+k,i.clientY+j,i);!ck.length&&a.mousemove(cl).mouseup(cm);ck.push({el:this,move:b,end:d,move_scope:e,start_scope:f,end_scope:h})});return this};bO.undrag=function(b,c,d){var e=ck.length;while(e--)ck[e].el==this&&(ck[e].move==b&&ck[e].end==d)&&ck.splice(e++,1);!ck.length&&a.unmousemove(cl).unmouseup(cm)};k.circle=function(a,b,c){return bP(this,a||0,b||0,c||0)};k.rect=function(a,b,c,d,e){return bQ(this,a||0,b||0,c||0,d||0,e||0)};k.ellipse=function(a,b,c,d){return bR(this,a||0,b||0,c||0,d||0)};k.path=function(b){b&&!a.is(b,F)&&!a.is(b[0],G)&&(b+=p);return bH(a.format[m](a,arguments),this)};k.image=function(a,b,c,d,e){return bS(this,a||"about:blank",b||0,c||0,d||0,e||0)};k.text=function(a,b,c){return bT(this,a||0,b||0,r(c))};k.set=function(a){arguments[w]>1&&(a=Array[e].splice.call(arguments,0,arguments[w]));return new cC(a)};k.setSize=bU;k.top=k.bottom=null;k.raphael=a;function co(){return this.x+q+this.y}bO.resetScale=function(){if(this.removed)return this;this._.sx=1;this._.sy=1;this.attrs.scale="1 1"};bO.scale=function(a,b,c,d){if(this.removed)return this;if(a==null&&b==null)return{x:this._.sx,y:this._.sy,toString:co};b=b||a;!(+b)&&(b=a);var e,f,g,h,i=this.attrs;if(a!=0){var j=this.getBBox(),k=j.x+j.width/2,l=j.y+j.height/2,m=B(a/this._.sx),o=B(b/this._.sy);c=+c||c==0?c:k;d=+d||d==0?d:l;var r=this._.sx>0,s=this._.sy>0,t=~(~(a/B(a))),u=~(~(b/B(b))),x=m*t,y=o*u,z=this.node.style,A=c+B(k-c)*x*(k>c==r?1:-1),C=d+B(l-d)*y*(l>d==s?1:-1),D=a*t>b*u?o:m;switch(this.type){case"rect":case"image":var E=i.width*m,F=i.height*o;this.attr({height:F,r:i.r*D,width:E,x:A-E/2,y:C-F/2});break;case"circle":case"ellipse":this.attr({rx:i.rx*m,ry:i.ry*o,r:i.r*D,cx:A,cy:C});break;case"text":this.attr({x:A,y:C});break;case"path":var G=bp(i.path),H=true,I=r?x:m,J=s?y:o;for(var K=0,L=G[w];K<L;K++){var M=G[K],N=V.call(M[0]);{if(N=="M"&&H)continue;H=false}if(N=="A"){M[G[K][w]-2]*=I;M[G[K][w]-1]*=J;M[1]*=m;M[2]*=o;M[5]=+(t+u?!(!(+M[5])):!(+M[5]))}else if(N=="H")for(var O=1,P=M[w];O<P;O++)M[O]*=I;else if(N=="V")for(O=1,P=M[w];O<P;O++)M[O]*=J;else for(O=1,P=M[w];O<P;O++)M[O]*=O%2?I:J}var Q=bn(G);e=A-Q.x-Q.width/2;f=C-Q.y-Q.height/2;G[0][1]+=e;G[0][2]+=f;this.attr({path:G});break}if(this.type in{text:1,image:1}&&(t!=1||u!=1))if(this.transformations){this.transformations[2]="scale("[n](t,",",u,")");this.node[R]("transform",this.transformations[v](q));e=t==-1?-i.x-(E||0):i.x;f=u==-1?-i.y-(F||0):i.y;this.attr({x:e,y:f});i.fx=t-1;i.fy=u-1}else{this.node.filterMatrix=U+".Matrix(M11="[n](t,", M12=0, M21=0, M22=",u,", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')");z.filter=(this.node.filterMatrix||p)+(this.node.filterOpacity||p)}else if(this.transformations){this.transformations[2]=p;this.node[R]("transform",this.transformations[v](q));i.fx=0;i.fy=0}else{this.node.filterMatrix=p;z.filter=(this.node.filterMatrix||p)+(this.node.filterOpacity||p)}i.scale=[a,b,c,d][v](q);this._.sx=a;this._.sy=b}return this};bO.clone=function(){if(this.removed)return null;var a=this.attr();delete a.scale;delete a.translation;return this.paper[this.type]().attr(a)};var cp={},cq=function(b,c,d,e,f,g,h,i,j){var k=0,l=100,m=[b,c,d,e,f,g,h,i].join(),n=cp[m],o,p;!n&&(cp[m]=n={data:[]});n.timer&&clearTimeout(n.timer);n.timer=setTimeout(function(){delete cp[m]},2000);if(j!=null){var q=cq(b,c,d,e,f,g,h,i);l=~(~q)*10}for(var r=0;r<l+1;r++){if(n.data[j]>r)p=n.data[r*l];else{p=a.findDotsAtSegment(b,c,d,e,f,g,h,i,r/l);n.data[r]=p}r&&(k+=C(C(o.x-p.x,2)+C(o.y-p.y,2),0.5));if(j!=null&&k>=j)return p;o=p}if(j==null)return k},cr=function(b,c){return function(d,e,f){d=bw(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;o<p;o++){i=d[o];if(i[0]=="M"){g=+i[1];h=+i[2]}else{j=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6]);if(n+j>e){if(c&&!l.start){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);k+=["C",m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k;k=["M",m.x,m.y+"C",m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]][v]();n+=j;g=+i[5];h=+i[6];continue}if(!b&&!c){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j;g=+i[5];h=+i[6]}k+=i}l.end=k;m=b?n:c?l:a.findDotsAtSegment(g,h,i[1],i[2],i[3],i[4],i[5],i[6],1);m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cs=cr(1),ct=cr(),cu=cr(0,1);bO.getTotalLength=function(){if(this.type!="path")return;if(this.node.getTotalLength)return this.node.getTotalLength();return cs(this.attrs.path)};bO.getPointAtLength=function(a){if(this.type!="path")return;return ct(this.attrs.path,a)};bO.getSubpath=function(a,b){if(this.type!="path")return;if(B(this.getTotalLength()-b)<"1e-6")return cu(this.attrs.path,a).end;var c=cu(this.attrs.path,b,1);return a?cu(c,a).end:c};a.easing_formulas={linear:function(a){return a},"<":function(a){return C(a,3)},">":function(a){return C(a-1,3)+1},"<>":function(a){a=a*2;if(a<1)return C(a,3)/2;a-=2;return(C(a,3)+2)/2},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==0||a==1)return a;var b=0.3,c=b/4;return C(2,-10*a)*y.sin((a-c)*(2*D)/b)+1},bounce:function(a){var b=7.5625,c=2.75,d;if(a<1/c)d=b*a*a;else if(a<2/c){a-=1.5/c;d=b*a*a+0.75}else if(a<2.5/c){a-=2.25/c;d=b*a*a+0.9375}else{a-=2.625/c;d=b*a*a+0.984375}return d}};var cv=[],cw=function(){var b=+(new Date);for(var c=0;c<cv[w];c++){var d=cv[c];if(d.stop||d.el.removed)continue;var e=b-d.start,g=d.ms,h=d.easing,i=d.from,j=d.diff,k=d.to,l=d.t,m=d.el,n={},o;if(e<g){var r=h(e/g);for(var s in i)if(i[f](s)){switch(X[s]){case"along":o=r*g*j[s];k.back&&(o=k.len-o);var t=ct(k[s],o);m.translate(j.sx-j.x||0,j.sy-j.y||0);j.x=t.x;j.y=t.y;m.translate(t.x-j.sx,t.y-j.sy);k.rot&&m.rotate(j.r+t.alpha,t.x,t.y);break;case E:o=+i[s]+r*g*j[s];break;case"colour":o="rgb("+[cy(Q(i[s].r+r*g*j[s].r)),cy(Q(i[s].g+r*g*j[s].g)),cy(Q(i[s].b+r*g*j[s].b))][v](",")+")";break;case"path":o=[];for(var u=0,x=i[s][w];u<x;u++){o[u]=[i[s][u][0]];for(var y=1,z=i[s][u][w];y<z;y++)o[u][y]=+i[s][u][y]+r*g*j[s][u][y];o[u]=o[u][v](q)}o=o[v](q);break;case"csv":switch(s){case"translation":var A=r*g*j[s][0]-l.x,B=r*g*j[s][1]-l.y;l.x+=A;l.y+=B;o=A+q+B;break;case"rotation":o=+i[s][0]+r*g*j[s][0];i[s][1]&&(o+=","+i[s][1]+","+i[s][2]);break;case"scale":o=[+i[s][0]+r*g*j[s][0],+i[s][1]+r*g*j[s][1],2 in k[s]?k[s][2]:p,3 in k[s]?k[s][3]:p][v](q);break;case"clip-rect":o=[];u=4;while(u--)o[u]=+i[s][u]+r*g*j[s][u];break}break;default:var C=[].concat(i[s]);o=[];u=m.paper.customAttributes[s].length;while(u--)o[u]=+C[u]+r*g*j[s][u];break}n[s]=o}m.attr(n);m._run&&m._run.call(m)}else{if(k.along){t=ct(k.along,k.len*!k.back);m.translate(j.sx-(j.x||0)+t.x-j.sx,j.sy-(j.y||0)+t.y-j.sy);k.rot&&m.rotate(j.r+t.alpha,t.x,t.y)}(l.x||l.y)&&m.translate(-l.x,-l.y);k.scale&&(k.scale+=p);m.attr(k);cv.splice(c--,1)}}a.svg&&m&&m.paper&&m.paper.safari();cv[w]&&setTimeout(cw)},cx=function(b,c,d,e,f){var g=d-e;c.timeouts.push(setTimeout(function(){a.is(f,"function")&&f.call(c);c.animate(b,g,b.easing)},e))},cy=function(a){return z(A(a,255),0)},cz=function(a,b){if(a==null)return{x:this._.tx,y:this._.ty,toString:co};this._.tx+=+a;this._.ty+=+b;switch(this.type){case"circle":case"ellipse":this.attr({cx:+a+this.attrs.cx,cy:+b+this.attrs.cy});break;case"rect":case"image":case"text":this.attr({x:+a+this.attrs.x,y:+b+this.attrs.y});break;case"path":var c=bp(this.attrs.path);c[0][1]+=+a;c[0][2]+=+b;this.attr({path:c});break}return this};bO.animateWith=function(a,b,c,d,e){for(var f=0,g=cv.length;f<g;f++)cv[f].el.id==a.id&&(b.start=cv[f].start);return this.animate(b,c,d,e)};bO.animateAlong=cA();bO.animateAlongBack=cA(1);function cA(b){return function(c,d,e,f){var g={back:b};a.is(e,"function")?f=e:g.rot=e;c&&c.constructor==bN&&(c=c.attrs.path);c&&(g.along=c);return this.animate(g,d,f)}}function cB(a,b,c,d,e,f){var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;function m(a){return((i*a+h)*a+g)*a}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function o(a,b){var c,d,e,f,j,k;for(e=a,k=0;k<8;k++){f=m(e)-a;if(B(f)<b)return e;j=(3*i*e+2*h)*e+g;if(B(j)<0.000001)break;e=e-f/j}c=0;d=1;e=a;if(e<c)return c;if(e>d)return d;while(c<d){f=m(e);if(B(f-a)<b)return e;a>f?c=e:d=e;e=(d-c)/2+c}return e}return n(a,1/(200*f))}bO.onAnimation=function(a){this._run=a||0;return this};bO.animate=function(c,d,e,g){var h=this;h.timeouts=h.timeouts||[];if(a.is(e,"function")||!e)g=e||null;if(h.removed){g&&g.call(h);return h}var i={},j={},k=false,l={};for(var m in c)if(c[f](m)){if(X[f](m)||h.paper.customAttributes[f](m)){k=true;i[m]=h.attr(m);i[m]==null&&(i[m]=W[m]);j[m]=c[m];switch(X[m]){case"along":var n=cs(c[m]),o=ct(c[m],n*!(!c.back)),p=h.getBBox();l[m]=n/d;l.tx=p.x;l.ty=p.y;l.sx=o.x;l.sy=o.y;j.rot=c.rot;j.back=c.back;j.len=n;c.rot&&(l.r=S(h.rotate())||0);break;case E:l[m]=(j[m]-i[m])/d;break;case"colour":i[m]=a.getRGB(i[m]);var q=a.getRGB(j[m]);l[m]={r:(q.r-i[m].r)/d,g:(q.g-i[m].g)/d,b:(q.b-i[m].b)/d};break;case"path":var t=bw(i[m],j[m]);i[m]=t[0];var u=t[1];l[m]=[];for(var v=0,x=i[m][w];v<x;v++){l[m][v]=[0];for(var y=1,z=i[m][v][w];y<z;y++)l[m][v][y]=(u[v][y]-i[m][v][y])/d}break;case"csv":var A=r(c[m])[s](b),B=r(i[m])[s](b);switch(m){case"translation":i[m]=[0,0];l[m]=[A[0]/d,A[1]/d];break;case"rotation":i[m]=B[1]==A[1]&&B[2]==A[2]?B:[0,A[1],A[2]];l[m]=[(A[0]-i[m][0])/d,0,0];break;case"scale":c[m]=A;i[m]=r(i[m])[s](b);l[m]=[(A[0]-i[m][0])/d,(A[1]-i[m][1])/d,0,0];break;case"clip-rect":i[m]=r(i[m])[s](b);l[m]=[];v=4;while(v--)l[m][v]=(A[v]-i[m][v])/d;break}j[m]=A;break;default:A=[].concat(c[m]);B=[].concat(i[m]);l[m]=[];v=h.paper.customAttributes[m][w];while(v--)l[m][v]=((A[v]||0)-(B[v]||0))/d;break}}}if(k){var G=a.easing_formulas[e];if(!G){G=r(e).match(P);if(G&&G[w]==5){var H=G;G=function(a){return cB(a,+H[1],+H[2],+H[3],+H[4],d)}}else G=function(a){return a}}cv.push({start:c.start||+(new Date),ms:d,easing:G,from:i,diff:l,to:j,el:h,t:{x:0,y:0}});a.is(g,"function")&&(h._ac=setTimeout(function(){g.call(h)},d));cv[w]==1&&setTimeout(cw)}else{var C=[],D;for(var F in c)if(c[f](F)&&Z.test(F)){m={value:c[F]};F=="from"&&(F=0);F=="to"&&(F=100);m.key=T(F,10);C.push(m)}C.sort(be);C[0].key&&C.unshift({key:0,value:h.attrs});for(v=0,x=C[w];v<x;v++)cx(C[v].value,h,d/100*C[v].key,d/100*(C[v-1]&&C[v-1].key||0),C[v-1]&&C[v-1].value.callback);D=C[C[w]-1].value.callback;D&&h.timeouts.push(setTimeout(function(){D.call(h)},d))}return this};bO.stop=function(){for(var a=0;a<cv.length;a++)cv[a].el.id==this.id&&cv.splice(a--,1);for(a=0,ii=this.timeouts&&this.timeouts.length;a<ii;a++)clearTimeout(this.timeouts[a]);this.timeouts=[];clearTimeout(this._ac);delete this._ac;return this};bO.translate=function(a,b){return this.attr({translation:a+" "+b})};bO[H]=function(){return"Raphaël’s object"};a.ae=cv;var cC=function(a){this.items=[];this[w]=0;this.type="set";if(a)for(var b=0,c=a[w];b<c;b++){if(a[b]&&(a[b].constructor==bN||a[b].constructor==cC)){this[this.items[w]]=this.items[this.items[w]]=a[b];this[w]++}}};cC[e][L]=function(){var a,b;for(var c=0,d=arguments[w];c<d;c++){a=arguments[c];if(a&&(a.constructor==bN||a.constructor==cC)){b=this.items[w];this[b]=this.items[b]=a;this[w]++}}return this};cC[e].pop=function(){delete this[this[w]--];return this.items.pop()};for(var cD in bO)bO[f](cD)&&(cC[e][cD]=(function(a){return function(){for(var b=0,c=this.items[w];b<c;b++)this.items[b][a][m](this.items[b],arguments);return this}})(cD));cC[e].attr=function(b,c){if(b&&a.is(b,G)&&a.is(b[0],"object"))for(var d=0,e=b[w];d<e;d++)this.items[d].attr(b[d]);else for(var f=0,g=this.items[w];f<g;f++)this.items[f].attr(b,c);return this};cC[e].animate=function(b,c,d,e){(a.is(d,"function")||!d)&&(e=d||null);var f=this.items[w],g=f,h,i=this,j;e&&(j=function(){!(--f)&&e.call(i)});d=a.is(d,F)?d:j;h=this.items[--g].animate(b,c,d,j);while(g--)this.items[g]&&!this.items[g].removed&&this.items[g].animateWith(h,b,c,d,j);return this};cC[e].insertAfter=function(a){var b=this.items[w];while(b--)this.items[b].insertAfter(a);return this};cC[e].getBBox=function(){var a=[],b=[],c=[],d=[];for(var e=this.items[w];e--;){var f=this.items[e].getBBox();a[L](f.x);b[L](f.y);c[L](f.x+f.width);d[L](f.y+f.height)}a=A[m](0,a);b=A[m](0,b);return{x:a,y:b,width:z[m](0,c)-a,height:z[m](0,d)-b}};cC[e].clone=function(a){a=new cC;for(var b=0,c=this.items[w];b<c;b++)a[L](this.items[b].clone());return a};a.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[f](d)&&(b.face[d]=a.face[d]);this.fonts[c]?this.fonts[c][L](b):this.fonts[c]=[b];if(!a.svg){b.face["units-per-em"]=T(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[f](e)){var g=a.glyphs[e];b.glyphs[e]={w:g.w,k:{},d:g.d&&"M"+g.d[Y](/[mlcxtrv]/g,function(a){return({l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"})[a]||"M"})+"z"};if(g.k)for(var h in g.k)g[f](h)&&(b.glyphs[e].k[h]=g.k[h])}}return a};k.getFont=function(b,c,d,e){e=e||"normal";d=d||"normal";c=+c||({normal:400,bold:700,lighter:300,bolder:800})[c]||400;if(!a.fonts)return;var g=a.fonts[b];if(!g){var h=new RegExp("(^|\\s)"+b[Y](/[^\w\d\s+!~.:_-]/g,p)+"(\\s|$)","i");for(var i in a.fonts)if(a.fonts[f](i)){if(h.test(i)){g=a.fonts[i];break}}}var j;if(g)for(var k=0,l=g[w];k<l;k++){j=g[k];if(j.face["font-weight"]==c&&(j.face["font-style"]==d||!j.face["font-style"])&&j.face["font-stretch"]==e)break}return j};k.print=function(c,d,e,f,g,h,i){h=h||"middle";i=z(A(i||0,1),-1);var j=this.set(),k=r(e)[s](p),l=0,m=p,n;a.is(f,e)&&(f=this.getFont(f));if(f){n=(g||16)/f.face["units-per-em"];var o=f.face.bbox.split(b),q=+o[0],t=+o[1]+(h=="baseline"?o[3]-o[1]+ +f.face.descent:(o[3]-o[1])/2);for(var u=0,v=k[w];u<v;u++){var x=u&&f.glyphs[k[u-1]]||{},y=f.glyphs[k[u]];l+=u?(x.w||f.w)+(x.k&&x.k[k[u]]||0)+f.w*i:0;y&&y.d&&j[L](this.path(y.d).attr({fill:"#000",stroke:"none",translation:[l,0]}))}j.scale(n,n,q,t).translate(c-q,d-t)}return j};a.format=function(b,c){var e=a.is(c,G)?[0][n](c):arguments;b&&a.is(b,F)&&e[w]-1&&(b=b[Y](d,function(a,b){return e[++b]==null?p:e[b]}));return b||p};a.ninja=function(){i.was?h.Raphael=i.is:delete Raphael;return a};a.el=bO;a.st=cC[e];i.was?h.Raphael=a:Raphael=a})()
\ No newline at end of file
diff --git a/lib/raphael.js b/lib/raphael.js
new file mode 100644 (file)
index 0000000..9912a46
--- /dev/null
@@ -0,0 +1,3725 @@
+/*!
+ * Raphael 1.5.2 - JavaScript Vector Library
+ *
+ * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com)
+ * Licensed under the MIT (http://raphaeljs.com/license.html) license.
+ */
+(function () {
+    function R() {
+        if (R.is(arguments[0], array)) {
+            var a = arguments[0],
+                cnv = create[apply](R, a.splice(0, 3 + R.is(a[0], nu))),
+                res = cnv.set();
+            for (var i = 0, ii = a[length]; i < ii; i++) {
+                var j = a[i] || {};
+                elements[has](j.type) && res[push](cnv[j.type]().attr(j));
+            }
+            return res;
+        }
+        return create[apply](R, arguments);
+    }
+    R.version = "1.5.2";
+    var separator = /[, ]+/,
+        elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
+        formatrg = /\{(\d+)\}/g,
+        proto = "prototype",
+        has = "hasOwnProperty",
+        doc = document,
+        win = window,
+        oldRaphael = {
+            was: Object[proto][has].call(win, "Raphael"),
+            is: win.Raphael
+        },
+        Paper = function () {
+            this.customAttributes = {};
+        },
+        paperproto,
+        appendChild = "appendChild",
+        apply = "apply",
+        concat = "concat",
+        supportsTouch = "createTouch" in doc,
+        E = "",
+        S = " ",
+        Str = String,
+        split = "split",
+        events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend orientationchange touchcancel gesturestart gesturechange gestureend"[split](S),
+        touchMap = {
+            mousedown: "touchstart",
+            mousemove: "touchmove",
+            mouseup: "touchend"
+        },
+        join = "join",
+        length = "length",
+        lowerCase = Str[proto].toLowerCase,
+        math = Math,
+        mmax = math.max,
+        mmin = math.min,
+        abs = math.abs,
+        pow = math.pow,
+        PI = math.PI,
+        nu = "number",
+        string = "string",
+        array = "array",
+        toString = "toString",
+        fillString = "fill",
+        objectToString = Object[proto][toString],
+        paper = {},
+        push = "push",
+        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,
+        isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
+        bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+        round = math.round,
+        setAttribute = "setAttribute",
+        toFloat = parseFloat,
+        toInt = parseInt,
+        ms = " progid:DXImageTransform.Microsoft",
+        upperCase = Str[proto].toUpperCase,
+        availableAttrs = {blur: 0, "clip-rect": "0 0 1e9 1e9", cursor: "default", cx: 0, cy: 0, fill: "#fff", "fill-opacity": 1, font: '10px "Arial"', "font-family": '"Arial"', "font-size": "10", "font-style": "normal", "font-weight": 400, gradient: 0, height: 0, href: "http://raphaeljs.com/", opacity: 1, path: "M0,0", r: 0, rotation: 0, rx: 0, ry: 0, scale: "1 1", src: "", stroke: "#000", "stroke-dasharray": "", "stroke-linecap": "butt", "stroke-linejoin": "butt", "stroke-miterlimit": 0, "stroke-opacity": 1, "stroke-width": 1, target: "_blank", "text-anchor": "middle", title: "Raphael", translation: "0 0", width: 0, x: 0, y: 0},
+        availableAnimAttrs = {along: "along", blur: nu, "clip-rect": "csv", cx: nu, cy: nu, fill: "colour", "fill-opacity": nu, "font-size": nu, height: nu, opacity: nu, path: "path", r: nu, rotation: "csv", rx: nu, ry: nu, scale: "csv", stroke: "colour", "stroke-opacity": nu, "stroke-width": nu, translation: "csv", width: nu, x: nu, y: nu},
+        rp = "replace",
+        animKeyFrames= /^(from|to|\d+%?)$/,
+        commaSpaces = /\s*,\s*/,
+        hsrg = {hs: 1, rg: 1},
+        p2s = /,?([achlmqrstvxz]),?/gi,
+        pathCommand = /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
+        pathValues = /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
+        radial_gradient = /^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/,
+        sortByKey = function (a, b) {
+            return a.key - b.key;
+        };
+
+    R.type = (win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
+    if (R.type == "VML") {
+        var d = doc.createElement("div"),
+            b;
+        d.innerHTML = '<v:shape adj="1"/>';
+        b = d.firstChild;
+        b.style.behavior = "url(#default#VML)";
+        if (!(b && typeof b.adj == "object")) {
+            return R.type = null;
+        }
+        d = null;
+    }
+    R.svg = !(R.vml = R.type == "VML");
+    Paper[proto] = R[proto];
+    paperproto = Paper[proto];
+    R._id = 0;
+    R._oid = 0;
+    R.fn = {};
+    R.is = function (o, type) {
+        type = lowerCase.call(type);
+        if (type == "finite") {
+            return !isnan[has](+o);
+        }
+        return  (type == "null" && o === null) ||
+                (type == typeof o) ||
+                (type == "object" && o === Object(o)) ||
+                (type == "array" && Array.isArray && Array.isArray(o)) ||
+                objectToString.call(o).slice(8, -1).toLowerCase() == type;
+    };
+    R.angle = function (x1, y1, x2, y2, x3, y3) {
+        if (x3 == null) {
+            var x = x1 - x2,
+                y = y1 - y2;
+            if (!x && !y) {
+                return 0;
+            }
+            return ((x < 0) * 180 + math.atan(-y / -x) * 180 / PI + 360) % 360;
+        } else {
+            return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
+        }
+    };
+    R.rad = function (deg) {
+        return deg % 360 * PI / 180;
+    };
+    R.deg = function (rad) {
+        return rad * 180 / PI % 360;
+    };
+    R.snapTo = function (values, value, tolerance) {
+        tolerance = R.is(tolerance, "finite") ? tolerance : 10;
+        if (R.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;
+    };
+    function createUUID() {
+        // http://www.ietf.org/rfc/rfc4122.txt
+        var s = [],
+            i = 0;
+        for (; i < 32; i++) {
+            s[i] = (~~(math.random() * 16))[toString](16);
+        }
+        s[12] = 4;  // bits 12-15 of the time_hi_and_version field to 0010
+        s[16] = ((s[16] & 3) | 8)[toString](16);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
+        return "r-" + s[join]("");
+    }
+
+    R.setWindow = function (newwin) {
+        win = newwin;
+        doc = win.document;
+    };
+    // colour utilities
+    var toHex = function (color) {
+        if (R.vml) {
+            // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
+            var trim = /^\s+|\s+$/g;
+            var bod;
+            try {
+                var docum = new ActiveXObject("htmlfile");
+                docum.write("<body>");
+                docum.close();
+                bod = docum.body;
+            } catch(e) {
+                bod = createPopup().document.body;
+            }
+            var range = bod.createTextRange();
+            toHex = cacher(function (color) {
+                try {
+                    bod.style.color = Str(color)[rp](trim, E);
+                    var value = range.queryCommandValue("ForeColor");
+                    value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
+                    return "#" + ("000000" + value[toString](16)).slice(-6);
+                } catch(e) {
+                    return "none";
+                }
+            });
+        } else {
+            var i = doc.createElement("i");
+            i.title = "Rapha\xebl Colour Picker";
+            i.style.display = "none";
+            doc.body[appendChild](i);
+            toHex = cacher(function (color) {
+                i.style.color = color;
+                return doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+            });
+        }
+        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.hex;
+    };
+    R.hsb2rgb = function (h, s, b, o) {
+        if (R.is(h, "object") && "h" in h && "s" in h && "b" in h) {
+            b = h.b;
+            s = h.s;
+            h = h.h;
+            o = h.o;
+        }
+        return R.hsl2rgb(h, s, b / 2, o);
+    };
+    R.hsl2rgb = function (h, s, l, o) {
+        if (R.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;
+        }
+        var rgb = {},
+            channels = ["r", "g", "b"],
+            t2, t1, t3, r, g, b;
+        if (!s) {
+            rgb = {
+                r: l,
+                g: l,
+                b: l
+            };
+        } else {
+            if (l < .5) {
+                t2 = l * (1 + s);
+            } else {
+                t2 = l + s - l * s;
+            }
+            t1 = 2 * l - t2;
+            for (var i = 0; i < 3; i++) {
+                t3 = h + 1 / 3 * -(i - 1);
+                t3 < 0 && t3++;
+                t3 > 1 && t3--;
+                if (t3 * 6 < 1) {
+                    rgb[channels[i]] = t1 + (t2 - t1) * 6 * t3;
+                } else if (t3 * 2 < 1) {
+                    rgb[channels[i]] = t2;
+                } else if (t3 * 3 < 2) {
+                    rgb[channels[i]] = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
+                } else {
+                    rgb[channels[i]] = t1;
+                }
+            }
+        }
+        rgb.r *= 255;
+        rgb.g *= 255;
+        rgb.b *= 255;
+        rgb.hex = "#" + (16777216 | rgb.b | (rgb.g << 8) | (rgb.r << 16)).toString(16).slice(1);
+        R.is(o, "finite") && (rgb.opacity = o);
+        rgb.toString = rgbtoString;
+        return rgb;
+    };
+    R.rgb2hsb = function (red, green, blue) {
+        if (green == null && R.is(red, "object") && "r" in red && "g" in red && "b" in red) {
+            blue = red.b;
+            green = red.g;
+            red = red.r;
+        }
+        if (green == null && R.is(red, string)) {
+            var clr = R.getRGB(red);
+            red = clr.r;
+            green = clr.g;
+            blue = clr.b;
+        }
+        if (red > 1 || green > 1 || blue > 1) {
+            red /= 255;
+            green /= 255;
+            blue /= 255;
+        }
+        var max = mmax(red, green, blue),
+            min = mmin(red, green, blue),
+            hue,
+            saturation,
+            brightness = max;
+        if (min == max) {
+            return {h: 0, s: 0, b: max, toString: hsbtoString};
+        } else {
+            var delta = (max - min);
+            saturation = delta / max;
+            if (red == max) {
+                hue = (green - blue) / delta;
+            } else if (green == max) {
+                hue = 2 + ((blue - red) / delta);
+            } else {
+                hue = 4 + ((red - green) / delta);
+            }
+            hue /= 6;
+            hue < 0 && hue++;
+            hue > 1 && hue--;
+        }
+        return {h: hue, s: saturation, b: brightness, toString: hsbtoString};
+    };
+    R.rgb2hsl = function (red, green, blue) {
+        if (green == null && R.is(red, "object") && "r" in red && "g" in red && "b" in red) {
+            blue = red.b;
+            green = red.g;
+            red = red.r;
+        }
+        if (green == null && R.is(red, string)) {
+            var clr = R.getRGB(red);
+            red = clr.r;
+            green = clr.g;
+            blue = clr.b;
+        }
+        if (red > 1 || green > 1 || blue > 1) {
+            red /= 255;
+            green /= 255;
+            blue /= 255;
+        }
+        var max = mmax(red, green, blue),
+            min = mmin(red, green, blue),
+            h,
+            s,
+            l = (max + min) / 2,
+            hsl;
+        if (min == max) {
+            hsl =  {h: 0, s: 0, l: l};
+        } else {
+            var delta = max - min;
+            s = l < .5 ? delta / (max + min) : delta / (2 - max - min);
+            if (red == max) {
+                h = (green - blue) / delta;
+            } else if (green == max) {
+                h = 2 + (blue - red) / delta;
+            } else {
+                h = 4 + (red - green) / delta;
+            }
+            h /= 6;
+            h < 0 && h++;
+            h > 1 && h--;
+            hsl = {h: h, s: s, l: l};
+        }
+        hsl.toString = hsltoString;
+        return hsl;
+    };
+    R._path2string = function () {
+        return this.join(",")[rp](p2s, "$1");
+    };
+    function cacher(f, scope, postprocessor) {
+        function newf() {
+            var arg = Array[proto].slice.call(arguments, 0),
+                args = arg[join]("\u25ba"),
+                cache = newf.cache = newf.cache || {},
+                count = newf.count = newf.count || [];
+            if (cache[has](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;
+    }
+    R.getRGB = cacher(function (colour) {
+        if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+            return {r: -1, g: -1, b: -1, hex: "none", error: 1};
+        }
+        if (colour == "none") {
+            return {r: -1, g: -1, b: -1, hex: "none"};
+        }
+        !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+        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 *= 2.55);
+                green = toFloat(values[1]);
+                values[1].slice(-1) == "%" && (green *= 2.55);
+                blue = toFloat(values[2]);
+                values[2].slice(-1) == "%" && (blue *= 2.55);
+                (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 R.hsb2rgb(red, green, blue, opacity);
+            }
+            if (rgb[6]) {
+                values = rgb[6][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);
+                (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 R.hsl2rgb(red, green, blue, opacity);
+            }
+            rgb = {r: red, g: green, b: blue};
+            rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+            R.is(opacity, "finite") && (rgb.opacity = opacity);
+            return rgb;
+        }
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1};
+    }, R);
+    R.getColor = function (value) {
+        var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
+            rgb = this.hsb2rgb(start.h, start.s, start.b);
+        start.h += .075;
+        if (start.h > 1) {
+            start.h = 0;
+            start.s -= .2;
+            start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
+        }
+        return rgb.hex;
+    };
+    R.getColor.reset = function () {
+        delete this.start;
+    };
+    // path utilities
+    R.parsePathString = cacher(function (pathString) {
+        if (!pathString) {
+            return null;
+        }
+        var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
+            data = [];
+        if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
+            data = pathClone(pathString);
+        }
+        if (!data[length]) {
+            Str(pathString)[rp](pathCommand, function (a, b, c) {
+                var params = [],
+                    name = lowerCase.call(b);
+                c[rp](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";
+                }
+                while (params[length] >= paramCounts[name]) {
+                    data[push]([b][concat](params.splice(0, paramCounts[name])));
+                    if (!paramCounts[name]) {
+                        break;
+                    }
+                }
+            });
+        }
+        data[toString] = R._path2string;
+        return data;
+    });
+    R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            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,
+            mx = p1x + 2 * t * (c1x - p1x) + t * t * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t * t * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t * t * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t * t * (p2y - 2 * c2y + c1y),
+            ax = (1 - t) * p1x + t * c1x,
+            ay = (1 - t) * p1y + t * c1y,
+            cx = (1 - t) * c2x + t * p2x,
+            cy = (1 - t) * c2y + t * p2y,
+            alpha = (90 - math.atan((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};
+    };
+    var pathDimensions = cacher(function (path) {
+        if (!path) {
+            return {x: 0, y: 0, width: 0, height: 0};
+        }
+        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);
+        return {
+            x: xmin,
+            y: ymin,
+            width: mmax[apply](0, X) - xmin,
+            height: mmax[apply](0, Y) - ymin
+        };
+    }),
+        pathClone = function (pathArray) {
+            var res = [];
+            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+                pathArray = R.parsePathString(pathArray);
+            }
+            for (var i = 0, ii = pathArray[length]; i < ii; i++) {
+                res[i] = [];
+                for (var j = 0, jj = pathArray[i][length]; j < jj; j++) {
+                    res[i][j] = pathArray[i][j];
+                }
+            }
+            res[toString] = R._path2string;
+            return res;
+        },
+        pathToRelative = cacher(function (pathArray) {
+            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+                pathArray = R.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] = R._path2string;
+            return res;
+        }, 0, pathClone),
+        pathToAbsolute = cacher(function (pathArray) {
+            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+                pathArray = R.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[0] = ["M", x, y];
+            }
+            for (var i = start, ii = pathArray[length]; i < ii; i++) {
+                var r = res[i] = [],
+                    pa = pathArray[i];
+                if (pa[0] != upperCase.call(pa[0])) {
+                    r[0] = upperCase.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);
+                            r[7] = +(pa[7] + y);
+                            break;
+                        case "V":
+                            r[1] = +pa[1] + y;
+                            break;
+                        case "H":
+                            r[1] = +pa[1] + x;
+                            break;
+                        case "M":
+                            mx = +pa[1] + x;
+                            my = +pa[2] + y;
+                        default:
+                            for (var j = 1, jj = pa[length]; j < jj; j++) {
+                                r[j] = +pa[j] + ((j % 2) ? x : y);
+                            }
+                    }
+                } else {
+                    for (var k = 0, kk = pa[length]; k < kk; k++) {
+                        res[i][k] = pa[k];
+                    }
+                }
+                switch (r[0]) {
+                    case "Z":
+                        x = mx;
+                        y = my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = res[i][res[i][length] - 2];
+                        my = res[i][res[i][length] - 1];
+                    default:
+                        x = res[i][res[i][length] - 2];
+                        y = res[i][res[i][length] - 1];
+                }
+            }
+            res[toString] = R._path2string;
+            return res;
+        }, null, pathClone),
+        l2c = function (x1, y1, x2, y2) {
+            return [x1, y1, x2, y2, x2, y2];
+        },
+        q2c = function (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
+                ];
+        },
+        a2c = function (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 = 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;
+            }
+        },
+        findDotAtSegment = function (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
+            };
+        },
+        curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+            var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
+                b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
+                c = p1x - c1x,
+                t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
+                t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
+                y = [p1y, p2y],
+                x = [p1x, p2x],
+                dot;
+            abs(t1) > "1e12" && (t1 = .5);
+            abs(t2) > "1e12" && (t2 = .5);
+            if (t1 > 0 && t1 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+                x[push](dot.x);
+                y[push](dot.y);
+            }
+            if (t2 > 0 && t2 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+                x[push](dot.x);
+                y[push](dot.y);
+            }
+            a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
+            b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
+            c = p1y - c1y;
+            t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
+            t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
+            abs(t1) > "1e12" && (t1 = .5);
+            abs(t2) > "1e12" && (t2 = .5);
+            if (t1 > 0 && t1 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+                x[push](dot.x);
+                y[push](dot.y);
+            }
+            if (t2 > 0 && t2 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+                x[push](dot.x);
+                y[push](dot.y);
+            }
+            return {
+                min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
+                max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
+            };
+        }),
+        path2curve = cacher(function (path, path2) {
+            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) {
+                    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":
+                            nx = d.x + (d.x - (d.bx || d.x));
+                            ny = d.y + (d.y - (d.by || d.y));
+                            path = ["C", nx, ny][concat](path.slice(1));
+                            break;
+                        case "T":
+                            d.qx = d.x + (d.x - (d.qx || d.x));
+                            d.qy = d.y + (d.y - (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]) {
+                            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);
+                    }
+                };
+            for (var i = 0, ii = mmax(p[length], p2 && p2[length] || 0); i < ii; i++) {
+                p[i] = processPath(p[i], attrs);
+                fixArc(p, i);
+                p2 && (p2[i] = processPath(p2[i], attrs2));
+                p2 && 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];
+            }
+            return p2 ? [p, p2] : p;
+        }, null, pathClone),
+        parseDots = cacher(function (gradient) {
+            var dots = [];
+            for (var i = 0, ii = gradient[length]; i < ii; i++) {
+                var dot = {},
+                    par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
+                dot.color = R.getRGB(par[1]);
+                if (dot.color.error) {
+                    return null;
+                }
+                dot.color = dot.color.hex;
+                par[2] && (dot.offset = par[2] + "%");
+                dots[push](dot);
+            }
+            for (i = 1, ii = dots[length] - 1; i < ii; i++) {
+                if (!dots[i].offset) {
+                    var start = toFloat(dots[i - 1].offset || 0),
+                        end = 0;
+                    for (var j = i + 1; j < ii; j++) {
+                        if (dots[j].offset) {
+                            end = dots[j].offset;
+                            break;
+                        }
+                    }
+                    if (!end) {
+                        end = 100;
+                        j = ii;
+                    }
+                    end = toFloat(end);
+                    var d = (end - start) / (j - i + 1);
+                    for (; i < j; i++) {
+                        start += d;
+                        dots[i].offset = start + "%";
+                    }
+                }
+            }
+            return dots;
+        }),
+        getContainer = function (x, y, w, h) {
+            var container;
+            if (R.is(x, string) || R.is(x, "object")) {
+                container = R.is(x, string) ? doc.getElementById(x) : x;
+                if (container.tagName) {
+                    if (y == null) {
+                        return {
+                            container: container,
+                            width: container.style.pixelWidth || container.offsetWidth,
+                            height: container.style.pixelHeight || container.offsetHeight
+                        };
+                    } else {
+                        return {container: container, width: y, height: w};
+                    }
+                }
+            } else {
+                return {container: 1, x: x, y: y, width: w, height: h};
+            }
+        },
+        plugins = function (con, add) {
+            var that = this;
+            for (var prop in add) {
+                if (add[has](prop) && !(prop in con)) {
+                    switch (typeof add[prop]) {
+                        case "function":
+                            (function (f) {
+                                con[prop] = con === that ? f : function () { return f[apply](that, arguments); };
+                            })(add[prop]);
+                        break;
+                        case "object":
+                            con[prop] = con[prop] || {};
+                            plugins.call(this, con[prop], add[prop]);
+                        break;
+                        default:
+                            con[prop] = add[prop];
+                        break;
+                    }
+                }
+            }
+        },
+        tear = function (el, paper) {
+            el == paper.top && (paper.top = el.prev);
+            el == paper.bottom && (paper.bottom = el.next);
+            el.next && (el.next.prev = el.prev);
+            el.prev && (el.prev.next = el.next);
+        },
+        tofront = function (el, paper) {
+            if (paper.top === el) {
+                return;
+            }
+            tear(el, paper);
+            el.next = null;
+            el.prev = paper.top;
+            paper.top.next = el;
+            paper.top = el;
+        },
+        toback = function (el, paper) {
+            if (paper.bottom === el) {
+                return;
+            }
+            tear(el, paper);
+            el.next = paper.bottom;
+            el.prev = null;
+            paper.bottom.prev = el;
+            paper.bottom = el;
+        },
+        insertafter = function (el, el2, paper) {
+            tear(el, paper);
+            el2 == paper.top && (paper.top = el);
+            el2.next && (el2.next.prev = el);
+            el.next = el2.next;
+            el.prev = el2;
+            el2.next = el;
+        },
+        insertbefore = function (el, el2, paper) {
+            tear(el, paper);
+            el2 == paper.bottom && (paper.bottom = el);
+            el2.prev && (el2.prev.next = el);
+            el.prev = el2.prev;
+            el2.prev = el;
+            el.next = el2;
+        },
+        removed = function (methodname) {
+            return function () {
+                throw new Error("Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object");
+            };
+        };
+    R.pathToRelative = pathToRelative;
+    // SVG
+    if (R.svg) {
+        paperproto.svgns = "http://www.w3.org/2000/svg";
+        paperproto.xlink = "http://www.w3.org/1999/xlink";
+        round = function (num) {
+            return +num + (~~num === num) * .5;
+        };
+        var $ = function (el, attr) {
+            if (attr) {
+                for (var key in attr) {
+                    if (attr[has](key)) {
+                        el[setAttribute](key, Str(attr[key]));
+                    }
+                }
+            } else {
+                el = doc.createElementNS(paperproto.svgns, el);
+                el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
+                return el;
+            }
+        };
+        R[toString] = function () {
+            return  "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
+        };
+        var thePath = function (pathString, SVG) {
+            var el = $("path");
+            SVG.canvas && SVG.canvas[appendChild](el);
+            var p = new Element(el, SVG);
+            p.type = "path";
+            setFillAndStroke(p, {fill: "none", stroke: "#000", path: pathString});
+            return p;
+        };
+        var addGradientFill = function (o, gradient, SVG) {
+            var type = "linear",
+                fx = .5, fy = .5,
+                s = o.style;
+            gradient = Str(gradient)[rp](radial_gradient, function (all, _fx, _fy) {
+                type = "radial";
+                if (_fx && _fy) {
+                    fx = toFloat(_fx);
+                    fy = toFloat(_fy);
+                    var dir = ((fy > .5) * 2 - 1);
+                    pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
+                        (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
+                        fy != .5 &&
+                        (fy = fy.toFixed(5) - 1e-5 * dir);
+                }
+                return E;
+            });
+            gradient = gradient[split](/\s*\-\s*/);
+            if (type == "linear") {
+                var angle = gradient.shift();
+                angle = -toFloat(angle);
+                if (isNaN(angle)) {
+                    return null;
+                }
+                var vector = [0, 0, math.cos(angle * PI / 180), math.sin(angle * PI / 180)],
+                    max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
+                vector[2] *= max;
+                vector[3] *= max;
+                if (vector[2] < 0) {
+                    vector[0] = -vector[2];
+                    vector[2] = 0;
+                }
+                if (vector[3] < 0) {
+                    vector[1] = -vector[3];
+                    vector[3] = 0;
+                }
+            }
+            var dots = parseDots(gradient);
+            if (!dots) {
+                return null;
+            }
+            var id = o.getAttribute(fillString);
+            id = id.match(/^url\(#(.*)\)$/);
+            id && SVG.defs.removeChild(doc.getElementById(id[1]));
+
+            var el = $(type + "Gradient");
+            el.id = createUUID();
+            $(el, type == "radial" ? {fx: fx, fy: fy} : {x1: vector[0], y1: vector[1], x2: vector[2], y2: vector[3]});
+            SVG.defs[appendChild](el);
+            for (var i = 0, ii = dots[length]; i < ii; i++) {
+                var stop = $("stop");
+                $(stop, {
+                    offset: dots[i].offset ? dots[i].offset : !i ? "0%" : "100%",
+                    "stop-color": dots[i].color || "#fff"
+                });
+                el[appendChild](stop);
+            }
+            $(o, {
+                fill: "url(#" + el.id + ")",
+                opacity: 1,
+                "fill-opacity": 1
+            });
+            s.fill = E;
+            s.opacity = 1;
+            s.fillOpacity = 1;
+            return 1;
+        };
+        var updatePosition = function (o) {
+            var bbox = o.getBBox();
+            $(o.pattern, {patternTransform: R.format("translate({0},{1})", bbox.x, bbox.y)});
+        };
+        var setFillAndStroke = function (o, params) {
+            var dasharray = {
+                    "": [0],
+                    "none": [0],
+                    "-": [3, 1],
+                    ".": [1, 1],
+                    "-.": [3, 1, 1, 1],
+                    "-..": [3, 1, 1, 1, 1, 1],
+                    ". ": [1, 3],
+                    "- ": [4, 3],
+                    "--": [8, 3],
+                    "- .": [4, 3, 1, 3],
+                    "--.": [8, 3, 1, 3],
+                    "--..": [8, 3, 1, 3, 1, 3]
+                },
+                node = o.node,
+                attrs = o.attrs,
+                rot = o.rotate(),
+                addDashes = function (o, value) {
+                    value = dasharray[lowerCase.call(value)];
+                    if (value) {
+                        var width = o.attrs["stroke-width"] || "1",
+                            butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
+                            dashes = [];
+                        var i = value[length];
+                        while (i--) {
+                            dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
+                        }
+                        $(node, {"stroke-dasharray": dashes[join](",")});
+                    }
+                };
+            params[has]("rotation") && (rot = params.rotation);
+            var rotxy = Str(rot)[split](separator);
+            if (!(rotxy.length - 1)) {
+                rotxy = null;
+            } else {
+                rotxy[1] = +rotxy[1];
+                rotxy[2] = +rotxy[2];
+            }
+            toFloat(rot) && o.rotate(0, true);
+            for (var att in params) {
+                if (params[has](att)) {
+                    if (!availableAttrs[has](att)) {
+                        continue;
+                    }
+                    var value = params[att];
+                    attrs[att] = value;
+                    switch (att) {
+                        case "blur":
+                            o.blur(value);
+                            break;
+                        case "rotation":
+                            o.rotate(value, true);
+                            break;
+                        case "href":
+                        case "title":
+                        case "target":
+                            var pn = node.parentNode;
+                            if (lowerCase.call(pn.tagName) != "a") {
+                                var hl = $("a");
+                                pn.insertBefore(hl, node);
+                                hl[appendChild](node);
+                                pn = hl;
+                            }
+                            if (att == "target" && value == "blank") {
+                                pn.setAttributeNS(o.paper.xlink, "show", "new");
+                            } else {
+                                pn.setAttributeNS(o.paper.xlink, att, value);
+                            }
+                            break;
+                        case "cursor":
+                            node.style.cursor = value;
+                            break;
+                        case "clip-rect":
+                            var rect = Str(value)[split](separator);
+                            if (rect[length] == 4) {
+                                o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
+                                var el = $("clipPath"),
+                                    rc = $("rect");
+                                el.id = createUUID();
+                                $(rc, {
+                                    x: rect[0],
+                                    y: rect[1],
+                                    width: rect[2],
+                                    height: rect[3]
+                                });
+                                el[appendChild](rc);
+                                o.paper.defs[appendChild](el);
+                                $(node, {"clip-path": "url(#" + el.id + ")"});
+                                o.clip = rc;
+                            }
+                            if (!value) {
+                                var clip = doc.getElementById(node.getAttribute("clip-path")[rp](/(^url\(#|\)$)/g, E));
+                                clip && clip.parentNode.removeChild(clip);
+                                $(node, {"clip-path": E});
+                                delete o.clip;
+                            }
+                        break;
+                        case "path":
+                            if (o.type == "path") {
+                                $(node, {d: value ? attrs.path = pathToAbsolute(value) : "M0,0"});
+                            }
+                            break;
+                        case "width":
+                            node[setAttribute](att, value);
+                            if (attrs.fx) {
+                                att = "x";
+                                value = attrs.x;
+                            } else {
+                                break;
+                            }
+                        case "x":
+                            if (attrs.fx) {
+                                value = -attrs.x - (attrs.width || 0);
+                            }
+                        case "rx":
+                            if (att == "rx" && o.type == "rect") {
+                                break;
+                            }
+                        case "cx":
+                            rotxy && (att == "x" || att == "cx") && (rotxy[1] += value - attrs[att]);
+                            node[setAttribute](att, value);
+                            o.pattern && updatePosition(o);
+                            break;
+                        case "height":
+                            node[setAttribute](att, value);
+                            if (attrs.fy) {
+                                att = "y";
+                                value = attrs.y;
+                            } else {
+                                break;
+                            }
+                        case "y":
+                            if (attrs.fy) {
+                                value = -attrs.y - (attrs.height || 0);
+                            }
+                        case "ry":
+                            if (att == "ry" && o.type == "rect") {
+                                break;
+                            }
+                        case "cy":
+                            rotxy && (att == "y" || att == "cy") && (rotxy[2] += value - attrs[att]);
+                            node[setAttribute](att, value);
+                            o.pattern && updatePosition(o);
+                            break;
+                        case "r":
+                            if (o.type == "rect") {
+                                $(node, {rx: value, ry: value});
+                            } else {
+                                node[setAttribute](att, value);
+                            }
+                            break;
+                        case "src":
+                            if (o.type == "image") {
+                                node.setAttributeNS(o.paper.xlink, "href", value);
+                            }
+                            break;
+                        case "stroke-width":
+                            node.style.strokeWidth = value;
+                            // Need following line for Firefox
+                            node[setAttribute](att, value);
+                            if (attrs["stroke-dasharray"]) {
+                                addDashes(o, attrs["stroke-dasharray"]);
+                            }
+                            break;
+                        case "stroke-dasharray":
+                            addDashes(o, value);
+                            break;
+                        case "translation":
+                            var xy = Str(value)[split](separator);
+                            xy[0] = +xy[0] || 0;
+                            xy[1] = +xy[1] || 0;
+                            if (rotxy) {
+                                rotxy[1] += xy[0];
+                                rotxy[2] += xy[1];
+                            }
+                            translate.call(o, xy[0], xy[1]);
+                            break;
+                        case "scale":
+                            xy = Str(value)[split](separator);
+                            o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, isNaN(toFloat(xy[2])) ? null : +xy[2], isNaN(toFloat(xy[3])) ? null : +xy[3]);
+                            break;
+                        case fillString:
+                            var isURL = Str(value).match(ISURL);
+                            if (isURL) {
+                                el = $("pattern");
+                                var ig = $("image");
+                                el.id = createUUID();
+                                $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
+                                $(ig, {x: 0, y: 0});
+                                ig.setAttributeNS(o.paper.xlink, "href", isURL[1]);
+                                el[appendChild](ig);
+                                var img = doc.createElement("img");
+                                img.style.cssText = "position:absolute;left:-9999em;top-9999em";
+                                img.onload = function () {
+                                    $(el, {width: this.offsetWidth, height: this.offsetHeight});
+                                    $(ig, {width: this.offsetWidth, height: this.offsetHeight});
+                                    doc.body.removeChild(this);
+                                    o.paper.safari();
+                                };
+                                doc.body[appendChild](img);
+                                img.src = isURL[1];
+                                o.paper.defs[appendChild](el);
+                                node.style.fill = "url(#" + el.id + ")";
+                                $(node, {fill: "url(#" + el.id + ")"});
+                                o.pattern = el;
+                                o.pattern && updatePosition(o);
+                                break;
+                            }
+                            var clr = R.getRGB(value);
+                            if (!clr.error) {
+                                delete params.gradient;
+                                delete attrs.gradient;
+                                !R.is(attrs.opacity, "undefined") &&
+                                    R.is(params.opacity, "undefined") &&
+                                    $(node, {opacity: attrs.opacity});
+                                !R.is(attrs["fill-opacity"], "undefined") &&
+                                    R.is(params["fill-opacity"], "undefined") &&
+                                    $(node, {"fill-opacity": attrs["fill-opacity"]});
+                            } else if ((({circle: 1, ellipse: 1})[has](o.type) || Str(value).charAt() != "r") && addGradientFill(node, value, o.paper)) {
+                                attrs.gradient = value;
+                                attrs.fill = "none";
+                                break;
+                            }
+                            clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+                        case "stroke":
+                            clr = R.getRGB(value);
+                            node[setAttribute](att, clr.hex);
+                            att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+                            break;
+                        case "gradient":
+                            (({circle: 1, ellipse: 1})[has](o.type) || Str(value).charAt() != "r") && addGradientFill(node, value, o.paper);
+                            break;
+                        case "opacity":
+                            if (attrs.gradient && !attrs[has]("stroke-opacity")) {
+                                $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
+                            }
+                            // fall
+                        case "fill-opacity":
+                            if (attrs.gradient) {
+                                var gradient = doc.getElementById(node.getAttribute(fillString)[rp](/^url\(#|\)$/g, E));
+                                if (gradient) {
+                                    var stops = gradient.getElementsByTagName("stop");
+                                    stops[stops[length] - 1][setAttribute]("stop-opacity", value);
+                                }
+                                break;
+                            }
+                        default:
+                            att == "font-size" && (value = toInt(value, 10) + "px");
+                            var cssrule = att[rp](/(\-.)/g, function (w) {
+                                return upperCase.call(w.substring(1));
+                            });
+                            node.style[cssrule] = value;
+                            // Need following line for Firefox
+                            node[setAttribute](att, value);
+                            break;
+                    }
+                }
+            }
+            
+            tuneText(o, params);
+            if (rotxy) {
+                o.rotate(rotxy.join(S));
+            } else {
+                toFloat(rot) && o.rotate(rot, true);
+            }
+        };
+        var leading = 1.2,
+        tuneText = function (el, params) {
+            if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
+                return;
+            }
+            var a = el.attrs,
+                node = el.node,
+                fontSize = node.firstChild ? toInt(doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
+            if (params[has]("text")) {
+                a.text = params.text;
+                while (node.firstChild) {
+                    node.removeChild(node.firstChild);
+                }
+                var texts = Str(params.text)[split]("\n");
+                for (var i = 0, ii = texts[length]; i < ii; i++) if (texts[i]) {
+                    var tspan = $("tspan");
+                    i && $(tspan, {dy: fontSize * leading, x: a.x});
+                    tspan[appendChild](doc.createTextNode(texts[i]));
+                    node[appendChild](tspan);
+                }
+            } else {
+                texts = node.getElementsByTagName("tspan");
+                for (i = 0, ii = texts[length]; i < ii; i++) {
+                    i && $(texts[i], {dy: fontSize * leading, x: a.x});
+                }
+            }
+            $(node, {y: a.y});
+            var bb = el.getBBox(),
+                dif = a.y - (bb.y + bb.height / 2);
+            dif && R.is(dif, "finite") && $(node, {y: a.y + dif});
+        },
+        Element = function (node, svg) {
+            var X = 0,
+                Y = 0;
+            this[0] = node;
+            this.id = R._oid++;
+            this.node = node;
+            node.raphael = this;
+            this.paper = svg;
+            this.attrs = this.attrs || {};
+            this.transformations = []; // rotate, translate, scale
+            this._ = {
+                tx: 0,
+                ty: 0,
+                rt: {deg: 0, cx: 0, cy: 0},
+                sx: 1,
+                sy: 1
+            };
+            !svg.bottom && (svg.bottom = this);
+            this.prev = svg.top;
+            svg.top && (svg.top.next = this);
+            svg.top = this;
+            this.next = null;
+        };
+        var elproto = Element[proto];
+        Element[proto].rotate = function (deg, cx, cy) {
+            if (this.removed) {
+                return this;
+            }
+            if (deg == null) {
+                if (this._.rt.cx) {
+                    return [this._.rt.deg, this._.rt.cx, this._.rt.cy][join](S);
+                }
+                return this._.rt.deg;
+            }
+            var bbox = this.getBBox();
+            deg = Str(deg)[split](separator);
+            if (deg[length] - 1) {
+                cx = toFloat(deg[1]);
+                cy = toFloat(deg[2]);
+            }
+            deg = toFloat(deg[0]);
+            if (cx != null && cx !== false) {
+                this._.rt.deg = deg;
+            } else {
+                this._.rt.deg += deg;
+            }
+            (cy == null) && (cx = null);
+            this._.rt.cx = cx;
+            this._.rt.cy = cy;
+            cx = cx == null ? bbox.x + bbox.width / 2 : cx;
+            cy = cy == null ? bbox.y + bbox.height / 2 : cy;
+            if (this._.rt.deg) {
+                this.transformations[0] = R.format("rotate({0} {1} {2})", this._.rt.deg, cx, cy);
+                this.clip && $(this.clip, {transform: R.format("rotate({0} {1} {2})", -this._.rt.deg, cx, cy)});
+            } else {
+                this.transformations[0] = E;
+                this.clip && $(this.clip, {transform: E});
+            }
+            $(this.node, {transform: this.transformations[join](S)});
+            return this;
+        };
+        Element[proto].hide = function () {
+            !this.removed && (this.node.style.display = "none");
+            return this;
+        };
+        Element[proto].show = function () {
+            !this.removed && (this.node.style.display = "");
+            return this;
+        };
+        Element[proto].remove = function () {
+            if (this.removed) {
+                return;
+            }
+            tear(this, this.paper);
+            this.node.parentNode.removeChild(this.node);
+            for (var i in this) {
+                delete this[i];
+            }
+            this.removed = true;
+        };
+        Element[proto].getBBox = function () {
+            if (this.removed) {
+                return this;
+            }
+            if (this.type == "path") {
+                return pathDimensions(this.attrs.path);
+            }
+            if (this.node.style.display == "none") {
+                this.show();
+                var hide = true;
+            }
+            var bbox = {};
+            try {
+                bbox = this.node.getBBox();
+            } catch(e) {
+                // Firefox 3.0.x plays badly here
+            } finally {
+                bbox = bbox || {};
+            }
+            if (this.type == "text") {
+                bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
+                for (var i = 0, ii = this.node.getNumberOfChars(); i < ii; i++) {
+                    var bb = this.node.getExtentOfChar(i);
+                    (bb.y < bbox.y) && (bbox.y = bb.y);
+                    (bb.y + bb.height - bbox.y > bbox.height) && (bbox.height = bb.y + bb.height - bbox.y);
+                    (bb.x + bb.width - bbox.x > bbox.width) && (bbox.width = bb.x + bb.width - bbox.x);
+                }
+            }
+            hide && this.hide();
+            return bbox;
+        };
+        Element[proto].attr = function (name, value) {
+            if (this.removed) {
+                return this;
+            }
+            if (name == null) {
+                var res = {};
+                for (var i in this.attrs) if (this.attrs[has](i)) {
+                    res[i] = this.attrs[i];
+                }
+                this._.rt.deg && (res.rotation = this.rotate());
+                (this._.sx != 1 || this._.sy != 1) && (res.scale = this.scale());
+                res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+                return res;
+            }
+            if (value == null && R.is(name, string)) {
+                if (name == "translation") {
+                    return translate.call(this);
+                }
+                if (name == "rotation") {
+                    return this.rotate();
+                }
+                if (name == "scale") {
+                    return this.scale();
+                }
+                if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
+                    return this.attrs.gradient;
+                }
+                return this.attrs[name];
+            }
+            if (value == null && R.is(name, array)) {
+                var values = {};
+                for (var j = 0, jj = name.length; j < jj; j++) {
+                    values[name[j]] = this.attr(name[j]);
+                }
+                return values;
+            }
+            if (value != null) {
+                var params = {};
+                params[name] = value;
+            } else if (name != null && R.is(name, "object")) {
+                params = name;
+            }
+            for (var key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+                var par = this.paper.customAttributes[key].apply(this, [][concat](params[key]));
+                this.attrs[key] = params[key];
+                for (var subkey in par) if (par[has](subkey)) {
+                    params[subkey] = par[subkey];
+                }
+            }
+            setFillAndStroke(this, params);
+            return this;
+        };
+        Element[proto].toFront = function () {
+            if (this.removed) {
+                return this;
+            }
+            this.node.parentNode[appendChild](this.node);
+            var svg = this.paper;
+            svg.top != this && tofront(this, svg);
+            return this;
+        };
+        Element[proto].toBack = function () {
+            if (this.removed) {
+                return this;
+            }
+            if (this.node.parentNode.firstChild != this.node) {
+                this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
+                toback(this, this.paper);
+                var svg = this.paper;
+            }
+            return this;
+        };
+        Element[proto].insertAfter = function (element) {
+            if (this.removed) {
+                return this;
+            }
+            var node = element.node || element[element.length - 1].node;
+            if (node.nextSibling) {
+                node.parentNode.insertBefore(this.node, node.nextSibling);
+            } else {
+                node.parentNode[appendChild](this.node);
+            }
+            insertafter(this, element, this.paper);
+            return this;
+        };
+        Element[proto].insertBefore = function (element) {
+            if (this.removed) {
+                return this;
+            }
+            var node = element.node || element[0].node;
+            node.parentNode.insertBefore(this.node, node);
+            insertbefore(this, element, this.paper);
+            return this;
+        };
+        Element[proto].blur = function (size) {
+            // Experimental. No Safari support. Use it on your own risk.
+            var t = this;
+            if (+size !== 0) {
+                var fltr = $("filter"),
+                    blur = $("feGaussianBlur");
+                t.attrs.blur = size;
+                fltr.id = createUUID();
+                $(blur, {stdDeviation: +size || 1.5});
+                fltr.appendChild(blur);
+                t.paper.defs.appendChild(fltr);
+                t._blur = fltr;
+                $(t.node, {filter: "url(#" + fltr.id + ")"});
+            } else {
+                if (t._blur) {
+                    t._blur.parentNode.removeChild(t._blur);
+                    delete t._blur;
+                    delete t.attrs.blur;
+                }
+                t.node.removeAttribute("filter");
+            }
+        };
+        var theCircle = function (svg, x, y, r) {
+            var el = $("circle");
+            svg.canvas && svg.canvas[appendChild](el);
+            var res = new Element(el, svg);
+            res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
+            res.type = "circle";
+            $(el, res.attrs);
+            return res;
+        },
+        theRect = function (svg, x, y, w, h, r) {
+            var el = $("rect");
+            svg.canvas && svg.canvas[appendChild](el);
+            var res = new Element(el, svg);
+            res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
+            res.type = "rect";
+            $(el, res.attrs);
+            return res;
+        },
+        theEllipse = function (svg, x, y, rx, ry) {
+            var el = $("ellipse");
+            svg.canvas && svg.canvas[appendChild](el);
+            var res = new Element(el, svg);
+            res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
+            res.type = "ellipse";
+            $(el, res.attrs);
+            return res;
+        },
+        theImage = function (svg, src, x, y, w, h) {
+            var el = $("image");
+            $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
+            el.setAttributeNS(svg.xlink, "href", src);
+            svg.canvas && svg.canvas[appendChild](el);
+            var res = new Element(el, svg);
+            res.attrs = {x: x, y: y, width: w, height: h, src: src};
+            res.type = "image";
+            return res;
+        },
+        theText = function (svg, x, y, text) {
+            var el = $("text");
+            $(el, {x: x, y: y, "text-anchor": "middle"});
+            svg.canvas && svg.canvas[appendChild](el);
+            var res = new Element(el, svg);
+            res.attrs = {x: x, y: y, "text-anchor": "middle", text: text, font: availableAttrs.font, stroke: "none", fill: "#000"};
+            res.type = "text";
+            setFillAndStroke(res, res.attrs);
+            return res;
+        },
+        setSize = function (width, height) {
+            this.width = width || this.width;
+            this.height = height || this.height;
+            this.canvas[setAttribute]("width", this.width);
+            this.canvas[setAttribute]("height", this.height);
+            return this;
+        },
+        create = function () {
+            var con = getContainer[apply](0, arguments),
+                container = con && con.container,
+                x = con.x,
+                y = con.y,
+                width = con.width,
+                height = con.height;
+            if (!container) {
+                throw new Error("SVG container not found.");
+            }
+            var cnvs = $("svg");
+            x = x || 0;
+            y = y || 0;
+            width = width || 512;
+            height = height || 342;
+            $(cnvs, {
+                xmlns: "http://www.w3.org/2000/svg",
+                version: 1.1,
+                width: width,
+                height: height
+            });
+            if (container == 1) {
+                cnvs.style.cssText = "position:absolute;left:" + x + "px;top:" + y + "px";
+                doc.body[appendChild](cnvs);
+            } else {
+                if (container.firstChild) {
+                    container.insertBefore(cnvs, container.firstChild);
+                } else {
+                    container[appendChild](cnvs);
+                }
+            }
+            container = new Paper;
+            container.width = width;
+            container.height = height;
+            container.canvas = cnvs;
+            plugins.call(container, container, R.fn);
+            container.clear();
+            return container;
+        };
+        paperproto.clear = function () {
+            var c = this.canvas;
+            while (c.firstChild) {
+                c.removeChild(c.firstChild);
+            }
+            this.bottom = this.top = null;
+            (this.desc = $("desc"))[appendChild](doc.createTextNode("Created with Rapha\xebl"));
+            c[appendChild](this.desc);
+            c[appendChild](this.defs = $("defs"));
+        };
+        paperproto.remove = function () {
+            this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
+            for (var i in this) {
+                this[i] = removed(i);
+            }
+        };
+    }
+
+    // VML
+    if (R.vml) {
+        var map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
+            bites = /([clmz]),?([^clmz]*)/gi,
+            blurregexp = / progid:\S+Blur\([^\)]+\)/g,
+            val = /-?[^,\s-]+/g,
+            coordsize = 1e3 + S + 1e3,
+            zoom = 10,
+            pathlike = {path: 1, rect: 1},
+            path2vml = function (path) {
+                var total =  /[ahqstv]/ig,
+                    command = pathToAbsolute;
+                Str(path).match(total) && (command = path2curve);
+                total = /[clmz]/g;
+                if (command == pathToAbsolute && !Str(path).match(total)) {
+                    var res = Str(path)[rp](bites, function (all, command, args) {
+                        var vals = [],
+                            isMove = lowerCase.call(command) == "m",
+                            res = map[command];
+                        args[rp](val, function (value) {
+                            if (isMove && vals[length] == 2) {
+                                res += vals + map[command == "m" ? "l" : "L"];
+                                vals = [];
+                            }
+                            vals[push](round(value * zoom));
+                        });
+                        return res + vals;
+                    });
+                    return res;
+                }
+                var pa = command(path), p, r;
+                res = [];
+                for (var i = 0, ii = pa[length]; i < ii; i++) {
+                    p = pa[i];
+                    r = lowerCase.call(pa[i][0]);
+                    r == "z" && (r = "x");
+                    for (var j = 1, jj = p[length]; j < jj; j++) {
+                        r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
+                    }
+                    res[push](r);
+                }
+                return res[join](S);
+            };
+        
+        R[toString] = function () {
+            return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
+        };
+        thePath = function (pathString, vml) {
+            var g = createNode("group");
+            g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
+            g.coordsize = vml.coordsize;
+            g.coordorigin = vml.coordorigin;
+            var el = createNode("shape"), ol = el.style;
+            ol.width = vml.width + "px";
+            ol.height = vml.height + "px";
+            el.coordsize = coordsize;
+            el.coordorigin = vml.coordorigin;
+            g[appendChild](el);
+            var p = new Element(el, g, vml),
+                attr = {fill: "none", stroke: "#000"};
+            pathString && (attr.path = pathString);
+            p.type = "path";
+            p.path = [];
+            p.Path = E;
+            setFillAndStroke(p, attr);
+            vml.canvas[appendChild](g);
+            return p;
+        };
+        setFillAndStroke = function (o, params) {
+            o.attrs = o.attrs || {};
+            var node = o.node,
+                a = o.attrs,
+                s = node.style,
+                xy,
+                newpath = (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.r != a.r) && o.type == "rect",
+                res = o;
+
+            for (var par in params) if (params[has](par)) {
+                a[par] = params[par];
+            }
+            if (newpath) {
+                a.path = rectPath(a.x, a.y, a.width, a.height, a.r);
+                o.X = a.x;
+                o.Y = a.y;
+                o.W = a.width;
+                o.H = a.height;
+            }
+            params.href && (node.href = params.href);
+            params.title && (node.title = params.title);
+            params.target && (node.target = params.target);
+            params.cursor && (s.cursor = params.cursor);
+            "blur" in params && o.blur(params.blur);
+            if (params.path && o.type == "path" || newpath) {
+                node.path = path2vml(a.path);
+            }
+            if (params.rotation != null) {
+                o.rotate(params.rotation, true);
+            }
+            if (params.translation) {
+                xy = Str(params.translation)[split](separator);
+                translate.call(o, xy[0], xy[1]);
+                if (o._.rt.cx != null) {
+                    o._.rt.cx +=+ xy[0];
+                    o._.rt.cy +=+ xy[1];
+                    o.setBox(o.attrs, xy[0], xy[1]);
+                }
+            }
+            if (params.scale) {
+                xy = Str(params.scale)[split](separator);
+                o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null);
+            }
+            if ("clip-rect" in params) {
+                var rect = Str(params["clip-rect"])[split](separator);
+                if (rect[length] == 4) {
+                    rect[2] = +rect[2] + (+rect[0]);
+                    rect[3] = +rect[3] + (+rect[1]);
+                    var div = node.clipRect || doc.createElement("div"),
+                        dstyle = div.style,
+                        group = node.parentNode;
+                    dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
+                    if (!node.clipRect) {
+                        dstyle.position = "absolute";
+                        dstyle.top = 0;
+                        dstyle.left = 0;
+                        dstyle.width = o.paper.width + "px";
+                        dstyle.height = o.paper.height + "px";
+                        group.parentNode.insertBefore(div, group);
+                        div[appendChild](group);
+                        node.clipRect = div;
+                    }
+                }
+                if (!params["clip-rect"]) {
+                    node.clipRect && (node.clipRect.style.clip = E);
+                }
+            }
+            if (o.type == "image" && params.src) {
+                node.src = params.src;
+            }
+            if (o.type == "image" && params.opacity) {
+                node.filterOpacity = ms + ".Alpha(opacity=" + (params.opacity * 100) + ")";
+                s.filter = (node.filterMatrix || E) + (node.filterOpacity || E);
+            }
+            params.font && (s.font = params.font);
+            params["font-family"] && (s.fontFamily = '"' + params["font-family"][split](",")[0][rp](/^['"]+|['"]+$/g, E) + '"');
+            params["font-size"] && (s.fontSize = params["font-size"]);
+            params["font-weight"] && (s.fontWeight = params["font-weight"]);
+            params["font-style"] && (s.fontStyle = params["font-style"]);
+            if (params.opacity != null || 
+                params["stroke-width"] != null ||
+                params.fill != null ||
+                params.stroke != null ||
+                params["stroke-width"] != null ||
+                params["stroke-opacity"] != null ||
+                params["fill-opacity"] != null ||
+                params["stroke-dasharray"] != null ||
+                params["stroke-miterlimit"] != null ||
+                params["stroke-linejoin"] != null ||
+                params["stroke-linecap"] != null) {
+                node = o.shape || node;
+                var fill = (node.getElementsByTagName(fillString) && node.getElementsByTagName(fillString)[0]),
+                    newfill = false;
+                !fill && (newfill = fill = createNode(fillString));
+                if ("fill-opacity" in params || "opacity" in params) {
+                    var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
+                    opacity = mmin(mmax(opacity, 0), 1);
+                    fill.opacity = opacity;
+                }
+                params.fill && (fill.on = true);
+                if (fill.on == null || params.fill == "none") {
+                    fill.on = false;
+                }
+                if (fill.on && params.fill) {
+                    var isURL = params.fill.match(ISURL);
+                    if (isURL) {
+                        fill.src = isURL[1];
+                        fill.type = "tile";
+                    } else {
+                        fill.color = R.getRGB(params.fill).hex;
+                        fill.src = E;
+                        fill.type = "solid";
+                        if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill)) {
+                            a.fill = "none";
+                            a.gradient = params.fill;
+                        }
+                    }
+                }
+                newfill && node[appendChild](fill);
+                var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
+                newstroke = false;
+                !stroke && (newstroke = stroke = createNode("stroke"));
+                if ((params.stroke && params.stroke != "none") ||
+                    params["stroke-width"] ||
+                    params["stroke-opacity"] != null ||
+                    params["stroke-dasharray"] ||
+                    params["stroke-miterlimit"] ||
+                    params["stroke-linejoin"] ||
+                    params["stroke-linecap"]) {
+                    stroke.on = true;
+                }
+                (params.stroke == "none" || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
+                var strokeColor = R.getRGB(params.stroke);
+                stroke.on && params.stroke && (stroke.color = strokeColor.hex);
+                opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
+                var width = (toFloat(params["stroke-width"]) || 1) * .75;
+                opacity = mmin(mmax(opacity, 0), 1);
+                params["stroke-width"] == null && (width = a["stroke-width"]);
+                params["stroke-width"] && (stroke.weight = width);
+                width && width < 1 && (opacity *= width) && (stroke.weight = 1);
+                stroke.opacity = opacity;
+                
+                params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
+                stroke.miterlimit = params["stroke-miterlimit"] || 8;
+                params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
+                if (params["stroke-dasharray"]) {
+                    var dasharray = {
+                        "-": "shortdash",
+                        ".": "shortdot",
+                        "-.": "shortdashdot",
+                        "-..": "shortdashdotdot",
+                        ". ": "dot",
+                        "- ": "dash",
+                        "--": "longdash",
+                        "- .": "dashdot",
+                        "--.": "longdashdot",
+                        "--..": "longdashdotdot"
+                    };
+                    stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
+                }
+                newstroke && node[appendChild](stroke);
+            }
+            if (res.type == "text") {
+                s = res.paper.span.style;
+                a.font && (s.font = a.font);
+                a["font-family"] && (s.fontFamily = a["font-family"]);
+                a["font-size"] && (s.fontSize = a["font-size"]);
+                a["font-weight"] && (s.fontWeight = a["font-weight"]);
+                a["font-style"] && (s.fontStyle = a["font-style"]);
+                res.node.string && (res.paper.span.innerHTML = Str(res.node.string)[rp](/</g, "&#60;")[rp](/&/g, "&#38;")[rp](/\n/g, "<br>"));
+                res.W = a.w = res.paper.span.offsetWidth;
+                res.H = a.h = res.paper.span.offsetHeight;
+                res.X = a.x;
+                res.Y = a.y + round(res.H / 2);
+                // text-anchor emulationm
+                switch (a["text-anchor"]) {
+                    case "start":
+                        res.node.style["v-text-align"] = "left";
+                        res.bbx = round(res.W / 2);
+                    break;
+                    case "end":
+                        res.node.style["v-text-align"] = "right";
+                        res.bbx = -round(res.W / 2);
+                    break;
+                    default:
+                        res.node.style["v-text-align"] = "center";
+                    break;
+                }
+            }
+        };
+        addGradientFill = function (o, gradient) {
+            o.attrs = o.attrs || {};
+            var attrs = o.attrs,
+                fill,
+                type = "linear",
+                fxfy = ".5 .5";
+            o.attrs.gradient = gradient;
+            gradient = Str(gradient)[rp](radial_gradient, function (all, fx, fy) {
+                type = "radial";
+                if (fx && fy) {
+                    fx = toFloat(fx);
+                    fy = toFloat(fy);
+                    pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
+                    fxfy = fx + S + fy;
+                }
+                return E;
+            });
+            gradient = gradient[split](/\s*\-\s*/);
+            if (type == "linear") {
+                var angle = gradient.shift();
+                angle = -toFloat(angle);
+                if (isNaN(angle)) {
+                    return null;
+                }
+            }
+            var dots = parseDots(gradient);
+            if (!dots) {
+                return null;
+            }
+            o = o.shape || o.node;
+            fill = o.getElementsByTagName(fillString)[0] || createNode(fillString);
+            !fill.parentNode && o.appendChild(fill);
+            if (dots[length]) {
+                fill.on = true;
+                fill.method = "none";
+                fill.color = dots[0].color;
+                fill.color2 = dots[dots[length] - 1].color;
+                var clrs = [];
+                for (var i = 0, ii = dots[length]; i < ii; i++) {
+                    dots[i].offset && clrs[push](dots[i].offset + S + dots[i].color);
+                }
+                fill.colors && (fill.colors.value = clrs[length] ? clrs[join]() : "0% " + fill.color);
+                if (type == "radial") {
+                    fill.type = "gradientradial";
+                    fill.focus = "100%";
+                    fill.focussize = fxfy;
+                    fill.focusposition = fxfy;
+                } else {
+                    fill.type = "gradient";
+                    fill.angle = (270 - angle) % 360;
+                }
+            }
+            return 1;
+        };
+        Element = function (node, group, vml) {
+            var Rotation = 0,
+                RotX = 0,
+                RotY = 0,
+                Scale = 1;
+            this[0] = node;
+            this.id = R._oid++;
+            this.node = node;
+            node.raphael = this;
+            this.X = 0;
+            this.Y = 0;
+            this.attrs = {};
+            this.Group = group;
+            this.paper = vml;
+            this._ = {
+                tx: 0,
+                ty: 0,
+                rt: {deg:0},
+                sx: 1,
+                sy: 1
+            };
+            !vml.bottom && (vml.bottom = this);
+            this.prev = vml.top;
+            vml.top && (vml.top.next = this);
+            vml.top = this;
+            this.next = null;
+        };
+        elproto = Element[proto];
+        elproto.rotate = function (deg, cx, cy) {
+            if (this.removed) {
+                return this;
+            }
+            if (deg == null) {
+                if (this._.rt.cx) {
+                    return [this._.rt.deg, this._.rt.cx, this._.rt.cy][join](S);
+                }
+                return this._.rt.deg;
+            }
+            deg = Str(deg)[split](separator);
+            if (deg[length] - 1) {
+                cx = toFloat(deg[1]);
+                cy = toFloat(deg[2]);
+            }
+            deg = toFloat(deg[0]);
+            if (cx != null) {
+                this._.rt.deg = deg;
+            } else {
+                this._.rt.deg += deg;
+            }
+            cy == null && (cx = null);
+            this._.rt.cx = cx;
+            this._.rt.cy = cy;
+            this.setBox(this.attrs, cx, cy);
+            this.Group.style.rotation = this._.rt.deg;
+            // gradient fix for rotation. TODO
+            // var fill = (this.shape || this.node).getElementsByTagName(fillString);
+            // fill = fill[0] || {};
+            // var b = ((360 - this._.rt.deg) - 270) % 360;
+            // !R.is(fill.angle, "undefined") && (fill.angle = b);
+            return this;
+        };
+        elproto.setBox = function (params, cx, cy) {
+            if (this.removed) {
+                return this;
+            }
+            var gs = this.Group.style,
+                os = (this.shape && this.shape.style) || this.node.style;
+            params = params || {};
+            for (var i in params) if (params[has](i)) {
+                this.attrs[i] = params[i];
+            }
+            cx = cx || this._.rt.cx;
+            cy = cy || this._.rt.cy;
+            var attr = this.attrs,
+                x,
+                y,
+                w,
+                h;
+            switch (this.type) {
+                case "circle":
+                    x = attr.cx - attr.r;
+                    y = attr.cy - attr.r;
+                    w = h = attr.r * 2;
+                    break;
+                case "ellipse":
+                    x = attr.cx - attr.rx;
+                    y = attr.cy - attr.ry;
+                    w = attr.rx * 2;
+                    h = attr.ry * 2;
+                    break;
+                case "image":
+                    x = +attr.x;
+                    y = +attr.y;
+                    w = attr.width || 0;
+                    h = attr.height || 0;
+                    break;
+                case "text":
+                    this.textpath.v = ["m", round(attr.x), ", ", round(attr.y - 2), "l", round(attr.x) + 1, ", ", round(attr.y - 2)][join](E);
+                    x = attr.x - round(this.W / 2);
+                    y = attr.y - this.H / 2;
+                    w = this.W;
+                    h = this.H;
+                    break;
+                case "rect":
+                case "path":
+                    if (!this.attrs.path) {
+                        x = 0;
+                        y = 0;
+                        w = this.paper.width;
+                        h = this.paper.height;
+                    } else {
+                        var dim = pathDimensions(this.attrs.path);
+                        x = dim.x;
+                        y = dim.y;
+                        w = dim.width;
+                        h = dim.height;
+                    }
+                    break;
+                default:
+                    x = 0;
+                    y = 0;
+                    w = this.paper.width;
+                    h = this.paper.height;
+                    break;
+            }
+            cx = (cx == null) ? x + w / 2 : cx;
+            cy = (cy == null) ? y + h / 2 : cy;
+            var left = cx - this.paper.width / 2,
+                top = cy - this.paper.height / 2, t;
+            gs.left != (t = left + "px") && (gs.left = t);
+            gs.top != (t = top + "px") && (gs.top = t);
+            this.X = pathlike[has](this.type) ? -left : x;
+            this.Y = pathlike[has](this.type) ? -top : y;
+            this.W = w;
+            this.H = h;
+            if (pathlike[has](this.type)) {
+                os.left != (t = -left * zoom + "px") && (os.left = t);
+                os.top != (t = -top * zoom + "px") && (os.top = t);
+            } else if (this.type == "text") {
+                os.left != (t = -left + "px") && (os.left = t);
+                os.top != (t = -top + "px") && (os.top = t);
+            } else {
+                gs.width != (t = this.paper.width + "px") && (gs.width = t);
+                gs.height != (t = this.paper.height + "px") && (gs.height = t);
+                os.left != (t = x - left + "px") && (os.left = t);
+                os.top != (t = y - top + "px") && (os.top = t);
+                os.width != (t = w + "px") && (os.width = t);
+                os.height != (t = h + "px") && (os.height = t);
+            }
+        };
+        elproto.hide = function () {
+            !this.removed && (this.Group.style.display = "none");
+            return this;
+        };
+        elproto.show = function () {
+            !this.removed && (this.Group.style.display = "block");
+            return this;
+        };
+        elproto.getBBox = function () {
+            if (this.removed) {
+                return this;
+            }
+            if (pathlike[has](this.type)) {
+                return pathDimensions(this.attrs.path);
+            }
+            return {
+                x: this.X + (this.bbx || 0),
+                y: this.Y,
+                width: this.W,
+                height: this.H
+            };
+        };
+        elproto.remove = function () {
+            if (this.removed) {
+                return;
+            }
+            tear(this, this.paper);
+            this.node.parentNode.removeChild(this.node);
+            this.Group.parentNode.removeChild(this.Group);
+            this.shape && this.shape.parentNode.removeChild(this.shape);
+            for (var i in this) {
+                delete this[i];
+            }
+            this.removed = true;
+        };
+        elproto.attr = function (name, value) {
+            if (this.removed) {
+                return this;
+            }
+            if (name == null) {
+                var res = {};
+                for (var i in this.attrs) if (this.attrs[has](i)) {
+                    res[i] = this.attrs[i];
+                }
+                this._.rt.deg && (res.rotation = this.rotate());
+                (this._.sx != 1 || this._.sy != 1) && (res.scale = this.scale());
+                res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+                return res;
+            }
+            if (value == null && R.is(name, "string")) {
+                if (name == "translation") {
+                    return translate.call(this);
+                }
+                if (name == "rotation") {
+                    return this.rotate();
+                }
+                if (name == "scale") {
+                    return this.scale();
+                }
+                if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
+                    return this.attrs.gradient;
+                }
+                return this.attrs[name];
+            }
+            if (this.attrs && value == null && R.is(name, array)) {
+                var ii, values = {};
+                for (i = 0, ii = name[length]; i < ii; i++) {
+                    values[name[i]] = this.attr(name[i]);
+                }
+                return values;
+            }
+            var params;
+            if (value != null) {
+                params = {};
+                params[name] = value;
+            }
+            value == null && R.is(name, "object") && (params = name);
+            if (params) {
+                for (var key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+                    var par = this.paper.customAttributes[key].apply(this, [][concat](params[key]));
+                    this.attrs[key] = params[key];
+                    for (var subkey in par) if (par[has](subkey)) {
+                        params[subkey] = par[subkey];
+                    }
+                }
+                if (params.text && this.type == "text") {
+                    this.node.string = params.text;
+                }
+                setFillAndStroke(this, params);
+                if (params.gradient && (({circle: 1, ellipse: 1})[has](this.type) || Str(params.gradient).charAt() != "r")) {
+                    addGradientFill(this, params.gradient);
+                }
+                (!pathlike[has](this.type) || this._.rt.deg) && this.setBox(this.attrs);
+            }
+            return this;
+        };
+        elproto.toFront = function () {
+            !this.removed && this.Group.parentNode[appendChild](this.Group);
+            this.paper.top != this && tofront(this, this.paper);
+            return this;
+        };
+        elproto.toBack = function () {
+            if (this.removed) {
+                return this;
+            }
+            if (this.Group.parentNode.firstChild != this.Group) {
+                this.Group.parentNode.insertBefore(this.Group, this.Group.parentNode.firstChild);
+                toback(this, this.paper);
+            }
+            return this;
+        };
+        elproto.insertAfter = function (element) {
+            if (this.removed) {
+                return this;
+            }
+            if (element.constructor == Set) {
+                element = element[element.length - 1];
+            }
+            if (element.Group.nextSibling) {
+                element.Group.parentNode.insertBefore(this.Group, element.Group.nextSibling);
+            } else {
+                element.Group.parentNode[appendChild](this.Group);
+            }
+            insertafter(this, element, this.paper);
+            return this;
+        };
+        elproto.insertBefore = function (element) {
+            if (this.removed) {
+                return this;
+            }
+            if (element.constructor == Set) {
+                element = element[0];
+            }
+            element.Group.parentNode.insertBefore(this.Group, element.Group);
+            insertbefore(this, element, this.paper);
+            return this;
+        };
+        elproto.blur = function (size) {
+            var s = this.node.runtimeStyle,
+                f = s.filter;
+            f = f.replace(blurregexp, E);
+            if (+size !== 0) {
+                this.attrs.blur = size;
+                s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
+                s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
+            } else {
+                s.filter = f;
+                s.margin = 0;
+                delete this.attrs.blur;
+            }
+        };
+        theCircle = function (vml, x, y, r) {
+            var g = createNode("group"),
+                o = createNode("oval"),
+                ol = o.style;
+            g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
+            g.coordsize = coordsize;
+            g.coordorigin = vml.coordorigin;
+            g[appendChild](o);
+            var res = new Element(o, g, vml);
+            res.type = "circle";
+            setFillAndStroke(res, {stroke: "#000", fill: "none"});
+            res.attrs.cx = x;
+            res.attrs.cy = y;
+            res.attrs.r = r;
+            res.setBox({x: x - r, y: y - r, width: r * 2, height: r * 2});
+            vml.canvas[appendChild](g);
+            return res;
+        };
+        function rectPath(x, y, w, h, r) {
+            if (r) {
+                return R.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z", x + r, y, w - r * 2, r, -r, h - r * 2, r * 2 - w, r * 2 - h);
+            } else {
+                return R.format("M{0},{1}l{2},0,0,{3},{4},0z", x, y, w, h, -w);
+            }
+        }
+        theRect = function (vml, x, y, w, h, r) {
+            var path = rectPath(x, y, w, h, r),
+                res = vml.path(path),
+                a = res.attrs;
+            res.X = a.x = x;
+            res.Y = a.y = y;
+            res.W = a.width = w;
+            res.H = a.height = h;
+            a.r = r;
+            a.path = path;
+            res.type = "rect";
+            return res;
+        };
+        theEllipse = function (vml, x, y, rx, ry) {
+            var g = createNode("group"),
+                o = createNode("oval"),
+                ol = o.style;
+            g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
+            g.coordsize = coordsize;
+            g.coordorigin = vml.coordorigin;
+            g[appendChild](o);
+            var res = new Element(o, g, vml);
+            res.type = "ellipse";
+            setFillAndStroke(res, {stroke: "#000"});
+            res.attrs.cx = x;
+            res.attrs.cy = y;
+            res.attrs.rx = rx;
+            res.attrs.ry = ry;
+            res.setBox({x: x - rx, y: y - ry, width: rx * 2, height: ry * 2});
+            vml.canvas[appendChild](g);
+            return res;
+        };
+        theImage = function (vml, src, x, y, w, h) {
+            var g = createNode("group"),
+                o = createNode("image");
+            g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
+            g.coordsize = coordsize;
+            g.coordorigin = vml.coordorigin;
+            o.src = src;
+            g[appendChild](o);
+            var res = new Element(o, g, vml);
+            res.type = "image";
+            res.attrs.src = src;
+            res.attrs.x = x;
+            res.attrs.y = y;
+            res.attrs.w = w;
+            res.attrs.h = h;
+            res.setBox({x: x, y: y, width: w, height: h});
+            vml.canvas[appendChild](g);
+            return res;
+        };
+        theText = function (vml, x, y, text) {
+            var g = createNode("group"),
+                el = createNode("shape"),
+                ol = el.style,
+                path = createNode("path"),
+                ps = path.style,
+                o = createNode("textpath");
+            g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
+            g.coordsize = coordsize;
+            g.coordorigin = vml.coordorigin;
+            path.v = R.format("m{0},{1}l{2},{1}", round(x * 10), round(y * 10), round(x * 10) + 1);
+            path.textpathok = true;
+            ol.width = vml.width;
+            ol.height = vml.height;
+            o.string = Str(text);
+            o.on = true;
+            el[appendChild](o);
+            el[appendChild](path);
+            g[appendChild](el);
+            var res = new Element(o, g, vml);
+            res.shape = el;
+            res.textpath = path;
+            res.type = "text";
+            res.attrs.text = text;
+            res.attrs.x = x;
+            res.attrs.y = y;
+            res.attrs.w = 1;
+            res.attrs.h = 1;
+            setFillAndStroke(res, {font: availableAttrs.font, stroke: "none", fill: "#000"});
+            res.setBox();
+            vml.canvas[appendChild](g);
+            return res;
+        };
+        setSize = function (width, height) {
+            var cs = this.canvas.style;
+            width == +width && (width += "px");
+            height == +height && (height += "px");
+            cs.width = width;
+            cs.height = height;
+            cs.clip = "rect(0 " + width + " " + height + " 0)";
+            return this;
+        };
+        var createNode;
+        doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
+        try {
+            !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
+            createNode = function (tagName) {
+                return doc.createElement('<rvml:' + tagName + ' class="rvml">');
+            };
+        } catch (e) {
+            createNode = function (tagName) {
+                return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
+            };
+        }
+        create = function () {
+            var con = getContainer[apply](0, arguments),
+                container = con.container,
+                height = con.height,
+                s,
+                width = con.width,
+                x = con.x,
+                y = con.y;
+            if (!container) {
+                throw new Error("VML container not found.");
+            }
+            var res = new Paper,
+                c = res.canvas = doc.createElement("div"),
+                cs = c.style;
+            x = x || 0;
+            y = y || 0;
+            width = width || 512;
+            height = height || 342;
+            width == +width && (width += "px");
+            height == +height && (height += "px");
+            res.width = 1e3;
+            res.height = 1e3;
+            res.coordsize = zoom * 1e3 + S + zoom * 1e3;
+            res.coordorigin = "0 0";
+            res.span = doc.createElement("span");
+            res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
+            c[appendChild](res.span);
+            cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
+            if (container == 1) {
+                doc.body[appendChild](c);
+                cs.left = x + "px";
+                cs.top = y + "px";
+                cs.position = "absolute";
+            } else {
+                if (container.firstChild) {
+                    container.insertBefore(c, container.firstChild);
+                } else {
+                    container[appendChild](c);
+                }
+            }
+            plugins.call(res, res, R.fn);
+            return res;
+        };
+        paperproto.clear = function () {
+            this.canvas.innerHTML = E;
+            this.span = doc.createElement("span");
+            this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
+            this.canvas[appendChild](this.span);
+            this.bottom = this.top = null;
+        };
+        paperproto.remove = function () {
+            this.canvas.parentNode.removeChild(this.canvas);
+            for (var i in this) {
+                this[i] = removed(i);
+            }
+            return true;
+        };
+    }
+    // rest
+    // WebKit rendering bug workaround method
+    var version = navigator.userAgent.match(/Version\/(.*?)\s/);
+    if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP")) {
+        paperproto.safari = function () {
+            var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
+            win.setTimeout(function () {rect.remove();});
+        };
+    } else {
+        paperproto.safari = function () {};
+    }
+    // Events
+    var preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = (function () {
+        if (doc.addEventListener) {
+            return function (obj, type, fn, element) {
+                var realName = supportsTouch && touchMap[type] ? touchMap[type] : type;
+                var f = function (e) {
+                    if (supportsTouch && touchMap[has](type)) {
+                        for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                            if (e.targetTouches[i].target == obj) {
+                                var olde = e;
+                                e = e.targetTouches[i];
+                                e.originalEvent = olde;
+                                e.preventDefault = preventTouch;
+                                e.stopPropagation = stopTouch;
+                                break;
+                            }
+                        }
+                    }
+                    return fn.call(element, e);
+                };
+                obj.addEventListener(realName, f, false);
+                return function () {
+                    obj.removeEventListener(realName, f, false);
+                    return true;
+                };
+            };
+        } else if (doc.attachEvent) {
+            return function (obj, type, fn, element) {
+                var f = function (e) {
+                    e = e || win.event;
+                    e.preventDefault = e.preventDefault || preventDefault;
+                    e.stopPropagation = e.stopPropagation || stopPropagation;
+                    return fn.call(element, e);
+                };
+                obj.attachEvent("on" + type, f);
+                var detacher = function () {
+                    obj.detachEvent("on" + type, f);
+                    return true;
+                };
+                return detacher;
+            };
+        }
+    })(),
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = doc.documentElement.scrollTop || doc.body.scrollTop,
+            scrollX = doc.documentElement.scrollLeft || doc.body.scrollLeft,
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            x += scrollX;
+            y += scrollY;
+            dragi.move && dragi.move.call(dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        R.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            dragi.end && dragi.end.call(dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+        }
+        drag = [];
+    };
+    for (var i = events[length]; i--;) {
+        (function (eventName) {
+            R[eventName] = Element[proto][eventName] = function (fn, scope) {
+                if (R.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || doc, eventName, fn, scope || this)});
+                }
+                return this;
+            };
+            R["un" + eventName] = Element[proto]["un" + eventName] = function (fn) {
+                var events = this.events,
+                    l = events[length];
+                while (l--) if (events[l].name == eventName && events[l].f == fn) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    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);
+    };
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        this._drag = {};
+        this.mousedown(function (e) {
+            (e.originalEvent || e).preventDefault();
+            var scrollY = doc.documentElement.scrollTop || doc.body.scrollTop,
+                scrollX = doc.documentElement.scrollLeft || doc.body.scrollLeft;
+            this._drag.x = e.clientX + scrollX;
+            this._drag.y = e.clientY + scrollY;
+            this._drag.id = e.identifier;
+            onstart && onstart.call(start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
+            !drag.length && R.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: this, move: onmove, end: onend, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+        });
+        return this;
+    };
+    elproto.undrag = function (onmove, onstart, onend) {
+        var i = drag.length;
+        while (i--) {
+            drag[i].el == this && (drag[i].move == onmove && drag[i].end == onend) && drag.splice(i++, 1);
+        }
+        !drag.length && R.unmousemove(dragMove).unmouseup(dragUp);
+    };
+    paperproto.circle = function (x, y, r) {
+        return theCircle(this, x || 0, y || 0, r || 0);
+    };
+    paperproto.rect = function (x, y, w, h, r) {
+        return theRect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
+    };
+    paperproto.ellipse = function (x, y, rx, ry) {
+        return theEllipse(this, x || 0, y || 0, rx || 0, ry || 0);
+    };
+    paperproto.path = function (pathString) {
+        pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
+        return thePath(R.format[apply](R, arguments), this);
+    };
+    paperproto.image = function (src, x, y, w, h) {
+        return theImage(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
+    };
+    paperproto.text = function (x, y, text) {
+        return theText(this, x || 0, y || 0, Str(text));
+    };
+    paperproto.set = function (itemsArray) {
+        arguments[length] > 1 && (itemsArray = Array[proto].splice.call(arguments, 0, arguments[length]));
+        return new Set(itemsArray);
+    };
+    paperproto.setSize = setSize;
+    paperproto.top = paperproto.bottom = null;
+    paperproto.raphael = R;
+    function x_y() {
+        return this.x + S + this.y;
+    }
+    elproto.resetScale = function () {
+        if (this.removed) {
+            return this;
+        }
+        this._.sx = 1;
+        this._.sy = 1;
+        this.attrs.scale = "1 1";
+    };
+    elproto.scale = function (x, y, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        if (x == null && y == null) {
+            return {
+                x: this._.sx,
+                y: this._.sy,
+                toString: x_y
+            };
+        }
+        y = y || x;
+        !+y && (y = x);
+        var dx,
+            dy,
+            dcx,
+            dcy,
+            a = this.attrs;
+        if (x != 0) {
+            var bb = this.getBBox(),
+                rcx = bb.x + bb.width / 2,
+                rcy = bb.y + bb.height / 2,
+                kx = abs(x / this._.sx),
+                ky = abs(y / this._.sy);
+            cx = (+cx || cx == 0) ? cx : rcx;
+            cy = (+cy || cy == 0) ? cy : rcy;
+            var posx = this._.sx > 0,
+                posy = this._.sy > 0,
+                dirx = ~~(x / abs(x)),
+                diry = ~~(y / abs(y)),
+                dkx = kx * dirx,
+                dky = ky * diry,
+                s = this.node.style,
+                ncx = cx + abs(rcx - cx) * dkx * (rcx > cx == posx ? 1 : -1),
+                ncy = cy + abs(rcy - cy) * dky * (rcy > cy == posy ? 1 : -1),
+                fr = (x * dirx > y * diry ? ky : kx);
+            switch (this.type) {
+                case "rect":
+                case "image":
+                    var neww = a.width * kx,
+                        newh = a.height * ky;
+                    this.attr({
+                        height: newh,
+                        r: a.r * fr,
+                        width: neww,
+                        x: ncx - neww / 2,
+                        y: ncy - newh / 2
+                    });
+                    break;
+                case "circle":
+                case "ellipse":
+                    this.attr({
+                        rx: a.rx * kx,
+                        ry: a.ry * ky,
+                        r: a.r * fr,
+                        cx: ncx,
+                        cy: ncy
+                    });
+                    break;
+                case "text":
+                    this.attr({
+                        x: ncx,
+                        y: ncy
+                    });
+                    break;
+                case "path":
+                    var path = pathToRelative(a.path),
+                        skip = true,
+                        fx = posx ? dkx : kx,
+                        fy = posy ? dky : ky;
+                    for (var i = 0, ii = path[length]; i < ii; i++) {
+                        var p = path[i],
+                            P0 = upperCase.call(p[0]);
+                        if (P0 == "M" && skip) {
+                            continue;
+                        } else {
+                            skip = false;
+                        }
+                        if (P0 == "A") {
+                            p[path[i][length] - 2] *= fx;
+                            p[path[i][length] - 1] *= fy;
+                            p[1] *= kx;
+                            p[2] *= ky;
+                            p[5] = +(dirx + diry ? !!+p[5] : !+p[5]);
+                        } else if (P0 == "H") {
+                            for (var j = 1, jj = p[length]; j < jj; j++) {
+                                p[j] *= fx;
+                            }
+                        } else if (P0 == "V") {
+                            for (j = 1, jj = p[length]; j < jj; j++) {
+                                p[j] *= fy;
+                            }
+                         } else {
+                            for (j = 1, jj = p[length]; j < jj; j++) {
+                                p[j] *= (j % 2) ? fx : fy;
+                            }
+                        }
+                    }
+                    var dim2 = pathDimensions(path);
+                    dx = ncx - dim2.x - dim2.width / 2;
+                    dy = ncy - dim2.y - dim2.height / 2;
+                    path[0][1] += dx;
+                    path[0][2] += dy;
+                    this.attr({path: path});
+                break;
+            }
+            if (this.type in {text: 1, image:1} && (dirx != 1 || diry != 1)) {
+                if (this.transformations) {
+                    this.transformations[2] = "scale("[concat](dirx, ",", diry, ")");
+                    this.node[setAttribute]("transform", this.transformations[join](S));
+                    dx = (dirx == -1) ? -a.x - (neww || 0) : a.x;
+                    dy = (diry == -1) ? -a.y - (newh || 0) : a.y;
+                    this.attr({x: dx, y: dy});
+                    a.fx = dirx - 1;
+                    a.fy = diry - 1;
+                } else {
+                    this.node.filterMatrix = ms + ".Matrix(M11="[concat](dirx,
+                        ", M12=0, M21=0, M22=", diry,
+                        ", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')");
+                    s.filter = (this.node.filterMatrix || E) + (this.node.filterOpacity || E);
+                }
+            } else {
+                if (this.transformations) {
+                    this.transformations[2] = E;
+                    this.node[setAttribute]("transform", this.transformations[join](S));
+                    a.fx = 0;
+                    a.fy = 0;
+                } else {
+                    this.node.filterMatrix = E;
+                    s.filter = (this.node.filterMatrix || E) + (this.node.filterOpacity || E);
+                }
+            }
+            a.scale = [x, y, cx, cy][join](S);
+            this._.sx = x;
+            this._.sy = y;
+        }
+        return this;
+    };
+    elproto.clone = function () {
+        if (this.removed) {
+            return null;
+        }
+        var attr = this.attr();
+        delete attr.scale;
+        delete attr.translation;
+        return this.paper[this.type]().attr(attr);
+    };
+    var curveslengths = {},
+    getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        var len = 0,
+            precision = 100,
+            name = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y].join(),
+            cache = curveslengths[name],
+            old, dot;
+        !cache && (curveslengths[name] = cache = {data: []});
+        cache.timer && clearTimeout(cache.timer);
+        cache.timer = setTimeout(function () {delete curveslengths[name];}, 2000);
+        if (length != null) {
+            var total = getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+            precision = ~~total * 10;
+        }
+        for (var i = 0; i < precision + 1; i++) {
+            if (cache.data[length] > i) {
+                dot = cache.data[i * precision];
+            } else {
+                dot = R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, i / precision);
+                cache.data[i] = dot;
+            }
+            i && (len += pow(pow(old.x - dot.x, 2) + pow(old.y - dot.y, 2), .5));
+            if (length != null && len >= length) {
+                return dot;
+            }
+            old = dot;
+        }
+        if (length == null) {
+            return len;
+        }
+    },
+    getLengthFactory = function (istotal, subpath) {
+        return function (path, length, onlystart) {
+            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", point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = ["M", point.x, point.y + "C", point.n.x, point.n.y, point.end.x, point.end.y, p[5], 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 {x: point.x, y: point.y, alpha: point.alpha};
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[1], p[2], p[3], p[4], p[5], p[6], 1);
+            point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
+            return point;
+        };
+    };
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    elproto.getTotalLength = function () {
+        if (this.type != "path") {return;}
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+        return getTotalLength(this.attrs.path);
+    };
+    elproto.getPointAtLength = function (length) {
+        if (this.type != "path") {return;}
+        return getPointAtLength(this.attrs.path, length);
+    };
+    elproto.getSubpath = function (from, to) {
+        if (this.type != "path") {return;}
+        if (abs(this.getTotalLength() - to) < "1e-6") {
+            return getSubpathsAtLength(this.attrs.path, from).end;
+        }
+        var a = getSubpathsAtLength(this.attrs.path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+
+    // animation easing formulas
+    R.easing_formulas = {
+        linear: function (n) {
+            return n;
+        },
+        "<": function (n) {
+            return pow(n, 3);
+        },
+        ">": function (n) {
+            return pow(n - 1, 3) + 1;
+        },
+        "<>": function (n) {
+            n = n * 2;
+            if (n < 1) {
+                return pow(n, 3) / 2;
+            }
+            n -= 2;
+            return (pow(n, 3) + 2) / 2;
+        },
+        backIn: function (n) {
+            var s = 1.70158;
+            return n * n * ((s + 1) * n - s);
+        },
+        backOut: function (n) {
+            n = n - 1;
+            var s = 1.70158;
+            return n * n * ((s + 1) * n + s) + 1;
+        },
+        elastic: function (n) {
+            if (n == 0 || n == 1) {
+                return n;
+            }
+            var p = .3,
+                s = p / 4;
+            return pow(2, -10 * n) * math.sin((n - s) * (2 * PI) / p) + 1;
+        },
+        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;
+        }
+    };
+
+    var animationElements = [],
+        animation = function () {
+            var Now = +new Date;
+            for (var l = 0; l < animationElements[length]; l++) {
+                var e = animationElements[l];
+                if (e.stop || e.el.removed) {
+                    continue;
+                }
+                var time = Now - e.start,
+                    ms = e.ms,
+                    easing = e.easing,
+                    from = e.from,
+                    diff = e.diff,
+                    to = e.to,
+                    t = e.t,
+                    that = e.el,
+                    set = {},
+                    now;
+                if (time < ms) {
+                    var pos = easing(time / ms);
+                    for (var attr in from) if (from[has](attr)) {
+                        switch (availableAnimAttrs[attr]) {
+                            case "along":
+                                now = pos * ms * diff[attr];
+                                to.back && (now = to.len - now);
+                                var point = getPointAtLength(to[attr], now);
+                                that.translate(diff.sx - diff.x || 0, diff.sy - diff.y || 0);
+                                diff.x = point.x;
+                                diff.y = point.y;
+                                that.translate(point.x - diff.sx, point.y - diff.sy);
+                                to.rot && that.rotate(diff.r + point.alpha, point.x, point.y);
+                                break;
+                            case nu:
+                                now = +from[attr] + pos * ms * diff[attr];
+                                break;
+                            case "colour":
+                                now = "rgb(" + [
+                                    upto255(round(from[attr].r + pos * ms * diff[attr].r)),
+                                    upto255(round(from[attr].g + pos * ms * diff[attr].g)),
+                                    upto255(round(from[attr].b + pos * ms * diff[attr].b))
+                                ][join](",") + ")";
+                                break;
+                            case "path":
+                                now = [];
+                                for (var i = 0, ii = from[attr][length]; i < ii; i++) {
+                                    now[i] = [from[attr][i][0]];
+                                    for (var j = 1, jj = from[attr][i][length]; j < jj; j++) {
+                                        now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
+                                    }
+                                    now[i] = now[i][join](S);
+                                }
+                                now = now[join](S);
+                                break;
+                            case "csv":
+                                switch (attr) {
+                                    case "translation":
+                                        var x = pos * ms * diff[attr][0] - t.x,
+                                            y = pos * ms * diff[attr][1] - t.y;
+                                        t.x += x;
+                                        t.y += y;
+                                        now = x + S + y;
+                                    break;
+                                    case "rotation":
+                                        now = +from[attr][0] + pos * ms * diff[attr][0];
+                                        from[attr][1] && (now += "," + from[attr][1] + "," + from[attr][2]);
+                                    break;
+                                    case "scale":
+                                        now = [+from[attr][0] + pos * ms * diff[attr][0], +from[attr][1] + pos * ms * diff[attr][1], (2 in to[attr] ? to[attr][2] : E), (3 in to[attr] ? to[attr][3] : E)][join](S);
+                                    break;
+                                    case "clip-rect":
+                                        now = [];
+                                        i = 4;
+                                        while (i--) {
+                                            now[i] = +from[attr][i] + pos * ms * diff[attr][i];
+                                        }
+                                    break;
+                                }
+                                break;
+                            default:
+                              var from2 = [].concat(from[attr]);
+                                now = [];
+                                i = that.paper.customAttributes[attr].length;
+                                while (i--) {
+                                    now[i] = +from2[i] + pos * ms * diff[attr][i];
+                                }
+                                break;
+                        }
+                        set[attr] = now;
+                    }
+                    that.attr(set);
+                    that._run && that._run.call(that);
+                } else {
+                    if (to.along) {
+                        point = getPointAtLength(to.along, to.len * !to.back);
+                        that.translate(diff.sx - (diff.x || 0) + point.x - diff.sx, diff.sy - (diff.y || 0) + point.y - diff.sy);
+                        to.rot && that.rotate(diff.r + point.alpha, point.x, point.y);
+                    }
+                    (t.x || t.y) && that.translate(-t.x, -t.y);
+                    to.scale && (to.scale += E);
+                    that.attr(to);
+                    animationElements.splice(l--, 1);
+                }
+            }
+            R.svg && that && that.paper && that.paper.safari();
+            animationElements[length] && setTimeout(animation);
+        },
+        keyframesRun = function (attr, element, time, prev, prevcallback) {
+            var dif = time - prev;
+            element.timeouts.push(setTimeout(function () {
+                R.is(prevcallback, "function") && prevcallback.call(element);
+                element.animate(attr, dif, attr.easing);
+            }, prev));
+        },
+        upto255 = function (color) {
+            return mmax(mmin(color, 255), 0);
+        },
+        translate = function (x, y) {
+            if (x == null) {
+                return {x: this._.tx, y: this._.ty, toString: x_y};
+            }
+            this._.tx += +x;
+            this._.ty += +y;
+            switch (this.type) {
+                case "circle":
+                case "ellipse":
+                    this.attr({cx: +x + this.attrs.cx, cy: +y + this.attrs.cy});
+                    break;
+                case "rect":
+                case "image":
+                case "text":
+                    this.attr({x: +x + this.attrs.x, y: +y + this.attrs.y});
+                    break;
+                case "path":
+                    var path = pathToRelative(this.attrs.path);
+                    path[0][1] += +x;
+                    path[0][2] += +y;
+                    this.attr({path: path});
+                break;
+            }
+            return this;
+        };
+    elproto.animateWith = function (element, params, ms, easing, callback) {
+        for (var i = 0, ii = animationElements.length; i < ii; i++) {
+            if (animationElements[i].el.id == element.id) {
+                params.start = animationElements[i].start;
+            }
+        }
+        return this.animate(params, ms, easing, callback);
+    };
+    elproto.animateAlong = along();
+    elproto.animateAlongBack = along(1);
+    function along(isBack) {
+        return function (path, ms, rotate, callback) {
+            var params = {back: isBack};
+            R.is(rotate, "function") ? (callback = rotate) : (params.rot = rotate);
+            path && path.constructor == Element && (path = path.attrs.path);
+            path && (params.along = path);
+            return this.animate(params, ms, callback);
+        };
+    }
+    function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
+        var cx = 3 * p1x,
+            bx = 3 * (p2x - p1x) - cx,
+            ax = 1 - cx - bx,
+            cy = 3 * p1y,
+            by = 3 * (p2y - p1y) - cy,
+            ay = 1 - cy - by;
+        function sampleCurveX(t) {
+            return ((ax * t + bx) * t + cx) * t;
+        }
+        function solve(x, epsilon) {
+            var t = solveCurveX(x, epsilon);
+            return ((ay * t + by) * t + cy) * t;
+        }
+        function solveCurveX(x, epsilon) {
+            var t0, t1, t2, x2, d2, i;
+            for(t2 = x, i = 0; i < 8; i++) {
+                x2 = sampleCurveX(t2) - x;
+                if (abs(x2) < epsilon) {
+                    return t2;
+                }
+                d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
+                if (abs(d2) < 1e-6) {
+                    break;
+                }
+                t2 = t2 - x2 / d2;
+            }
+            t0 = 0;
+            t1 = 1;
+            t2 = x;
+            if (t2 < t0) {
+                return t0;
+            }
+            if (t2 > t1) {
+                return t1;
+            }
+            while (t0 < t1) {
+                x2 = sampleCurveX(t2);
+                if (abs(x2 - x) < epsilon) {
+                    return t2;
+                }
+                if (x > x2) {
+                    t0 = t2;
+                } else {
+                    t1 = t2;
+                }
+                t2 = (t1 - t0) / 2 + t0;
+            }
+            return t2;
+        }
+        return solve(t, 1 / (200 * duration));
+    }
+    elproto.onAnimation = function (f) {
+        this._run = f || 0;
+        return this;
+    };
+    elproto.animate = function (params, ms, easing, callback) {
+        var element = this;
+        element.timeouts = element.timeouts || [];
+        if (R.is(easing, "function") || !easing) {
+            callback = easing || null;
+        }
+        if (element.removed) {
+            callback && callback.call(element);
+            return element;
+        }
+        var from = {},
+            to = {},
+            animateable = false,
+            diff = {};
+        for (var attr in params) if (params[has](attr)) {
+            if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
+                animateable = true;
+                from[attr] = element.attr(attr);
+                (from[attr] == null) && (from[attr] = availableAttrs[attr]);
+                to[attr] = params[attr];
+                switch (availableAnimAttrs[attr]) {
+                    case "along":
+                        var len = getTotalLength(params[attr]);
+                        var point = getPointAtLength(params[attr], len * !!params.back);
+                        var bb = element.getBBox();
+                        diff[attr] = len / ms;
+                        diff.tx = bb.x;
+                        diff.ty = bb.y;
+                        diff.sx = point.x;
+                        diff.sy = point.y;
+                        to.rot = params.rot;
+                        to.back = params.back;
+                        to.len = len;
+                        params.rot && (diff.r = toFloat(element.rotate()) || 0);
+                        break;
+                    case nu:
+                        diff[attr] = (to[attr] - from[attr]) / ms;
+                        break;
+                    case "colour":
+                        from[attr] = R.getRGB(from[attr]);
+                        var toColour = R.getRGB(to[attr]);
+                        diff[attr] = {
+                            r: (toColour.r - from[attr].r) / ms,
+                            g: (toColour.g - from[attr].g) / ms,
+                            b: (toColour.b - from[attr].b) / ms
+                        };
+                        break;
+                    case "path":
+                        var pathes = path2curve(from[attr], to[attr]);
+                        from[attr] = pathes[0];
+                        var toPath = pathes[1];
+                        diff[attr] = [];
+                        for (var i = 0, ii = from[attr][length]; i < ii; i++) {
+                            diff[attr][i] = [0];
+                            for (var j = 1, jj = from[attr][i][length]; j < jj; j++) {
+                                diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
+                            }
+                        }
+                        break;
+                    case "csv":
+                        var values = Str(params[attr])[split](separator),
+                            from2 = Str(from[attr])[split](separator);
+                        switch (attr) {
+                            case "translation":
+                                from[attr] = [0, 0];
+                                diff[attr] = [values[0] / ms, values[1] / ms];
+                            break;
+                            case "rotation":
+                                from[attr] = (from2[1] == values[1] && from2[2] == values[2]) ? from2 : [0, values[1], values[2]];
+                                diff[attr] = [(values[0] - from[attr][0]) / ms, 0, 0];
+                            break;
+                            case "scale":
+                                params[attr] = values;
+                                from[attr] = Str(from[attr])[split](separator);
+                                diff[attr] = [(values[0] - from[attr][0]) / ms, (values[1] - from[attr][1]) / ms, 0, 0];
+                            break;
+                            case "clip-rect":
+                                from[attr] = Str(from[attr])[split](separator);
+                                diff[attr] = [];
+                                i = 4;
+                                while (i--) {
+                                    diff[attr][i] = (values[i] - from[attr][i]) / ms;
+                                }
+                            break;
+                        }
+                        to[attr] = values;
+                        break;
+                    default:
+                        values = [].concat(params[attr]);
+                        from2 = [].concat(from[attr]);
+                        diff[attr] = [];
+                        i = element.paper.customAttributes[attr][length];
+                        while (i--) {
+                            diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
+                        }
+                        break;
+                }
+            }
+        }
+        if (!animateable) {
+            var attrs = [],
+                lastcall;
+            for (var key in params) if (params[has](key) && animKeyFrames.test(key)) {
+                attr = {value: params[key]};
+                key == "from" && (key = 0);
+                key == "to" && (key = 100);
+                attr.key = toInt(key, 10);
+                attrs.push(attr);
+            }
+            attrs.sort(sortByKey);
+            if (attrs[0].key) {
+                attrs.unshift({key: 0, value: element.attrs});
+            }
+            for (i = 0, ii = attrs[length]; i < ii; i++) {
+                keyframesRun(attrs[i].value, element, ms / 100 * attrs[i].key, ms / 100 * (attrs[i - 1] && attrs[i - 1].key || 0), attrs[i - 1] && attrs[i - 1].value.callback);
+            }
+            lastcall = attrs[attrs[length] - 1].value.callback;
+            if (lastcall) {
+                element.timeouts.push(setTimeout(function () {lastcall.call(element);}, ms));
+            }
+        } else {
+            var easyeasy = R.easing_formulas[easing];
+            if (!easyeasy) {
+                easyeasy = Str(easing).match(bezierrg);
+                if (easyeasy && easyeasy[length] == 5) {
+                    var curve = easyeasy;
+                    easyeasy = function (t) {
+                        return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
+                    };
+                } else {
+                    easyeasy = function (t) {
+                        return t;
+                    };
+                }
+            }
+            animationElements.push({
+                start: params.start || +new Date,
+                ms: ms,
+                easing: easyeasy,
+                from: from,
+                diff: diff,
+                to: to,
+                el: element,
+                t: {x: 0, y: 0}
+            });
+            R.is(callback, "function") && (element._ac = setTimeout(function () {
+                callback.call(element);
+            }, ms));
+            animationElements[length] == 1 && setTimeout(animation);
+        }
+        return this;
+    };
+    elproto.stop = function () {
+        for (var i = 0; i < animationElements.length; i++) {
+            animationElements[i].el.id == this.id && animationElements.splice(i--, 1);
+        }
+        for (i = 0, ii = this.timeouts && this.timeouts.length; i < ii; i++) {
+            clearTimeout(this.timeouts[i]);
+        }
+        this.timeouts = [];
+        clearTimeout(this._ac);
+        delete this._ac;
+        return this;
+    };
+    elproto.translate = function (x, y) {
+        return this.attr({translation: x + " " + y});
+    };
+    elproto[toString] = function () {
+        return "Rapha\xebl\u2019s object";
+    };
+    R.ae = animationElements;
+    // Set
+    var Set = function (items) {
+        this.items = [];
+        this[length] = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items[length]; i < ii; i++) {
+                if (items[i] && (items[i].constructor == Element || items[i].constructor == Set)) {
+                    this[this.items[length]] = this.items[this.items[length]] = items[i];
+                    this[length]++;
+                }
+            }
+        }
+    };
+    Set[proto][push] = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments[length]; i < ii; i++) {
+            item = arguments[i];
+            if (item && (item.constructor == Element || item.constructor == Set)) {
+                len = this.items[length];
+                this[len] = this.items[len] = item;
+                this[length]++;
+            }
+        }
+        return this;
+    };
+    Set[proto].pop = function () {
+        delete this[this[length]--];
+        return this.items.pop();
+    };
+    for (var method in elproto) if (elproto[has](method)) {
+        Set[proto][method] = (function (methodname) {
+            return function () {
+                for (var i = 0, ii = this.items[length]; i < ii; i++) {
+                    this.items[i][methodname][apply](this.items[i], arguments);
+                }
+                return this;
+            };
+        })(method);
+    }
+    Set[proto].attr = function (name, value) {
+        if (name && R.is(name, array) && R.is(name[0], "object")) {
+            for (var j = 0, jj = name[length]; j < jj; j++) {
+                this.items[j].attr(name[j]);
+            }
+        } else {
+            for (var i = 0, ii = this.items[length]; i < ii; i++) {
+                this.items[i].attr(name, value);
+            }
+        }
+        return this;
+    };
+    Set[proto].animate = function (params, ms, easing, callback) {
+        (R.is(easing, "function") || !easing) && (callback = easing || null);
+        var len = this.items[length],
+            i = len,
+            item,
+            set = this,
+            collector;
+        callback && (collector = function () {
+            !--len && callback.call(set);
+        });
+        easing = R.is(easing, string) ? easing : collector;
+        item = this.items[--i].animate(params, ms, easing, collector);
+        while (i--) {
+            this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, params, ms, easing, collector);
+        }
+        return this;
+    };
+    Set[proto].insertAfter = function (el) {
+        var i = this.items[length];
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    Set[proto].getBBox = function () {
+        var x = [],
+            y = [],
+            w = [],
+            h = [];
+        for (var i = this.items[length]; i--;) {
+            var box = this.items[i].getBBox();
+            x[push](box.x);
+            y[push](box.y);
+            w[push](box.x + box.width);
+            h[push](box.y + box.height);
+        }
+        x = mmin[apply](0, x);
+        y = mmin[apply](0, y);
+        return {
+            x: x,
+            y: y,
+            width: mmax[apply](0, w) - x,
+            height: mmax[apply](0, h) - y
+        };
+    };
+    Set[proto].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;
+    };
+
+    R.registerFont = function (font) {
+        if (!font.face) {
+            return font;
+        }
+        this.fonts = this.fonts || {};
+        var fontcopy = {
+                w: font.w,
+                face: {},
+                glyphs: {}
+            },
+            family = font.face["font-family"];
+        for (var prop in font.face) if (font.face[has](prop)) {
+            fontcopy.face[prop] = font.face[prop];
+        }
+        if (this.fonts[family]) {
+            this.fonts[family][push](fontcopy);
+        } else {
+            this.fonts[family] = [fontcopy];
+        }
+        if (!font.svg) {
+            fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
+            for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
+                var path = font.glyphs[glyph];
+                fontcopy.glyphs[glyph] = {
+                    w: path.w,
+                    k: {},
+                    d: path.d && "M" + path.d[rp](/[mlcxtrv]/g, function (command) {
+                            return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
+                        }) + "z"
+                };
+                if (path.k) {
+                    for (var k in path.k) if (path[has](k)) {
+                        fontcopy.glyphs[glyph].k[k] = path.k[k];
+                    }
+                }
+            }
+        }
+        return font;
+    };
+    paperproto.getFont = function (family, weight, style, stretch) {
+        stretch = stretch || "normal";
+        style = style || "normal";
+        weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
+        if (!R.fonts) {
+            return;
+        }
+        var font = R.fonts[family];
+        if (!font) {
+            var name = new RegExp("(^|\\s)" + family[rp](/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
+            for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
+                if (name.test(fontName)) {
+                    font = R.fonts[fontName];
+                    break;
+                }
+            }
+        }
+        var thefont;
+        if (font) {
+            for (var i = 0, ii = font[length]; i < ii; i++) {
+                thefont = font[i];
+                if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
+                    break;
+                }
+            }
+        }
+        return thefont;
+    };
+    paperproto.print = function (x, y, string, font, size, origin, letter_spacing) {
+        origin = origin || "middle"; // baseline|middle
+        letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
+        var out = this.set(),
+            letters = Str(string)[split](E),
+            shift = 0,
+            path = E,
+            scale;
+        R.is(font, string) && (font = this.getFont(font));
+        if (font) {
+            scale = (size || 16) / font.face["units-per-em"];
+            var bb = font.face.bbox.split(separator),
+                top = +bb[0],
+                height = +bb[1] + (origin == "baseline" ? bb[3] - bb[1] + (+font.face.descent) : (bb[3] - bb[1]) / 2);
+            for (var i = 0, ii = letters[length]; i < ii; i++) {
+                var prev = i && font.glyphs[letters[i - 1]] || {},
+                    curr = font.glyphs[letters[i]];
+                shift += i ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
+                curr && curr.d && out[push](this.path(curr.d).attr({fill: "#000", stroke: "none", translation: [shift, 0]}));
+            }
+            out.scale(scale, scale, top, height).translate(x - top, y - height);
+        }
+        return out;
+    };
+
+    R.format = function (token, params) {
+        var args = R.is(params, array) ? [0][concat](params) : arguments;
+        token && R.is(token, string) && args[length] - 1 && (token = token[rp](formatrg, function (str, i) {
+            return args[++i] == null ? E : args[i];
+        }));
+        return token || E;
+    };
+    R.ninja = function () {
+        oldRaphael.was ? (win.Raphael = oldRaphael.is) : delete Raphael;
+        return R;
+    };
+    R.el = elproto;
+    R.st = Set[proto];
+
+    oldRaphael.was ? (win.Raphael = R) : (Raphael = R);
+})();
\ No newline at end of file
diff --git a/lib/uki-0.3.8.js b/lib/uki-0.3.8.js
new file mode 100644 (file)
index 0000000..e748c85
--- /dev/null
@@ -0,0 +1,8022 @@
+/*!
+ * uki JavaScript Library
+ * Licensed under the MIT license http://ukijs.org/LICENSE
+ *
+ * Copyright (c) 2010 Vladimir Kolesnikov
+ *
+ * Parts of code derived from jQuery JavaScript Library
+ * Copyright (c) 2009 John Resig
+ */
+(function() {
+  /**
+ * Global uki constants, for speed optimization and better merging
+ */
+/** @ignore */
+var root = this,
+    doc  = document,
+    nav = navigator,
+    ua  = nav.userAgent,
+    expando = 'uki' + (+new Date),
+    
+    MAX = Math.max,
+    MIN = Math.min,
+    FLOOR = Math.floor,
+    CEIL = Math.ceil,
+    
+    PX = 'px';
+
+
+
+/**
+ * Shortcut access to uki.build, uki.Selector.find and uki.Collection constructor
+ * uki('#id') is also a shortcut for search by id
+ *
+ * @param {String|uki.view.Base|Object|Array.<uki.view.Base>} val
+ * @param {Array.<uki.view.Base>=} optional context for selector
+ * @class
+ * @namespace
+ * @name uki
+ * @return {uki.Collection}
+ */
+root.uki = root.uki || function(val, context) {
+    if (typeof val === "string") {
+       
+        var m = val.match(/^#((?:[\w\u00c0-\uFFFF_-]|\\.)+)$/),
+            e = m && uki._ids[m[1]];
+        if (m && !context) {
+            return new uki.Collection( e ? [e] : [] );
+        }
+        return uki.find(val, context);
+               
+    }
+    if (val.length === undefined) val = [val];
+    if (val.length > 0 && uki.isFunction(val[0].typeName)) return new uki.Collection(val);
+       
+    return uki.build(val);
+};
+
+/**
+ * @type string
+ * @field
+ */
+uki.version = '0.3.8';
+uki.guid = 1;
+
+/**
+ * Empty function
+ * @type function():boolean
+ */
+uki.F = function() { return false; };
+uki._ids = {};
+
+uki.registerId = function(comp) {
+    uki._ids[ uki.attr(comp, 'id') ] = comp;
+}; 
+uki.unregisterId = function(comp) {
+    uki._ids[ uki.attr(comp, 'id') ] = undefined;
+};
+
+
+
+(function() {
+    
+var toString = Object.prototype.toString,
+    trim = String.prototype.trim,
+    slice = Array.prototype.slice,
+    
+    trimRe = /^\s+|\s+$/g;
+    
+var marked = '__uki_marked';
+
+// dummy subclass
+/** @ignore */
+function inheritance () {}
+
+/**
+ * Utility functions.
+ */
+var utils = {
+    
+    /**
+     * Sets or retrieves attribute on an object. 
+     * <p>If target has function with attr it will be called target[attr](value=)
+     * If no function present attribute will be set/get directly: target[attr] = value or return target[attr]</p>
+     *
+     * @example
+     *   uki.attr(view, 'name', 'funny') // sets name to funny on view
+     *   uki.attr(view, 'id') // gets id attribute of view
+     *
+     * @param {object} target
+     * @param {string} attr Attribute name
+     * @param {object=} value Value to set
+     * @returns {object} target if value is being set, retrieved value otherwise
+     */
+    attr: function(target, attr, value) {
+        if (value !== undefined) {
+            if (target[attr] && target[attr].apply) {
+            // if (uki.isFunction(target[attr])) {
+                target[attr](value);
+            } else {
+                target[attr] = value;
+            }
+            return target;
+        } else {
+            if (target[attr] && target[attr].apply) {
+            // if (uki.isFunction(target[attr])) {
+                return target[attr]();
+            } else {
+                return target[attr];
+            }
+        }
+    },
+    
+    /**
+     * Runs a function in a given context
+     *
+     * @param {function()} fn
+     * @param {object} context
+     */
+    proxy: function(fn, context) {
+        var args = slice.call(arguments, 2),
+            result = function() {
+                return fn.apply(context, args.concat(slice.call(arguments, 0)));
+            };
+        result.huid = fn.huid = fn.huid || uki.guid++;
+        return result;
+    },
+    
+    /**
+     * Checks if obj is a function
+     *
+     * @param {object} object Object to check
+     * @returns {boolean}
+     */
+       isFunction: function( obj ) {
+               return toString.call(obj) === "[object Function]";
+       },
+
+    /**
+     * Checks if obj is an Array
+     *
+     * @param {object} object Object to check
+     * @returns {boolean}
+     */
+    isArray: function( obj ) {
+        return toString.call(obj) === "[object Array]";
+    },
+    
+    /**
+     * Trims the string
+     *
+     * @param {string} text 
+     * @returns {string} trimmed text
+     */
+    trim: function( text ) {
+        text = text || '';
+        return trim ? trim.call(text) : text.replace( trimRe, "" );
+    },
+    
+    /**
+     * Converts unsafe symbols to entities
+     *
+     * @param {string} html 
+     * @returns {string} escaped html
+     */
+    escapeHTML: function( html ) {
+        var trans = {
+            '&': '&amp;',
+            '<': '&lt;',
+            '>': '&gt;',
+            '"': '&quot;',
+            "'": '&#x27;'
+        };
+        return (html + '').replace(/[&<>\"\']/g, function(c) { return trans[c]; });
+    },
+    
+    /**
+     * Iterates through all non empty values of object or an Array
+     *
+     * @param {object|Array} object Object to iterate through
+     * @param {function(number, object):boolean} callback Called for every item, may return false to stop iteration
+     * @param {object} context Context in which callback should called. If not specified context will be set to
+     *                         current item
+     * @returns {object}
+     */
+    each: function( object, callback, context ) {
+        var name, i = 0, length = object.length;
+
+        if ( length === undefined ) {
+            for ( name in object ) {
+                if ( !name || object[ name ] === undefined || !object.hasOwnProperty(name) ) continue;
+                if ( callback.call( context || object[ name ], name, object[ name ] ) === false ) { break; }
+            }
+        } else {
+            for ( var value = object[0]; i < length && callback.call( context || value, i, value ) !== false; value = object[++i] ){}
+        }
+        return object;
+    },
+    
+    /**
+     * Checks if elem is in array
+     *
+     * @param {object} elem
+     * @param {object} array
+     * @returns {boolean}
+     */
+    inArray: function( elem, array ) {
+        for ( var i = 0, length = array.length; i < length; i++ ) {
+            if ( array[ i ] === elem ) { return i; }
+        }
+
+        return -1;
+    },
+
+    /**
+     * Returns unique elements in array
+     *
+     * @param {Array} array
+     * @returns {Array}
+     */
+    unique: function( array ) {
+        if (array.length && (typeof array[0] == 'object' || typeof array[0] == 'function')) {
+           var result = [],
+               i;
+
+           for (i = 0; i < array.length; i++) { 
+               if (!array[i][marked]) { result[result.length] = array[i]; }
+               array[i][marked] = true;
+           };
+           for (i = 0; i < result.length; i++) { 
+               delete result[i][marked];
+           };
+           return result;
+               
+        } else {
+        
+            var ret = [], 
+                done = {};
+
+            for ( var i = 0, length = array.length; i < length; i++ ) {
+                var id = array[ i ];
+
+                if ( !done[ id ] ) {
+                    done[ id ] = true;
+                    ret.push( array[ i ] );
+                }
+            }
+
+            return ret;
+        }
+    },
+    
+    /**
+     * Searches for all items matching given criteria
+     *
+     * @param {Array} elems Element to search through
+     * @param {function(object, number)} callback Returns true for every matched element
+     * @returns {Array} matched elements
+     */
+    grep: function( elems, callback ) {
+        var ret = [];
+
+        for ( var i = 0, length = elems.length; i < length; i++ ) {
+            if ( callback( elems[ i ], i ) ) { ret.push( elems[ i ] ); }
+        }
+
+        return ret;
+    },
+    
+    /**
+     * Maps elements passing them to callback
+     * @example
+     *   x = uki.map([1, 2, 3], function(item) { return -item }); 
+     *
+     * @param {Array} elems Elements to map
+     * @param {function(object, number)} mapping function
+     * @param {object} context Context in which callback should called. If not specified context will be set to
+     *                         current item
+     * @returns {Array} mapped values
+     */
+    map: function( elems, callback, context ) {
+        var ret = [],
+            mapper = uki.isFunction(callback) ? callback : 
+                     function(e) { return uki.attr(e, callback); };
+
+        for ( var i = 0, length = elems.length; i < length; i++ ) {
+            var value = mapper.call( context || elems[ i ], elems[ i ], i );
+
+            if ( value != null ) { ret[ ret.length ] = value; }
+        }
+
+        return ret;
+    },
+    
+    /**
+     * Reduces array
+     * @example
+     *   x = uki.reduce(1, [1, 2, 3], function(p, x) { return p * x}) // calculates product
+     *
+     * @param {object} initial Initial value
+     * @param {Array} elems Elements to reduce
+     * @param {function(object, number)} reduce function
+     * @param {object} context Context in which callback should called. If not specified context will be set to
+     *                         current item
+     * @returns {object}
+     */
+    reduce: function( initial, elems, callback, context ) {
+        for ( var i = 0, length = elems.length; i < length; i++ ) {
+            initial = callback.call( context || elems[ i ], initial, elems[ i ], i );
+        }
+        return initial;
+    },
+
+    /**
+     * Copies properties from one object to another
+     * @example
+     *   uki.extend(x, { width: 13, height: 14 }) // sets x.width = 13, x.height = 14
+     *   options = uki.extend({}, defaultOptions, options)
+     *
+     * @param {object} target Object to copy properties into
+     * @param {...object} sources Objects to take properties from
+     * @returns Describe what it returns
+     */
+    extend: function() {
+        var target = arguments[0] || {}, i = 1, length = arguments.length, options;
+               
+        for ( ; i < length; i++ ) {
+            if ( (options = arguments[i]) != null ) {
+                
+                for ( var name in options ) {
+                    var copy = options[ name ];
+                                       
+                    if ( copy !== undefined ) {
+                        target[ name ] = copy;
+                    }
+
+                }
+            }
+        }
+               
+        return target;      
+    },
+    
+    /**
+     * Creates a new class inherited from base classes. Init function is used as constructor
+     * @example
+     *   baseClass = uki.newClass({
+     *      init: function() { this.x = 3 }
+     *   });
+     *
+     *   childClass = uki.newClass(baseClass, {
+     *      getSqrt: function() { return this.x*this.x }
+     *   });
+     *
+     * @param {object=} superClass If superClass has prototype "real" prototype base inheritance is used,
+     *                             otherwise superClass properties are simply copied to newClass prototype
+     * @param {Array.<object>=} mixins
+     * @param {object} methods
+     * @returns Describe what it returns
+     */
+    newClass: function(/* [[superClass], mixin1, mixin2, ..] */ methods) {
+        var klass = function() {
+                this.init.apply(this, arguments);
+            },
+            
+                       i, startFrom = 0, tmp, baseClasses = [], base, name, copy, $arguments = arguments, length;
+               
+        if ((length = $arguments.length) > 1) {
+            base = $arguments[0];
+            if (base.prototype) { // real inheritance
+                inheritance.prototype = base.prototype;
+                klass.prototype = new inheritance();
+                startFrom = 1;
+                baseClasses = [inheritance.prototype];
+                
+                // class method inheritance
+                for ( name in base ) {
+                    copy = base[ name ];
+                    if ( !base.hasOwnProperty(name) || copy === undefined || name == 'prototype' ) continue;
+                    klass[ name ] = copy;
+                }
+            }
+        }
+
+        for (i=startFrom; i < length; i++) {
+            base = $arguments[i];
+            if (this.isFunction(base)) {
+                tmp = {};
+                base.apply(tmp, baseClasses);
+                base = tmp;
+            }
+            baseClasses[ baseClasses.length ] = base;
+            
+            uki.extend(klass.prototype, base);
+        };
+        if (!klass.prototype.init) klass.prototype.init = function() {};
+        return klass;
+    },
+    
+    /**
+     * Search closest value in a sorted array
+     * @param {nubmer} value to search
+     * @param {array} array sorted array
+     * @returns {number} index of closest value
+     */
+    binarySearch: function (value, array) {
+        var low = 0, high = array.length, mid;
+               
+        while (low < high) {
+            mid = (low + high) >> 1;
+            array[mid] < value ? low = mid + 1 : high = mid;
+        }
+        
+        return low;
+    },
+    
+    
+    /**
+     * Creates default uki property function
+     * <p>If value is given to this function it sets property to value
+     * If no arguments given than function returns current property value</p>
+     *
+     * <p>Optional setter can be given. In this case setter will be called instead
+     * of simple this[field] = value</p>
+     *
+     * <p>If used as setter function returns self</p>
+     *   
+     * @example
+     *   x.width = uki.newProperty('_width');
+     *   x.width(12); // x._width = 12
+     *   x.width();   // return 12
+     *
+     * @param {string} field Field name
+     * @param {function(object)=} setter
+     * @returns {function(object=):object}
+     */
+    newProp: function(field, setter) {
+        return function(value) {
+            if (value === undefined) return this[field];
+            if (setter) { setter.call(this, value); } else { this[field] = value; };
+            return this;
+        };
+    },
+    
+    /**
+     * Adds several properties (uki.newProp) to a given object.
+     * <p>Field name equals to '_' + property name</p>
+     *
+     * @example
+     *   uki.addProps(x, ['width', 'height'])
+     *
+     * @param {object} proto Object to add properties to
+     * @param {Array.<string>} props Property names
+     */
+    addProps: function(proto, props) {
+        for (var i =0, len = props.length; i<len; i++)
+                       proto[ props[i] ] = uki.newProp('_' + props[i]);
+    },
+    
+    toArray: function(arr) {
+        return slice.call(arr, 0);
+    },
+    
+    delegateProp: function(proto, name, target) {
+        var propName = '_' + name;
+        proto[name] = function(value) {
+            if (value === undefined) {
+                if (this[target]) return uki.attr(this[target], name, value);
+                return this[propName];
+            }
+            if (this[target]) {
+                uki.attr(this[target], name, value);
+            } else {
+                this[propName] = value;
+            }
+            return this;
+        };
+    },
+    
+    camalize: function(string) {
+        return string.replace(/[-_]\S/g, function(v) {
+            return v.substr(1).toUpperCase();
+        });
+    },
+    
+    dasherize: function(string) {
+        return string.replace(/[A-Z]/g, function(v) {
+            return '-' + v.toLowerCase();
+        });
+    }
+};
+utils.extend(uki, utils);
+
+})();
+
+
+/** 
+ * Geometry 
+ * 
+ * @namespace
+ */
+uki.geometry = {};
+
+
+/**
+ * Point with x and y properties
+ *
+ * @author voloko
+ * @name uki.geometry.Point
+ * @constructor
+ *
+ * @param {Integer=} x defaults to 0
+ * @param {Integer=} y defaults to 0
+ */
+var Point = uki.geometry.Point = function(x, y) {
+    this.x = x*1.0 || 0.0;
+    this.y = y*1.0 || 0.0;
+};
+
+Point.prototype = /** @lends uki.geometry.Point.prototype */ {
+    
+    /**
+     * Converts to "100 50" string
+     *
+     * @this {uki.geometry.Point}
+     * @return {string}
+     */
+    toString: function() {
+        return this.x + ' ' + this.y;
+    },
+    
+    /**
+     * Creates a new Point with the same properties
+     *
+     * @this {uki.geometry.Point}
+     * @return {uki.geometry.Point}
+     */
+    clone: function() {
+        return new Point(this.x, this.y);
+    },
+    
+    /**
+     * Checks if this equals to another Point
+     *
+     * @param {uki.geometry.Point} point Point to compare with
+     * @this {uki.geometry.Point}
+     * @return {boolean}
+     */
+    eq: function(point) {
+        return this.x == point.x && this.y == point.y;
+    },
+    
+    /**
+     * Moves point by x, y
+     *
+     * @this {uki.geometry.Point}
+     * @return {uki.geometry.Point} self
+     */
+    offset: function(x, y) {
+        if (typeof x == 'object') {
+            y = x.y;
+            x = x.x;
+        }
+        this.x += x;
+        this.y += y;
+        return this;
+    },
+    
+    constructor: Point
+};
+
+/**
+ * Creates point from "x y" string
+ *
+ * @memberOf uki.geometry.Point
+ * @name fromString
+ * @function
+ *
+ * @param {string} string String representation of point
+ *
+ * @returns {uki.geometry.Point} created point
+ */
+Point.fromString = function(string) {
+    var parts = string.split(/\s+/);
+    return new Point( parts[0], parts[1] );
+};
+
+
+/**
+ * Size with width and height properties
+ *
+ * @param {number=} width defaults to 0
+ * @param {number=} height defaults to 0
+ * @name uki.geometry.Size
+ * @constructor
+ */
+var Size = uki.geometry.Size = function(width, height) {
+    this.width  = width*1.0 || 0.0;
+    this.height = height*1.0 || 0.0;
+};
+
+Size.prototype = /** @lends uki.geometry.Size.prototype */ {
+    /**
+     * Converts size to "300 100" string
+     *
+     * @this {uki.geometry.Size}
+     * @return {string} 
+     */
+    toString: function() {
+        return this.width + ' ' + this.height;
+    },
+    
+    /**
+     * Creates a new Size with same properties
+     *
+     * @this {uki.geometry.Size}
+     * @return {uki.geometry.Size} new Size
+     */
+    clone: function() {
+        return new Size(this.width, this.height);
+    },
+    
+    /**
+     * Checks if this equals to another Size
+     *
+     * @param {uki.geometry.Size} size Size to compare with
+     * @this {uki.geometry.Size}
+     * @return {boolean}
+     */
+    eq: function(size) {
+        return this.width == size.width && this.height == size.height;
+    },
+    
+    /**
+     * Checks if this size has non-positive width or height
+     *
+     * @this {uki.geometry.Size}
+     * @return {boolean}
+     */
+    empty: function() {
+        return this.width <= 0 || this.height <= 0;
+    },
+    
+    constructor: Size
+};
+
+/**
+ * Creates size from "width height" string
+ *
+ * @memberOf uki.geometry.Size
+ * @name fromString
+ * @function
+ *
+ * @param {string} string String representation of size
+ *
+ * @returns {uki.geometry.Size} created size
+ */
+Size.fromString = function(string) {
+    var parts = string.split(/\s+/);
+    return new Size( parts[0], parts[1] );
+};
+
+/**
+ * Creates size from different representations
+ * - if no params given returns null
+ * - if uki.geometry.Size given returns it
+ * - if "200 300" string converts it to size
+ * - if two params given creates size from them
+ *
+ * @memberOf uki.geometry.Size
+ * @name create
+ * @function
+ *
+ * @param {...string|number|uki.geometry.Size} var_args Size representation
+ *
+ * @returns {uki.geometry.Size} created size
+ */
+Size.create = function(a1, a2) {
+    if (a1 === undefined) return null;
+    if (a1.width !== undefined) return a1;
+    if (/\S+\s+\S+/.test(a1 + '')) return Size.fromString(a1, a2);
+    return new Size(a1, a2);
+};
+
+
+/**
+ * Rectangle with x, y, width and height properties
+ * May be used as uki.geometry.Point or uki.geometry.Size
+ * - if 4 arguments given creates size with x,y,width,height set to the given arguments
+ * - if 2 number arguments given creates size with x = y = 0 and width and height set
+ *   set to the given arguments
+ * - if a Point and a Size given creates rect with point as an origin and given size
+ *
+ * @param {...number|uki.geometry.Point|uki.geometry.Size} var_args
+ * @name uki.geometry.Rect
+ * @augments uki.geometry.Size
+ * @augments uki.geometry.Point
+ * @constructor
+ */
+var Rect = uki.geometry.Rect = function(a1, a2, a3, a4) {
+    if (a3 !== undefined) {
+        this.x      = a1*1.0 || 0.0;
+        this.y      = a2*1.0 || 0.0;
+        this.width  = a3*1.0 || 0.0;
+        this.height = a4*1.0 || 0.0;
+    } else if (a1 === undefined || a1.x === undefined) {
+        this.x      = 0;
+        this.y      = 0;
+        this.width  = a1*1.0 || 0.0;
+        this.height = a2*1.0 || 0.0;
+    } else {
+        this.x      = a1 ? a1.x*1.0      : 0;
+        this.y      = a1 ? a1.y*1.0      : 0;
+        this.width  = a2 ? a2.width*1.0  : 0;
+        this.height = a2 ? a2.height*1.0 : 0;
+    }
+};
+
+Rect.prototype = /** @lends uki.geometry.Rect.prototype */ {
+    /**
+     * Converts Rect to "x y width height" string
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {string}
+     */
+    toString: function() {
+        return [this.x, this.y, this.width, this.height].join(' ');
+    },
+    
+    /**
+     * Converts Rect to "x y maxX maxY" string
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {string}
+     */
+    toCoordsString: function() {
+        return [this.x, this.y, this.maxX(), this.maxY()].join(' ');
+    },
+    
+    /**
+     * Creates a new Rect with same properties
+     *
+     * @this {uki.geometry.Size}
+     * @return {uki.geometry.Size} new Size
+     */
+    clone: function() {
+        return new Rect(this.x, this.y, this.width, this.height);
+    },
+    
+    /**
+     * Equals to .x
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {number}
+     */
+    minX: function() {
+        return this.x;
+    },
+    
+    /**
+     * Equals to x + width
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {number}
+     */
+    maxX: function() {
+        return this.x + this.width;
+    },
+    
+    /**
+     * Point between minX and maxX
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {number}
+     */
+    midX: function() {
+        return this.x + this.width / 2.0;
+    },
+    
+    /**
+     * Equals to .y
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {number}
+     */
+    minY: function() {
+        return this.y;
+    },
+    
+    /**
+     * Point between minY and maxY
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {number}
+     */
+    midY: function() {
+        return this.y + this.height / 2.0;
+    },
+    
+    /**
+     * Equals to y + height
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {number}
+     */
+    maxY: function() {
+        return this.y + this.height;
+    },
+    
+    /**
+     * Moves origin to 0,0 point
+     *
+     * @this {uki.geometry.Rect}
+     * @returns {uki.geometry.Point} self
+     */
+    normalize: function() {
+        this.x = this.y = 0;
+        return this;
+    },
+    
+    /**
+     * Checks if this rect has non-positive width or height
+     *
+     * @this {uki.geometry.Rect}
+     * @function
+     * @return {boolean}
+     */
+    empty: Size.prototype.empty,
+    
+    /**
+     * Checks if this equals to another Rect
+     *
+     * @param {uki.geometry.Rect} rect Rect to compare with
+     * @this {uki.geometry.Rect}
+     * @return {boolean}
+     */
+    eq: function(rect) {
+        return rect && this.x == rect.x && this.y == rect.y && this.height == rect.height && this.width == rect.width;
+    },
+    
+    /**
+     * Insets size with dx and dy
+     *
+     * @param {number} dx
+     * @param {number} dy
+     * @this {uki.geometry.Rect}
+     * @returns {uki.geometry.Rect} sefl
+     */
+    inset: function(dx, dy) {
+        this.x += dx;
+        this.y += dy;
+        this.width -= dx*2.0;
+        this.height -= dy*2.0;
+        return this;
+    },
+    
+    /**
+     * Moves origin point by x, y
+     *
+     * @this {uki.geometry.Rect}
+     * @function
+     * @return {uki.geometry.Rect} self
+     */
+    offset: Point.prototype.offset,
+    
+    /**
+     * Intersects this with given rect
+     *
+     * @this {uki.geometry.Rect}
+     * @param {uki.geometry.Rect} rect Rect to intersect with
+     * @returns {uki.geometry.Rect} intersection
+     */
+    intersection: function(rect) {
+        var origin = new Point(
+                MAX(this.x, rect.x),
+                MAX(this.y, rect.y)
+            ),
+            size = new Size(
+                MIN(this.maxX(), rect.maxX()) - origin.x,
+                MIN(this.maxY(), rect.maxY()) - origin.y
+            );
+        return size.empty() ? new Rect() : new Rect(origin, size);
+    },
+    
+    /**
+     * Union rect of this and given rect
+     *
+     * @this {uki.geometry.Rect}
+     * @param {uki.geometry.Rect} rect
+     * @returns {uki.geometry.Rect} union
+     */
+    union: function(rect) {
+        return Rect.fromCoords(
+            MIN(this.x, rect.x),
+            MIN(this.y, rect.y),
+            MAX(this.maxX(), rect.maxX()),
+            MAX(this.maxY(), rect.maxY())
+        );
+    },
+    
+    /**
+     * Checks if point is within this
+     *
+     * @this {uki.geometry.Rect}
+     * @param {uki.geometry.Point} point
+     * @returns {boolean}
+     */
+    containsPoint: function(point) {
+        return point.x >= this.minX() &&
+               point.x <= this.maxX() &&
+               point.y >= this.minY() &&
+               point.y <= this.maxY();    
+    },
+    
+    /**
+     * Checks if this contains given rect
+     *
+     * @this {uki.geometry.Rect}
+     * @param {uki.geometry.Rect} rect
+     * @returns {boolean}
+     */
+    containsRect: function(rect) {
+        return this.eq(this.union(rect));
+    },
+    
+    constructor: Rect
+};
+
+Rect.prototype.left = Rect.prototype.minX;
+Rect.prototype.top  = Rect.prototype.minY;
+
+/**
+ * Creates Rect from minX, minY, maxX, maxY
+ *
+ * @memberOf uki.geometry.Rect
+ * @name fromCoords
+ * @function
+ *
+ * @param {number} minX
+ * @param {number} maxX
+ * @param {number} minY
+ * @param {number} maxY
+ * @returns {uki.geometry.Rect}
+ */
+Rect.fromCoords = function(minX, minY, maxX, maxY) {
+    if (maxX === undefined) {
+        return new Rect(
+            minX.x, 
+            minX.y, 
+            minY.x - minX.x, 
+            minY.y - minX.y
+        );
+    }
+    return new Rect(minX, minY, maxX - minX, maxY - minY);
+};
+
+/**
+ * Creates Rect from "minX minY maxX maxY" string
+ *
+ * @memberOf uki.geometry.Rect
+ * @name fromCoordsString
+ * @function
+ *
+ * @param {string} string
+ * @returns {uki.geometry.Rect}
+ */
+Rect.fromCoordsString = function(string) {
+    var parts = string.split(/\s+/);
+    return Rect.fromCoords( 
+        parts[0],
+        parts[1],
+        parts[2],
+        parts[3]
+    ) ;
+};
+
+/**
+ * Creates Rect from "x y width height" or "width height" string
+ *
+ * @memberOf uki.geometry.Rect
+ * @name fromString
+ * @function
+ *
+ * @param {string} string
+ * @returns {uki.geometry.Rect}
+ */
+Rect.fromString = function(string) {
+    var parts = string.split(/\s+/);
+    
+    if (parts.length > 2) return new Rect( 
+        parts[0],
+        parts[1],
+        parts[2],
+        parts[3]
+    );
+    return new Rect( 
+        parts[0],
+        parts[1]
+    ) ;
+};
+
+/**
+ * Creates rect from different representations
+ * - if no params given returns null
+ * - if uki.geometry.Rect given returns it
+ * - if "200 300" or "0 10 200 300" string converts it to rect
+ * - if two or four params given creates rect from them
+ *
+ * @memberOf uki.geometry.Rect
+ * @name creates
+ * @function
+ *
+ * @param {...string|number|uki.geometry.Rect} var_args Rect representation
+ *
+ * @returns {uki.geometry.Rect} created size
+ */
+Rect.create = function(a1, a2, a3, a4) {
+    if (a1 === undefined) return null;
+    if (a1.x !== undefined) return a1;
+    if (/\S+\s+\S+/.test(a1 + '')) return Rect.fromString(a1, a2);
+    if (a3 === undefined) return new Rect(a1, a2);
+    return new Rect(a1, a2, a3, a4);
+};
+
+
+/**
+ * Inset with top, right, bottom and left properties
+ * - if no params given top = right = bottom = left = 0
+ * - if two params given top = bottom and right = left
+ *
+ * @param {number=} top
+ * @param {number=} right
+ * @param {number=} bottom
+ * @param {number=} left
+ *
+ * @name uki.geometry.Inset
+ * @constructor
+ */
+var Inset = uki.geometry.Inset = function(top, right, bottom, left) {
+    this.top    = top*1.0   || 0;
+    this.right  = right*1.0 || 0;
+    this.bottom = bottom === undefined ? this.top*1.0 : bottom*1.0;
+    this.left   = left === undefined ? this.right*1.0 : left*1.0;
+};
+
+Inset.prototype = /** @lends uki.geometry.Inset.prototype */ {
+    
+    /**
+     * Converts Inset to "top right bottom left" string
+     *
+     * @returns {string}
+     */
+    toString: function() {
+        return [this.top, this.right, this.bottom, this.left].join(' ');
+    },
+    
+    /**
+     * Creates a new Inset with same properties
+     *
+     * @this {uki.geometry.Inset}
+     * @return {uki.geometry.Inset} new Inset
+     */
+    clone: function() {
+        return new Inset(this.top, this.right, this.bottom, this.left);
+    },
+    
+    /**
+     * left + right
+     *
+     * @this {uki.geometry.Inset}
+     * @return {number}
+     */
+    width: function() {
+        return this.left + this.right;
+    },
+    
+    /**
+     * top + bottom
+     *
+     * @this {uki.geometry.Inset}
+     * @return {number}
+     */
+    height: function() {
+        return this.top + this.bottom;
+    },
+    
+    /**
+     * True if any property < 0
+     *
+     * @this {uki.geometry.Inset}
+     * @return {boolean}
+     */
+    negative: function() {
+        return this.top < 0 || this.left < 0 || this.right < 0 || this.bottom < 0;
+    },
+    
+    /**
+     * True if all properties = 0
+     *
+     * @this {uki.geometry.Inset}
+     * @return {boolean}
+     */
+    empty: function() {
+        return !this.top && !this.left && !this.right && !this.bottom;
+    }
+};
+
+/**
+ * Creates Rect from "top right bottom left" or "top right" string
+ *
+ * @memberOf uki.geometry.Inset
+ * @name fromString
+ * @function
+ *
+ * @param {string} string
+ * @returns {uki.geometry.Inset}
+ */
+Inset.fromString = function(string) {
+    var parts = string.split(/\s+/);
+    if (parts.length < 3) parts[2] = parts[0];
+    if (parts.length < 4) parts[3] = parts[1];
+    
+    return new Inset(
+        parts[0],
+        parts[1],
+        parts[2],
+        parts[3]
+    );
+};
+
+/**
+ * Creates rect from different representations
+ * - if no params given returns null
+ * - if uki.geometry.Inset given returns it
+ * - if "200 300" or "0 10 200 300" string converts it to inset
+ * - if two or four params given creates inset from them
+ *
+ * @memberOf uki.geometry.Inset
+ * @name create
+ * @function
+ *
+ * @param {...string|number|uki.geometry.Inset} var_args Rect representation
+ *
+ * @returns {uki.geometry.Inset} created inset
+ */
+Inset.create = function(a1, a2, a3, a4) {
+    if (a1 === undefined) return null;
+    if (a1.top !== undefined) return a1;
+    if (/\S+\s+\S+/.test(a1 + '')) return Inset.fromString(a1, a2);
+    if (a3 === undefined) return new Inset(a1, a2);
+    return new Inset(a1, a2, a3, a4);
+};
+
+
+
+
+/**
+ * Basic utils to work with the dom tree
+ * @namespace
+ * @author voloko
+ */
+uki.dom = {
+    /**
+     * Convenience wrapper around document.createElement
+     * Creates dom element with given tagName, cssText and innerHTML
+     *
+     * @param {string} tagName
+     * @param {string=} cssText
+     * @param {string=} innerHTML
+     * @returns {Element} created element
+     */
+    createElement: function(tagName, cssText, innerHTML) {
+        var e = doc.createElement(tagName);            
+        if (cssText) e.style.cssText = cssText;
+        if (innerHTML) e.innerHTML = innerHTML;
+        e[expando] = uki.guid++;
+        return e;
+    },
+    
+    /**
+     * Adds a probe element to page dom tree, callbacks, removes the element
+     *
+     * @param {Element} dom Probing dom element
+     * @param {function(Element)} callback
+     */
+    probe: function(dom, callback) {
+        var target = doc.body;
+        target.appendChild(dom);
+        var result = callback(dom);
+        target.removeChild(dom);
+        return result;
+    },
+    
+    /**
+     * Assigns layout style properties to an element
+     *
+     * @param {CSSStyleDeclaration} style Target declaration
+     * @param {object} layout Properties to assign
+     * @param {object=} prevLayout If given assigns only difference between layout and prevLayout
+     */
+    layout: function(style, layout, prevLayout) {
+        prevLayout = prevLayout || {};
+        if (prevLayout.left   != layout.left)   style.left   = layout.left + PX;
+        if (prevLayout.top    != layout.top)    style.top    = layout.top + PX;
+        if (prevLayout.right  != layout.right)  style.right  = layout.right + PX;
+        if (prevLayout.bottom != layout.bottom) style.bottom = layout.bottom + PX;
+        if (prevLayout.width  != layout.width)  style.width  = MAX(layout.width, 0) + PX;
+        if (prevLayout.height != layout.height) style.height = MAX(layout.height, 0) + PX;
+        return layout;
+    },
+    
+    /**
+     * Computed style for a give element
+     *
+     * @param {Element} el
+     * @returns {CSSStyleDeclaration} style declaration
+     */
+    computedStyle: function(el) {
+        if (doc && doc.defaultView && doc.defaultView.getComputedStyle) {
+            return doc.defaultView.getComputedStyle( el, null );
+        } else if (el.currentStyle) {
+            return el.currentStyle;
+        }
+    },
+    
+    /**
+     * Checks if parent contains child
+     *
+     * @param {Element} parent 
+     * @param {Element} child 
+     * @return {Boolean}
+     */
+    contains: function(parent, child) {
+        try {
+            if (parent.contains) return parent.contains(child);
+            if (parent.compareDocumentPosition) return !!(parent.compareDocumentPosition(child) & 16);
+        } catch (e) {}
+        while ( child && child != parent ) {
+            try { child = child.parentNode } catch(e) { child = null };
+        }
+        return parent == child;
+    },
+    
+    createStylesheet: function(code) {
+        var style = doc.createElement('style');
+        doc.getElementsByTagName('head')[0].appendChild(style);
+        if (style.styleSheet) { //IE
+            style.styleSheet.cssText = code;
+        } else {
+            style.appendChild(document.createTextNode(code));
+        }
+        return style;
+    }
+    
+};
+
+uki.each(['createElement'], function(i, name) {
+    uki[name] = uki.dom[name];
+});
+
+uki.dom.special = {};
+
+uki.dom.Event = function( domEvent ) {
+    domEvent = domEvent || {};
+    this.domEvent = domEvent.domEvent || domEvent;
+
+       for ( var i = uki.dom.props.length, prop; i; ){
+               prop = uki.dom.props[ --i ];
+               this[ prop ] = domEvent[ prop ];
+       }
+       
+    // this.dataTransfer = new uki.dom.DataTransfer(domEvent);
+};
+
+uki.dom.Event.prototype = new function() {
+    function returnTrue () {
+        return true;
+    }
+    
+    this.preventDefault = function() {
+        var domEvent = this.domEvent;
+        domEvent.preventDefault && domEvent.preventDefault();
+        domEvent.returnValue = false;
+        
+        this.isDefaultPrevented = returnTrue;
+    }
+    
+    this.stopPropagation = function() {
+               var domEvent = this.domEvent;
+               domEvent.stopPropagation && domEvent.stopPropagation();
+               domEvent.cancelBubble = true;
+               
+               this.isPropagationStopped = returnTrue;
+    }
+    
+    this.isDefaultPrevented = this.isPropagationStopped = uki.F;
+}
+
+uki.extend(uki.dom, /** @lends uki.dom */ {
+    bound: {},
+    handlers: {},
+    
+    props: "type altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which dragOffset dataTransfer".split(" "),
+    
+    events: "blur focus load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error draggesturestart draggestureend draggesture dragstart dragend drag drop dragenter dragleave dragover".split(" "),
+
+    bind: function(el, types, listener) {
+               if ( el.setInterval && el != window )
+                       el = window;
+                       
+        listener.huid = listener.huid || uki.guid++;
+        
+        var id = el[expando] = el[expando] || uki.guid++,
+            handler = uki.dom.handlers[id] = uki.dom.handlers[id] || function() {
+                uki.dom.handler.apply(arguments.callee.elem, arguments);
+            },
+            i, type;
+            
+        handler.elem = el;
+        
+        if (!uki.dom.bound[id]) uki.dom.bound[id] = {};
+        
+        types = types.split(' ');
+        for (i=0; i < types.length; i++) {
+            type = types[i];
+            if (!uki.dom.bound[id][type]) {
+                uki.dom.bound[id][type] = [];
+                if ( !uki.dom.special[type] || uki.dom.special[type].setup.call(el) === false ) {
+                    el.addEventListener ? el.addEventListener(type, handler, false) : el.attachEvent('on' + type, handler);
+                }
+            }
+            uki.dom.bound[id][type].push(listener);
+        };
+        listener = handler = el = null;
+    },
+    
+    unbind: function(el, types, listener) {
+        var id = el[expando],
+            huid = listener && listener.huid,
+            i, type;
+        if (types) {
+            types = types.split(' ');
+        } else {
+            types = [];
+            uki.each(uki.dom.bound[id] || [], function(k, v) { types.push(k); });
+        }
+        for (i=0; i < types.length; i++) {
+            type = types[i];
+            if (!id || !uki.dom.bound[id] || !uki.dom.bound[id][type]) continue;
+            uki.dom.bound[id][type] = listener ? uki.grep(uki.dom.bound[id][type], function(h) { return h.huid !== huid; }) : [];
+            
+            if (uki.dom.bound[id][type].length == 0) {
+                var handler = uki.dom.handlers[id];
+                if ( !uki.dom.special[type] || uki.dom.special[type].teardown.call(el) === false ) {
+                    el.removeEventListener ? el.removeEventListener(type, handler, false) : el.detachEvent('on' + type, handler);
+                }
+                uki.dom.bound[id][type] = null;
+            }
+        }
+    },
+    
+    /** @ignore */
+    handler: function( e ) {
+        
+        e = e || root.event;
+        
+        var type = e.type,
+            id = this[expando],
+            handlers = uki.dom.bound[id],
+            i;
+            
+        if (!e.domEvent) {
+            e = new uki.dom.Event(e);
+            e = uki.dom.fix( e );
+        }
+        
+        if (!id || !handlers || !handlers[type]) return;
+        
+        uki.after.start();
+        for (i=0, handlers = handlers[type]; i < handlers.length; i++) {
+            handlers[i].call(this, e);
+        };
+        uki.after.stop();
+    },
+    
+    /**
+     * Taken from jQuery
+     * @ignore
+     */
+    fix: function( event ) {
+               // Fix target property, if necessary
+               if ( !event.target )
+                       event.target = event.srcElement || doc;
+
+               // check if target is a textnode (safari)
+               if ( event.target.nodeType == 3 )
+                       event.target = event.target.parentNode;
+
+               // Add relatedTarget, if necessary
+               if ( !event.relatedTarget && event.fromElement )
+                       event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+               // Calculate pageX/Y if missing and clientX/Y available
+               if ( event.pageX == null && event.clientX != null ) {
+                       var de = doc.documentElement, body = doc.body;
+                       event.pageX = event.clientX + (de && de.scrollLeft || body && body.scrollLeft || 0) - (de.clientLeft || 0);
+                       event.pageY = event.clientY + (de && de.scrollTop  || body && body.scrollTop || 0)  - (de.clientTop || 0);
+               }
+
+               // Add which for key events
+               if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+                       event.which = event.charCode || event.keyCode;
+
+               // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+               if ( !event.metaKey && event.ctrlKey )
+                       try { event.metaKey = event.ctrlKey; } catch(e){};
+
+               // Add which for click: 1 == left; 2 == middle; 3 == right
+               // Note: button is not normalized, so don't use it
+               if ( !event.which && event.button )
+                       event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));    
+                       
+               return event;    
+    },
+    
+    preventDefaultHandler: function(e) {
+        e && e.preventDefault();
+        return false;
+    }
+});
+
+uki.each({ 
+       mouseover: 'mouseenter', 
+       mouseout: 'mouseleave'
+}, function( orig, fix ){
+    var handler = function(e) {
+           if (!uki.dom.contains(this, e.relatedTarget)) {
+               e.type = fix;
+               uki.dom.handler.apply(this, arguments);
+        }
+       };
+       
+       uki.dom.special[ fix ] = {
+               setup: function() {
+                       uki.dom.bind( this, orig, handler );
+               },
+               teardown: function(){
+                   uki.dom.unbind( this, orig, handler );
+               }
+       };                         
+});
+
+
+if (root.attachEvent) {
+    root.attachEvent('onunload', function() {
+        uki.each(uki.dom.bound, function(id, types) {
+            uki.each(types, function(type, handlers) {
+                try {
+                    uki.dom.handlers[id].elem.detachEvent('on' + type, uki.dom.handlers[id]);
+                } catch (e) {};
+            });
+        });
+    });
+};
+
+
+
+(function() {
+/**
+ * Drag and Drop support for uki
+ * @namespace
+ */
+var dnd = uki.dom.dnd = {
+    draggable: null,
+    nativeDnD: false,
+    position: null
+};
+
+// detect if native DnD is supported
+try {
+    if (
+        // typeof doc.createElement('div').ondragstart == 'object' || // ie support
+        typeof doc.createEvent('MouseEvent').dataTransfer == 'object' || // safari
+        doc.createEvent('DragEvent').initDragEvent // w3c support
+    ) {
+        // Google Chrome has to many issues with native d&d. It is simpler to disable than to fix
+        dnd.nativeDnD = !ua.match(/Chrome\/4/);
+    }
+} catch (e) {}
+
+// bind single drag set of drag events for an element
+// regardless of the number of listeners
+var bindDraggestures = {
+    setup: function() {
+        if (this.__draggesturebound) {
+            this.__draggesturebound++;
+        } else {
+            this.__draggesturebound = 1;
+               uki.dom.bind( this, 'mousedown', draggesturestart );
+                // prevent interference with ie drag events
+            if (!dnd.nativeDnD && typeof this.ondragstart == 'object') 
+                this.ondragstart = function() { event.returnValue = false; };
+        }
+    },
+    teardown: function() {
+        this.__draggesturebound--;
+        if (!this.__draggesturebound) uki.dom.unbind( this, 'mousedown', draggesturestart );
+    }
+};
+
+// drag gestures
+uki.extend(uki.dom.special, {
+    draggesturestart: bindDraggestures,
+    draggestureend: bindDraggestures,
+    draggesture: bindDraggestures
+});
+
+var dragEndEvents = 'mouseup ' + (dnd.nativeDnD ? ' dragend' : '');
+// if (window.attachEvent && !window.opera) dragEndEvents += ' mouseleave';
+
+function startGesture (el) {
+    if (dnd.draggable) return;
+    dnd.draggable = el;
+    uki.dom.bind(doc, 'mousemove scroll', draggesture);
+    uki.dom.bind(doc, dragEndEvents, draggestureend);
+    uki.dom.bind(doc, 'selectstart mousedown', uki.dom.preventDefaultHandler);
+}
+
+function stopGesture () {
+    dnd.draggable = null;
+    uki.dom.unbind(doc, 'mousemove scroll', draggesture);
+    uki.dom.unbind(doc, dragEndEvents, draggestureend);
+    uki.dom.unbind(doc, 'selectstart mousedown', uki.dom.preventDefaultHandler);
+}
+
+function draggesturestart (e) {
+    e = new uki.dom.Event(e);
+    e.type = 'draggesturestart';
+    uki.dom.handler.apply(this, arguments);
+    if (!e.isDefaultPrevented()) {
+        startGesture(this);
+        dnd.position = new Point(-e.pageX, -e.pageY);
+    }
+}
+
+function draggesture (e) {
+    e = new uki.dom.Event(e);
+    e.type = 'draggesture';
+    e.dragOffset = (new Point(e.pageX, e.pageY)).offset(dnd.position);
+    uki.dom.handler.apply(dnd.draggable, arguments);
+    if (e.isDefaultPrevented()) stopGesture(dnd.draggable);
+}
+
+function draggestureend (e) {
+    e = new uki.dom.Event(e);
+    e.type = 'draggestureend';
+    e.dragOffset = (new Point(e.pageX, e.pageY)).offset(dnd.position);
+    uki.dom.handler.apply(dnd.draggable, arguments);
+    stopGesture(dnd.draggable);
+}
+
+})();
+
+
+
+(function() {
+    
+    var dnd = uki.dom.dnd,
+        retriggering = false;
+        
+    // common properties
+    uki.extend(dnd, {
+        dragDelta: 5,
+        initNativeDnD: function() {
+            // moz needs the drag image to be appended to document
+            // so create an offscreen container to hold drag images.
+            // note that it can't have overflow:hidden, since drag image will be cutted to container
+            var container = uki.createElement('div', 'position: absolute;left:-999em;');
+            doc.body.appendChild(container);
+            dnd.dragImageContainer = container;
+            dnd.initNativeDnD = uki.F;
+            return true;
+        },
+        dragImageContainer: null,
+        dataTransfer: null,
+        target: null,
+        dragOver: null
+    });
+    
+    
+    function viewToDom (element) {
+        if (uki.isFunction(element.dom)) {
+            if (element.parent().length) return element.dom();
+            var container = uki.createElement('div', 'width:1px;height:1px;position:absolute;left:-999em;top:0');
+            doc.body.appendChild(container);
+            element.attachTo(container);
+            return container;
+        }
+        return element;
+    }
+    
+    var dataTransferProps = ['dropEffect', 'effectAllowed', 'types', 'files'];
+    
+    uki.dom.DataTransferWrapper = uki.newClass(new function() {
+        this.init = function(dataTransfer) {
+            this.dataTransfer = dataTransfer;
+            for (var i = dataTransferProps.length - 1; i >= 0; i--){
+                this[ dataTransferProps[i] ] = dataTransfer[ dataTransferProps[i] ];
+            };
+        };
+        
+        this.setData = function(format, data) {
+            return this.dataTransfer.setData(format, data);
+        };
+        
+        this.clearData = function(format) {
+            return this.dataTransfer.clearData(format);
+        };
+        
+        this.getData = function(format) {
+            return this.dataTransfer.getData(format);
+        };
+        
+        this.setDragImage = function(image, x, y) {
+            dnd.initNativeDnD();
+            image = viewToDom(image);
+            var clone = image.cloneNode(true),
+                style = clone.style;
+            style.left = style.right = style.top = style.bottom = '';
+            style.position = 'static';
+            dnd.dragImageContainer.appendChild(clone);
+            setTimeout(function() {
+                dnd.dragImageContainer.removeChild(clone);
+            }, 1);
+            return this.dataTransfer.setDragImage(clone, x, y);
+        };
+    })
+        
+    // w3c spec based dataTransfer implementation
+    uki.dom.DataTransfer = uki.newClass(new function() {
+        this.init = function() {
+            uki.extend(this, {
+                dropEffect: 'none',
+                effectAllowed: 'none',
+                types: [],
+                files: [],
+                dragImage: new Image(),
+                imagePosition: new Point(),
+                data: {}
+            });
+        };
+
+        this.setData = function(format, data) {
+            this.data[format] = data;
+            if (uki.inArray(format, this.types) == -1) this.types.push(format);
+        };
+
+        this.clearData = function(format) {
+            if (format) {
+                delete this.data[format];
+                this.types = uki.grep(this.types, function(x) { return x != format; });
+            } else {
+                this.data = {};
+                this.types = [];
+            }
+        };
+
+        this.getData = function(format) {
+            return this.data[format];
+        };
+
+        this.setDragImage = function(image, x, y) {
+            this._dragImage = this._initDragImage(image);
+            this._imagePosition = new Point(x || 0, y || 0);
+        };
+
+        this.update = function(e) {
+            if (!this._dragImage) return;
+            this._dragImage.style.left = e.pageX - this._imagePosition.x + 'px';
+            this._dragImage.style.top = e.pageY -  this._imagePosition.y + 'px';
+        };
+
+        this.cleanup = function() {
+            this._dragImage && this._dragImage.parentNode.removeChild(this._dragImage);
+            this._dragImage = undefined;
+        };
+
+        this._initDragImage = function(image) {
+            image = viewToDom(image);
+            var clone = image.cloneNode(true),
+                style = clone.style;
+
+            style.left = style.right = style.top = style.bottom = '';
+            style.position = 'absolute';
+            style.left = '-999em';
+            style.zIndex = '9999';
+            doc.body.appendChild(clone);
+            return clone;
+        };
+    });
+
+
+    var bindW3CDrag = {
+        setup: function() {
+            if (this.__w3cdragbound) {
+                this.__w3cdragbound++;
+            } else {
+                this.__w3cdragbound = 1;
+                       uki.dom.bind( this, 'draggesture', drag );
+            }
+        },
+        teardown: function() {
+            this.__w3cdragbound--;
+            if (!this.__draggesturebound) uki.dom.unbind( this, 'draggesture', drag );
+        }
+    };
+    
+    if (dnd.nativeDnD) {
+        uki.extend(uki.dom.special, {
+            dragstart: {
+                setup: function() {
+                    this.addEventListener('dragstart', nativeDragWrapper, false);
+                },
+                teardown: function() {
+                    this.removeEventListener('dragstart', nativeDragWrapper, false);
+                }
+            }
+        })
+        
+    } else {
+        uki.extend(uki.dom.special, {
+            dragstart: bindW3CDrag,
+            drag: bindW3CDrag,
+            dragend: bindW3CDrag
+        });
+        
+        uki.each({
+            dragover: 'mousemove',
+            drop: 'mouseup'
+        }, function( source, target ){
+            
+            var handler = function(e) {
+                   if (dnd.dataTransfer && retriggering) {
+                    e = new uki.dom.Event(e);
+                       e.type = source;
+                       e.dataTransfer = dnd.dataTransfer;
+                       if (source == 'dragover') {
+                           dnd.__canDrop = false;
+                       } else {
+                        stopW3Cdrag(this);
+                           if (!dnd.__canDrop) return;
+                   }
+                       uki.dom.handler.apply(this, arguments);
+                       if (e.isDefaultPrevented()) {
+                           dnd.__canDrop = true;
+                   }
+                 }
+               };
+               
+               uki.dom.special[ source ] = {
+                       setup: function() {
+                               uki.dom.bind( this, target, handler );
+                       },
+                       teardown: function(){
+                           uki.dom.unbind( this, target, handler );
+                       }
+               };                         
+        });
+        
+       uki.dom.special.dragenter = {
+               setup: function() {
+                       uki.dom.bind( this, 'mousemove', dragenter );
+               },
+               teardown: function(){
+                   uki.dom.unbind( this, 'mousemove', dragenter );
+               }
+       };                         
+       uki.dom.special.dragleave = { setup: function() {}, teardown: function() {} }
+    }
+    
+    function nativeDragWrapper (e) {
+        e = new uki.dom.Event(e);
+        var dataTransfer = e.dataTransfer;
+        e.dataTransfer = new uki.dom.DataTransferWrapper(dataTransfer);
+        uki.dom.handler.apply(this, arguments);
+        dataTransfer.effectAllowed = e.dataTransfer.effectAllowed;
+        dataTransfer.dropEffect = e.dataTransfer.dropEffect;
+    }
+
+    function startW3Cdrag (element) {
+        uki.dom.bind( element, 'draggestureend', dragend );
+    }
+
+    function stopW3Cdrag (element) {
+        if (!dnd.dataTransfer) return;
+        dnd.dataTransfer.cleanup();
+        dnd.dragOver = dnd.dataTransfer = dnd.target = null;
+        uki.dom.unbind( element, 'draggestureend', dragend );
+    }
+    
+    function dragenter (e) {
+        if (!dnd.dataTransfer || e.domEvent.__dragEntered || !retriggering) return;
+        e = new uki.dom.Event(e);
+        e.domEvent.__dragEntered = true;
+        if (dnd.dragOver == this) return;
+        dnd.dragOver = this;
+        e.type = 'dragenter';
+        uki.dom.handler.apply(this, arguments);
+    }
+    
+    function drag (e) {
+        if (retriggering) {
+            if (!e.domEvent.__dragEntered && dnd.dragOver) {
+                e = new uki.dom.Event(e);
+                e.type = 'dragleave';
+                uki.dom.handler.apply(dnd.dragOver, arguments);
+                dnd.dragOver = null;
+            }
+            return;
+        };
+        
+        if (dnd.dataTransfer) { // dragging
+            e.type = 'drag';
+            e.target = dnd.target;
+        } else if (e.dragOffset.x > dnd.dragDelta || e.dragOffset.y > dnd.dragDelta) { // start drag
+            var target = e.target,
+                parent = this.parentNode;
+            try {
+                while (target && target != parent && !target.getAttribute('draggable')) target = target.parentNode;
+            } catch (ex) { target = null; }
+            if (target && target.getAttribute('draggable')) {
+                dnd.target = e.target = target;
+                e.type = 'dragstart';
+                dnd.dataTransfer = e.dataTransfer = new uki.dom.DataTransfer(e.domEvent.dataTransfer);
+                startW3Cdrag(this);
+            } else {
+                return;
+            }
+        } else {
+            return;
+        }
+        e = new uki.dom.Event(e);
+        uki.dom.handler.apply(this, arguments);
+        if (e.isDefaultPrevented()) {
+            stopW3Cdrag(this);
+        } else {
+            retriggerMouseEvent(e);
+        }
+        
+    }
+    
+    var props = 'detail screenX screenY clientX clientY ctrlKey altKey shiftKey metaKey button'.split(' ');
+    
+    function retriggerMouseEvent (e) {
+        var imageStyle = dnd.dataTransfer._dragImage.style,
+            type = e.domEvent.type, target;
+        e.stopPropagation();
+        e.preventDefault();
+        imageStyle.left = '-999em';
+        target = doc.elementFromPoint(e.pageX, e.pageY);
+        dnd.dataTransfer.update(e);
+        
+        try {
+            var newEvent;
+            retriggering = true;
+            try {
+                if (doc.createEventObject) {
+                    newEvent = doc.createEventObject();
+                       for ( var i = props.length, prop; i; ){
+                               prop = uki.dom.props[ --i ];
+                               newEvent[ prop ] = e.domEvent[ prop ];
+                       }
+                    target.fireEvent('on' + type, newEvent)
+                } else {
+                    newEvent = doc.createEvent('MouseEvents');   
+                    newEvent.initMouseEvent(
+                        type,
+                        true,
+                        true,
+                        doc.defaultView,
+                        e.detail, 
+                        e.screenX,  
+                        e.screenY, 
+                        e.clientX, 
+                        e.clientY, 
+                        e.ctrlKey, 
+                        e.altKey, 
+                        e.shiftKey,
+                        e.metaKey,
+                        e.button, 
+                        null
+                    );
+                    target.dispatchEvent(newEvent)
+                }
+            } catch (e) {}
+            retriggering = false;
+        } catch (e) {}
+    }
+
+    function dragend (e) {
+        if (retriggering) return;
+        if (dnd.dataTransfer) { // drag started
+            e.type = 'dragend';
+            e.target = dnd.target;
+            e.dataTransfer = dnd.dataTransfer;
+            uki.dom.handler.apply(this, arguments);
+            retriggerMouseEvent(e);
+            stopW3Cdrag(this);
+        }
+    }
+})();
+
+
+(function() {
+    var self;
+    
+    if ( doc.documentElement["getBoundingClientRect"] ) {
+       self = uki.dom.offset = function( elem ) {
+               if ( !elem || elem == root ) return new Point();
+               if ( elem === elem.ownerDocument.body ) return self.bodyOffset( elem );
+               self.boxModel === undefined && self.initializeBoxModel();
+               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  + (self.pageYOffset || self.boxModel && docElem.scrollTop  || body.scrollTop ) - clientTop,
+                       left = box.left + (self.pageXOffset || self.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
+
+               return new Point(left, top);
+       };
+    } else {
+       self = uki.dom.offset = function( elem ) {
+               if ( !elem || elem == root ) return new Point();
+               if ( elem === elem.ownerDocument.body ) return self.bodyOffset( elem );
+               self.initialized || self.initialize();
+
+               var offsetParent = elem.offsetParent, 
+                   prevOffsetParent = elem,
+                       doc = elem.ownerDocument, 
+                       computedStyle, 
+                       docElem = doc.documentElement,
+                       body = doc.body, 
+                       defaultView = doc.defaultView,
+                       prevComputedStyle = defaultView.getComputedStyle(elem, null),
+                       top = elem.offsetTop, 
+                       left = elem.offsetLeft;
+
+               while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+                       computedStyle = defaultView.getComputedStyle(elem, null);
+                       top -= elem.scrollTop; 
+                       left -= elem.scrollLeft;
+                       
+                       if ( elem === offsetParent ) {
+                               top += elem.offsetTop; 
+                               left += elem.offsetLeft;
+                               
+                               if ( self.doesNotAddBorder && !(self.doesAddBorderForTableAndCells && (/^t(able|d|h)$/i).test(elem.tagName)) ) {
+                                       top  += parseInt( computedStyle.borderTopWidth,  10) || 0;
+                                       left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+                               }
+                               prevOffsetParent = offsetParent; 
+                               offsetParent = elem.offsetParent;
+                       }
+                       if ( self.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+                               top  += parseInt( computedStyle.borderTopWidth,  10) || 0;
+                               left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+                       }
+                       prevComputedStyle = computedStyle;
+               }
+
+               if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+                       top  += body.offsetTop;
+                       left += body.offsetLeft;
+               }
+
+               if ( prevComputedStyle.position === "fixed" ) {
+                       top  += MAX(docElem.scrollTop, body.scrollTop);
+                       left += MAX(docElem.scrollLeft, body.scrollLeft);
+               }
+
+               return new Point(left, top);
+       };
+    }
+
+    uki.extend(self, {
+       initialize: function() {
+               if ( this.initialized ) return;
+               var body = doc.body, 
+                   container = doc.createElement('div'), 
+                   innerDiv, checkDiv, table, td, rules, prop, 
+                   bodyMarginTop = body.style.marginTop,
+                       html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
+
+               rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
+               for ( prop in rules ) container.style[prop] = rules[prop];
+
+               container.innerHTML = html;
+               body.insertBefore(container, body.firstChild);
+               innerDiv = container.firstChild; 
+               checkDiv = innerDiv.firstChild; 
+               td = innerDiv.nextSibling.firstChild.firstChild;
+
+               this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+               this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+               innerDiv.style.overflow = 'hidden'; 
+               innerDiv.style.position = 'relative';
+               this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+               body.style.marginTop = '1px';
+               this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
+               body.style.marginTop = bodyMarginTop;
+               
+               body.removeChild(container);
+               this.boxModel === undefined && this.initializeBoxModel();
+               this.initialized = true;
+       },
+       
+       initializeBoxModel: function() {
+           if (this.boxModel !== undefined) return;
+               var div = doc.createElement("div");
+               div.style.width = div.style.paddingLeft = "1px";
+
+               doc.body.appendChild( div );
+               this.boxModel = div.offsetWidth === 2;
+               doc.body.removeChild( div ).style.display = 'none';
+       },
+
+       bodyOffset: function(body) {
+               self.initialized || self.initialize();
+               var top = body.offsetTop, left = body.offsetLeft;
+               if ( uki.dom.doesNotIncludeMarginInBodyOffset ) {
+                       top  += parseInt( uki.dom.elem.currentStyle(body).marginTop, 10 ) || 0;
+                       left += parseInt( uki.dom.elem.currentStyle(body).marginLeft, 10 ) || 0;
+               }
+               return new Point(left, top);
+       }
+    });    
+})();
+
+
+
+uki.browser = new function() {
+    
+    var boxShadow;
+    this.cssBoxShadow = function() {
+        // Opera 10.5 consistently fails to redraw shadows. Easier to switch off
+        boxShadow = boxShadow || (root.opera ? 'unsupported' : checkPrefixes('box-shadow'));
+        return boxShadow;
+    };
+    
+    var borderRadius;
+    this.cssBorderRadius = function() {
+        borderRadius = borderRadius || checkPrefixes('border-radius');
+        return borderRadius;
+    };
+    
+    var userSelect;
+    this.cssUserSelect = function() {
+        userSelect = userSelect || checkPrefixes('user-select');
+        return userSelect;
+    };
+    
+    var linearGradient;
+    this.cssLinearGradient = function() {
+        linearGradient = linearGradient || initLinearGradient();
+        return linearGradient;
+    };
+    
+    var canvas;
+    this.canvas = function() {
+        if (canvas === undefined) canvas = !!uki.createElement('canvas').getContext;
+        return canvas;
+    };
+    
+    var filter;
+    this.cssFilter = function() {
+        if (filter === undefined) filter = typeof uki.createElement('div').style.filter != 'undefined';
+        return filter;
+    };
+    
+    function swap (obj, src, dst) {
+        var v = obj[src];
+        obj[src] = undefined;
+        obj[dst] = v;
+    }
+    this.css = function(css) {
+        if (!css) return '';
+        if (typeof css == 'string') {
+            return css.replace(/(^|[^-])(box-shadow|border-radius|user-select)/g, function(value) {
+                var p;
+                if ((p = value.indexOf('box-shadow')) > -1) return value.substr(0, p) + uki.browser.cssBoxShadow();
+                if ((p = value.indexOf('border-radius')) > -1) return value.substr(0, p) + uki.browser.cssBorderRadius();
+                if ((p = value.indexOf('user-select')) > -1) return value.substr(0, p) + uki.browser.cssUserSelect();
+            });
+        }
+        
+        uki.each(['boxShadow', 'borderRadius', 'userSelect'], function(k, v) {
+            if (css[v]) swap(css, v, uki.camalize( uki.browser[ uki.camalize('css-' + v) ]() ) );
+        });
+        return css;
+    };
+    
+    this.textStyles = 'font fontFamily fontWeight fontSize textDecoration textOverflow textAlign textShadow overflow color'.split(' ');
+    
+    function checkPrefixes (dashProp) {
+        var e = uki.createElement('div'),
+            style = e.style,
+            prefixes = ['', '-webkit-', '-moz-'];
+            
+        for (var i=0; i < prefixes.length; i++) {
+            if (style[ uki.camalize(prefixes[i] + dashProp) ] !== undefined) return prefixes[i] + dashProp;
+        };
+        return 'unsupported';
+    }
+    
+    function initLinearGradient () {
+        var e = uki.createElement(
+                'div', 
+                'background-image:-moz-linear-gradient(right,red,red);'+
+                'background-image:linear-gradient(right,red,red);'+
+                'background-image:-webkit-gradient(linear,0 0,100% 0,from(red),to(red))'
+            ),
+            style = e.style,
+            bgi = style.backgroundImage + '';
+            
+        if (bgi.indexOf('-moz-linear-gradient') > -1) {
+            return '-moz-linear-gradient';
+        } else if (bgi.indexOf('-webkit-gradient') > -1) {
+            return '-webkit-gradient';
+        } else if (bgi.indexOf('linear-gradient') > -1) {
+            return 'linear-gradient';
+        } else {
+            return 'unsupported';
+        }
+    }
+};
+
+uki.initNativeLayout = function() {
+    if (uki.supportNativeLayout === undefined) {
+        uki.dom.probe(
+            uki.createElement(
+                'div', 
+                'position:absolute;width:100px;height:100px;left:-999em;', 
+                '<div style="position:absolute;left:0;right:0"></div>'
+            ),
+            function(div) {
+                uki.supportNativeLayout = div.childNodes[0].offsetWidth == 100 && !root.opera;
+            }
+        );
+    }
+};
+
+// uki.supportNativeLayout = false;
+
+
+
+
+
+/**
+ * Executes callback at or after the end of processing.
+ * Example: execute flow layout after all child views were added.
+ * Runs either at the end of event handling or after a timeout.
+ * Each callback (by huid) is executed once
+ * @function
+ * @param {function()} callback
+ */
+uki.after = (function() {
+    var after = function(callback) {
+        callback.huid = callback.huid || uki.guid++;
+        if (after._bound[callback.huid]) return;
+        after._bound[callback.huid] = true;
+        after._queue.push(callback);
+        if (!after._running) after._startTimer();
+    };
+    
+    after._bound = {};
+    after._running = false;
+    after._timer = 0;
+    after._queue = [];
+    
+    after.start = function() {
+        after._clearTimer();
+        after._running++;
+    };
+    
+    after.stop = function() {
+        if (--after._running) return;
+        after._runCallbacks();
+    };
+    
+    
+    
+    after._runCallbacks = function() {
+        after._clearTimer();
+        var queue = after._queue;
+        after._queue = [];
+        after._bound = {};
+        for (var i=0; i < queue.length; i++) queue[i]();
+    };
+    
+    after._startTimer = function() {
+        if (after._timer) return;
+        after._timer = setTimeout(after._runCallbacks, 1);
+    };
+    
+    after._clearTimer = function() {
+        if (!after._timer) return;
+        clearTimeout(after._timer);
+        after._timer = 0;
+    };
+    
+    return after;
+})();
+
+
+
+
+/** @namespace */
+uki.view = {
+    declare: function(/*name, baseClasses, implementation*/) {
+        var args  = uki.toArray(arguments),
+            name  = args.shift(),
+            klass = uki.newClass.apply(uki, args),
+            parts = name.split('.'),
+            obj   = root,
+            i, part, l = parts.length - 1;
+        
+        klass.prototype.typeName = function() { return name; };
+               
+        for ( i= 0; i < l; i++ ) {
+            part = parts[i];
+            if (!obj[part]) obj[part] = {};
+            obj = obj[part];
+                       
+        };
+               
+        obj[ parts[l] ] = klass;
+        return klass;
+    }
+};
+
+/**
+ * @class
+ */
+uki.view.Observable = /** @lends uki.view.Observable.prototype */ {
+    // dom: function() {
+    //     return null; // should implement
+    // },
+    
+    /**
+     * @param {String} name Event name
+     * @param {function()} callback
+     */
+    bind: function(name, callback) {
+        callback.huid = callback.huid || uki.guid++;
+        uki.each(name.split(' '), function(i, name) {
+            if (!this._bound(name)) this._bindToDom(name);
+            this._observersFor(name).push(callback);
+        }, this);
+        return this;
+    },
+    
+    unbind: function(name, callback) {
+        if (!this._observers) return;
+        var names;
+        if (name) {
+            names = name.split(' ');
+        } else {
+            names = [];
+            uki.each(this._observers, function(k, v) { names.push(k) });
+        }
+        uki.each(names, function(i, name) {
+            this._observers[name] = !callback ? [] : uki.grep(this._observersFor(name, true), function(observer) {
+                return observer != callback && observer.huid != callback.huid;
+            });
+            if (this._observers[name].length == 0) {
+                this._observers[name] = undefined;
+                this._unbindFromDom(name);
+            }
+        }, this);
+        return this;
+    },
+    
+    trigger: function(name/*, data1, data2*/) {
+        var attrs = Array.prototype.slice.call(arguments, 1);
+        uki.each(this._observersFor(name, true), function(i, callback) {
+            callback.apply(this, attrs);
+        }, this);
+        return this;
+    },
+    
+    _unbindFromDom: function(name) {
+        if (!this._domHander || !this._eventTargets[name]) return;
+        uki.dom.unbind(this._eventTargets[name], name, this._domHander);
+    },
+    
+    _bindToDom: function(name, target) {
+        if (!target && !this.dom) return false;
+        this._domHander = this._domHander || uki.proxy(function(e) {
+            e.source = this;
+            this.trigger(e.type, e);
+        }, this);
+        this._eventTargets = this._eventTargets || {};
+        this._eventTargets[name] = target || this.dom();
+        uki.dom.bind(this._eventTargets[name], name, this._domHander);
+        return true;
+    },
+    
+    _bound: function(name) {
+        return this._observers && this._observers[name];
+    },
+    
+    _observersFor: function(name, skipCreate) {
+        if (skipCreate && (!this._observers || !this._observers[name])) return [];
+        if (!this._observers) this._observers = {};
+        if (!this._observers[name]) this._observers[name] = [];
+        return this._observers[name];
+    }
+};
+
+(function() {
+    var self = uki.Attachment = uki.newClass(uki.view.Observable, /** @lends uki.Attachment.prototype */ {
+        /**
+         * Attachment serves as a connection between a uki view and a dom container.
+         * It notifies its view with parentResized on window resize. 
+         * Attachment supports part of uki.view.Base API like #domForChild or #rectForChild
+         *
+         * @param {Element} dom Container element
+         * @param {uki.view.Base} view Attached view
+         * @param {uki.geometry.Rect} rect Initial size
+         *
+         * @see uki.view.Base#parentResized
+         * @name uki.Attachment
+         * @augments uki.view.Observable
+         * @constructor
+         */
+        init: function( dom, view, rect ) {
+            uki.initNativeLayout();
+            
+            this._dom     = dom = dom || root;
+            this._view    = view;
+            this._rect = Rect.create(rect) || this.rect();
+            
+            uki.dom.offset.initialize();
+            
+            view.parent(this);
+            this.domForChild().appendChild(view.dom());
+            
+            if (dom != root && dom.tagName != 'BODY') {
+                var computedStyle = dom.runtimeStyle || dom.ownerDocument.defaultView.getComputedStyle(dom, null);
+                if (!computedStyle.position || computedStyle.position == 'static') dom.style.position = 'relative';
+            }
+            self.register(this);
+
+            this.layout();
+        },
+        
+        /**
+         * Returns document.body if attached to window. Otherwise returns dom
+         * uki.view.Base api
+         *
+         * @type Element
+         */
+        domForChild: function() {
+            return this._dom === root ? doc.body : this._dom;
+        },
+        
+        /**
+         * uki.view.Base api
+         *
+         * @type uki.geometry.Rect
+         */
+        rectForChild: function(child) {
+            return this.rect();
+        },
+        
+        /**
+         * uki.view.Base api
+         */
+        scroll: function() {
+            // TODO: support window scrolling
+        },  
+        
+        /**
+         * uki.view.Base api
+         */
+        scrollTop: function() {
+            // TODO: support window scrolling
+            return this._dom.scrollTop || 0;
+        },
+        
+        /**
+         * uki.view.Base api
+         */
+        scrollLeft: function() {
+            // TODO: support window scrolling
+            return this._dom.scrollLeft || 0;
+        },
+        
+        /**
+         * uki.view.Base api
+         */
+        parent: uki.F,
+        
+        /**
+         * uki.view.Base api
+         */
+        childResized: uki.F,
+        
+        /**
+         * On window resize resizes and layout its child view
+         * @fires event:layout
+         */
+        layout: function() {
+            var oldRect = this._rect;
+                
+            // if (rect.eq(this._rect)) return;
+            var newRect = this._rect = this.rect();
+            this._view.parentResized(oldRect, newRect);
+            if (this._view._needsLayout) this._view.layout();
+            this.trigger('layout', {source: this, rect: newRect});
+        },
+        
+        /**
+         * @return {Element} Container dom
+         */
+        dom: function() {
+            return this._dom;
+        },
+        
+        /**
+         * @return {Element} Child view
+         */
+        view: function() {
+            return this._view;
+        },
+        
+        /**
+         * @private
+         * @return {uki.geometry.Rect} Size of the container
+         */
+        rect: function() {
+            var width = this._dom === root || this._dom === doc.body ? 
+                    MAX(getRootElement().clientWidth, this._dom.offsetWidth || 0) : 
+                    this._dom.offsetWidth,
+                height = this._dom === root || this._dom === doc.body ? 
+                    MAX(getRootElement().clientHeight, this._dom.offsetHeight || 0) : 
+                    this._dom.offsetHeight;
+            
+            return new Rect(width, height);
+        }
+    });
+    
+    function getRootElement() {
+        return doc.compatMode == "CSS1Compat" && doc.documentElement || doc.body;
+    }
+    
+    self.instances = [];
+    
+    /**
+     * @memberOf uki.Attachment
+     */
+    self.register = function(a) {
+        if (self.instances.length == 0) {
+            var timeout = false;
+            uki.dom.bind(root, 'resize', function() {
+                if (!timeout) {
+                    timeout = true;
+                    setTimeout(function(i,len) {
+                        uki.after.start();
+                        
+                        timeout = false;
+                                               for (i=0,len=self.instances.length;i<len;i++)
+                                                       self.instances[i].layout();
+                                                       
+                        uki.after.stop();
+                    }, 1);
+                }
+            });
+        }
+        self.instances.push(a);
+    };
+    
+    /**
+     * @memberOf uki.Attachment
+     */
+    self.childViews = function() {
+        return uki.map(self.instances, 'view');
+    };
+    
+    /**
+     * @memberOf uki.Attachment
+     */
+    uki.top = function() {
+        return [self];
+    };
+})();
+
+
+
+
+
+
+
+/**
+ * Collection performs group operations on uki.view objects.
+ * <p>Behaves much like result jQuery(dom nodes).
+ * Most methods are chainable like .attr('text', 'somevalue').bind('click', function() { ... })</p>
+ *
+ * <p>Its easier to call uki([view1, view2]) or uki('selector') instead of creating collection directly</p>
+ *
+ * @author voloko
+ * @constructor
+ * @class
+ */
+uki.Collection = function( elems ) {
+    this.length = 0;
+       Array.prototype.push.apply( this, elems );
+};
+
+uki.fn = uki.Collection.prototype = new function() {
+    var proto = this;
+    
+    /**#@+ @memberOf uki.Collection# */
+    /**
+     * Iterates trough all items within itself
+     *
+     * @function
+     *
+     * @param {function(this:uki.view.Base, number, uki.view.Base)} callback Callback to call for every item
+     * @returns {uki.view.Collection} self
+     */
+    this.each = function( callback ) {
+        return uki.each( this, callback );        
+    };
+
+    /**
+     * Creates a new uki.Collection populated with found items
+     *
+     * @function
+     *
+     * @param {function(uki.view.Base, number):boolean} callback Callback to call for every item
+     * @returns {uki.view.Collection} created collection
+     */
+    this.grep = function( callback ) {
+        return new uki.Collection( uki.grep(this, callback) );
+    };
+
+    /**
+     * Sets an attribute on all views or gets the value of the attribute on the first view
+     *
+     * @example
+     * c.attr('text', 'my text') // sets text to 'my text' on all collection views
+     * c.attr('name') // gets name attribute on the first view
+     *
+     * @function
+     *
+     * @param {string} name Name of the attribute
+     * @param {object=} value Value to set
+     * @returns {uki.view.Collection|Object} Self or attribute value
+     */
+    this.attr = function( name, value ) {
+        if (value !== undefined) {
+            for (var i=this.length-1; i >= 0; i--) {
+                uki.attr( this[i], name, value );
+            };
+            return this;
+        } else {
+            return this[0] ? uki.attr( this[0], name ) : "";
+        }
+    };
+
+    /**
+     * Finds views within collection context
+     * @example
+     * c.find('Button')
+     *
+     * @function
+     *
+     * @param {string} selector 
+     * @returns {uki.view.Collection} Collection of found items
+     */
+    this.find = function( selector ) {
+        return uki.find( selector, this );
+    };
+
+    /**
+     * Attaches all child views to dom container
+     *
+     * @function
+     *
+     * @param {Element} dom Container dom element
+     * @param {uki.geometry.Rect} rect Default size
+     * @returns {uki.view.Collection} self
+     */
+    this.attachTo = function( dom, rect ) {
+        this.each(function() {
+            new uki.Attachment( dom, this, rect );
+        });
+        return this;
+    };
+
+    /**
+     * Appends views to the first item in collection
+     *
+     * @function
+     *
+     * @param {Array.<uki.view.Base>} views Views to append
+     * @returns {uki.view.Collection} self
+     */
+    this.append = function( views ) {
+        var target = this[0];
+        if (!target) return this;
+               
+        views = views.length !== undefined ? views : [views];
+               
+        for (var i = views.length-1; i >= 0; i--) {
+            target.appendChild(views[i]);
+        };
+               
+        return this;
+    };
+    
+    this.appendTo = function( target ) {
+        target = uki(target)[0];
+        this.each(function() {
+            target.appendChild(this);
+        });
+        return this;   
+        
+    };
+
+    /**#@-*/
+
+    /**
+     * @function
+     */
+    uki.Collection.addAttrs = function(attrNames) {
+        uki.each(attrNames, function(i, name) {
+            proto[name] = function( value ) { return this.attr( name, value ); };
+        });
+    };
+
+    /** @function
+    @name uki.Collection#html */
+    /** @function
+    @name uki.Collection#text */
+    /** @function
+    @name uki.Collection#background */
+    /** @function
+    @name uki.Collection#value */
+    /** @function
+    @name uki.Collection#rect */
+    /** @function
+    @name uki.Collection#checked */
+    /** @function
+    @name uki.Collection#anchors */
+    /** @function
+    @name uki.Collection#childViews */
+    /** @function
+    @name uki.Collection#typeName */
+    /** @function
+    @name uki.Collection#id */
+    /** @function
+    @name uki.Collection#name */
+    /** @function
+    @name uki.Collection#visible */
+    /** @function
+    @name uki.Collection#disabled */
+    /** @function
+    @name uki.Collection#focusable */
+    /** @function
+    @name uki.Collection#style */
+    uki.Collection.addAttrs('dom html text background value rect checked anchors childViews typeName id name visible disabled focusable style draggable textSelectable width height minX maxX minY maxY left top x y contentsSize'.split(' '));
+    
+    
+    /** @function
+    @name uki.Collection#parent */
+    /** @function
+    @name uki.Collection#next */
+    /** @function
+    @name uki.Collection#prev */
+    uki.each([
+        ['parent', 'parent'],
+        ['next', 'nextView'],
+        ['prev', 'prevView']
+    ], function(i, desc) {
+        proto[ desc[0] ] = function() {
+            return new uki.Collection( uki.unique( uki.map(this, desc[1]) ) );
+        };
+    });
+    
+
+    /** @function
+    @name uki.Collection#bind */
+    /** @function
+    @name uki.Collection#unload */
+    /** @function
+    @name uki.Collection#trigger */
+    /** @function
+    @name uki.Collection#layout */
+    /** @function
+    @name uki.Collection#appendChild */
+    /** @function
+    @name uki.Collection#removeChild */
+    /** @function
+    @name uki.Collection#insertBefore */
+    /** @function
+    @name uki.Collection#addRow */
+    /** @function
+    @name uki.Collection#removeRow */
+    /** @function
+    @name uki.Collection#resizeToContents */
+    /** @function
+    @name uki.Collection#toggle */
+    uki.each('bind unbind trigger layout appendChild removeChild insertBefore addRow removeRow resizeToContents toggle'.split(' '), function(i, name) {
+        proto[name] = function() { 
+            for (var i=this.length-1; i >=0; i--) {
+                this[i][name].apply(this[i], arguments);
+            };
+            return this;
+        };
+    });
+
+     /** @function
+    @name uki.Collection#blur */
+    /** @function
+    @name uki.Collection#focus */
+    /** @function
+    @name uki.Collection#load */
+    /** @function
+    @name uki.Collection#resize */
+    /** @function
+    @name uki.Collection#scroll */
+    /** @function
+    @name uki.Collection#unload */
+    /** @function
+    @name uki.Collection#click */
+    /** @function
+    @name uki.Collection#dblclick */
+    /** @function
+    @name uki.Collection#mousedown */
+    /** @function
+    @name uki.Collection#mouseup */
+    /** @function
+    @name uki.Collection#mousemove */
+    /** @function
+    @name uki.Collection#mouseover */
+    /** @function
+    @name uki.Collection#mouseout */
+    /** @function
+    @name uki.Collection#mouseenter */
+    /** @function
+    @name uki.Collection#mouseleave */
+    /** @function
+    @name uki.Collection#change */
+    /** @function
+    @name uki.Collection#select */
+    /** @function
+    @name uki.Collection#submit */
+    /** @function
+    @name uki.Collection#keydown */
+    /** @function
+    @name uki.Collection#keypress */
+    /** @function
+    @name uki.Collection#keyup */
+    /** @function
+    @name uki.Collection#error */
+    uki.each( uki.dom.events, function(i, name){
+       proto[name] = function( handler ){
+           if (handler) {
+                       this.bind(name, handler);
+           } else {
+                for (var i=this.length-1; i >=0; i--) {
+                    this[i][name] ? this[i][name]() : this[i].trigger(name);
+                };
+           }
+               return this;
+       };
+    });
+};
+
+
+
+
+(function() {
+    
+    /**
+     * Creates uki view tree from JSON-like markup
+     *
+     * @example
+     * uki.build( {view: 'Button', rect: '100 100 100 24', text: 'Hello world' } )
+     * // Creates uki.view.Button with '100 100 100 24' passed to constructor, 
+     * // and calls text('Hello world') on it
+     *
+     * @function
+     * @name uki.build
+     *
+     * @param {object} ml JSON-like markup
+     * @returns {uki.view.Collection} collection of created elements
+     */
+    uki.build = function(ml) {
+        
+        return new uki.Collection( createMulti( (ml.length === undefined) ? [ml] : ml ) );
+               
+    };
+    
+    uki.viewNamespaces = ['uki.view.', ''];
+    
+    function createMulti (ml) {
+        return uki.map(ml, function(mlRow) { return createSingle(mlRow); });
+    }
+
+    function createSingle (mlRow) {
+        if (uki.isFunction(mlRow.typeName)) {
+            return mlRow;
+        }
+
+        var c = mlRow.view || mlRow.type,
+            result;
+        if (uki.isFunction(c)) {
+            result = new c(mlRow.rect);
+        } else if (typeof c === 'string') {
+            for (var i=0, ns = uki.viewNamespaces, ns$length = ns.length; i < ns$length; i++) {
+                var parts = (ns[i] + c).split('.'),
+                    obj = root;
+                
+                for (var j=0, parts$length = parts.length; obj && j < parts$length; j++) {
+                    obj = obj[parts[j]];
+                };
+                if (obj) {
+                    result = new obj(mlRow.rect);
+                    break;
+                }
+            };
+            if (!obj) throw 'No view of type ' + c + ' found';
+        } else {
+            result = c;
+        }
+
+        copyAttrs(result, mlRow);
+        return result;
+    }
+
+    function copyAttrs(comp, mlRow) {
+        uki.each(mlRow, function(name, value) {
+            if (name == 'view' || name == 'type' || name == 'rect') return;
+            uki.attr(comp, name, value);
+        });
+        return comp;
+    }
+
+    uki.build.copyAttrs = copyAttrs;    
+})();
+
+
+
+
+/* Ideas and code parts borrowed from Sizzle (http://sizzlejs.com/) */
+(function() {
+    /**#@+ @ignore */
+    var self,
+        attr = uki.attr,
+        chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
+        regexps = [ // enforce order
+               { name: 'ID', regexp: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/ },
+               { name: 'ATTR', regexp: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/ },
+               { name: 'TYPE', regexp: /^((?:[\w\u00c0-\uFFFF\*_\.-]|\\.)+)/ },
+               { name: 'POS',  regexp: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/ }
+           ],
+           posRegexp = regexps.POS,
+           posFilters = {
+               first: function(i){
+                       return i === 0;
+               },
+               last: function(i, match, array){
+                       return i === array.length - 1;
+               },
+               even: function(i){
+                       return i % 2 === 0;
+               },
+               odd: function(i){
+                       return i % 2 === 1;
+               },
+               lt: function(i, match){
+                       return i < match[2] - 0;
+               },
+               gt: function(i, match){
+                       return i > match[2] - 0;
+               },
+               nth: function(i, match){
+                       return match[2] - 0 == i;
+               },
+               eq: function(i, match){
+                       return match[2] - 0 == i;
+               }
+       },
+           reducers = {
+               TYPE: function(comp, match) {
+                   var expected = match[1];
+                   if (expected == '*') return true;
+                   var typeName = attr(comp, 'typeName');
+                   return typeName && typeName.length >= expected.length && 
+                          ('.' + typeName).indexOf('.' + expected) == (typeName.length - expected.length);
+               },
+               
+               ATTR: function(comp, match) {
+                       var result = attr(comp, match[1]),
+                           value = result + '',
+                               type = match[2],
+                               check = match[4];
+                               
+                return result == null ? type === "!=" :
+                       type === "="   ? value === check :
+                       type === "*="  ? value.indexOf(check) >= 0 :
+                       type === "~="  ? (" " + value + " ").indexOf(check) >= 0 :
+                       !check         ? value && result !== false :
+                       type === "!="  ? value != check :
+                       type === "^="  ? value.indexOf(check) === 0 :
+                       type === "$="  ? value.substr(value.length - check.length) === check :
+                       false;
+               },
+               
+               ID: function(comp, match) {
+                   return reducers.ATTR(comp, ['', 'id', '=', '', match[1]]);
+               },
+               
+               POS: function(comp, match, i, array) {
+                       var filter = posFilters[match[1]];
+                               return filter ? filter( i, match, array ) : false;
+               }
+           },
+           mappers = {
+               "+": function(context){
+                   return uki.unique( uki.map(context, 'nextView') );
+               },
+               
+               ">": function(context){
+                   return uki.unique( flatten(uki.map(context, 'childViews')) );
+               },
+               
+               "": function(context) {
+                   return uki.unique( recChildren(flatten(uki.map(context, 'childViews'))) );
+               },
+               
+               "~": function(context){
+                   return uki.unique( flatten( uki.map(context, nextViews) ) );
+               }               
+           };
+           
+       function nextViews (view) {
+           return view.parent().childViews().slice((view._viewIndex || 0) + 1);
+       }
+       
+       function recChildren (comps) {
+           return flatten(uki.map(comps, function(comp) {
+               return [comp].concat( recChildren(attr(comp, 'childViews')) );
+           }));
+       }
+           
+       function flatten (array) {
+          return uki.reduce( [], array, reduceFlatten );
+       }
+           
+       function reduceFlatten (x, e) {
+          return x.concat(e);
+       }
+       /**#@-*/
+       
+    self = 
+    /**
+     * @namespace
+     */
+    uki.Selector = {
+       /**
+        * Finds views by CSS3 selectors in view tree.
+        * <p>Can be called as uki(selector) instead of uki.Selector.find(selector)</p>
+        *
+        * @example
+        *   uki('Label') find all labels on page
+        *   uki('Box[name=main] > Label') find all immediate descendant Labels in a box with name = "main"
+        *   uki('> Slider', context) find all direct descendant Sliders within given context
+        *   uki('Slider,Checkbox') find all Sliders and Checkboxes
+        *   uki('Slider:eq(3)') find 3-d slider
+        *
+        * @param {string} selector
+        * @param {Array.<uki.view.Base>} context to search in
+        *
+        * @return {uki.Collection} found views
+        */    
+        find: function(selector, context, skipFiltering) {
+            context = context || uki.top();
+            if (context.length === undefined) context = [context];
+
+            var tokens = self.tokenize(selector),
+                expr   = tokens[0],
+                extra  = tokens[1],
+                result = context,
+                mapper;
+                
+            while (expr.length > 0) {
+                mapper = mappers[expr[0]] ? mappers[expr.shift()] : mappers[''];
+                result = mapper(result);
+                if (expr.length == 0) break;
+                result = self.reduce(expr.shift(), result);
+            }
+
+            if (extra) {
+                result = result.concat(self.find(extra, context, true));
+            }
+            
+            return skipFiltering ? result : new uki.Collection(uki.unique(result));
+        },
+        
+        /** @ignore */
+        reduce: function(exprItem, context) {
+            if (!context || !context.length) return [];
+            
+            var match, found;
+                
+            while (exprItem != '') {
+                found = false;
+                uki.each(regexps, function(index, row) {
+                    
+                    /*jsl:ignore*/
+                    if (match = exprItem.match(row.regexp)) {
+                    /*jsl:end*/
+                        found = true;
+                        context = uki.grep(context, function(comp, index) { 
+                            return reducers[row.name](comp, match, index, context); 
+                        });
+                        exprItem = exprItem.replace(row.regexp, '');
+                        return false;
+                    }
+                });
+                if (!found) break;
+            }
+            return context;
+        },
+        
+        /** @ignore */
+        tokenize: function(expr) {
+               var parts = [], match, extra;
+
+               chunker.lastIndex = 0;
+
+               while ( (match = chunker.exec(expr)) !== null ) {
+                       parts.push( match[1] );
+
+                       if ( match[2] ) {
+                               extra = RegExp.rightContext;
+                               break;
+                       }
+               }
+            
+            return [parts, extra];
+        }
+    };
+    
+    uki.find = self.find;
+})();
+
+
+
+
+/**
+ * Creates image element from url
+ *
+ * @param {string} url Image url
+ * @param {string=} dataUrl Data url representation of image, used if supported
+ * @param {string=} alphaUrl Gif image url for IE6
+ *
+ * @namespace
+ * @function
+ *
+ * @returns {Element}
+ */
+uki.image = function(url, dataUrl, alphaUrl) {
+    var result = new Image();
+    result.src = uki.imageSrc(url, dataUrl, alphaUrl);
+    return result;
+};
+/**
+ * Selects image src depending on browser
+ *
+ * @param {string} url Image url
+ * @param {string=} dataUrl Data url representation of image, used if supported
+ * @param {string=} alphaUrl Gif image url for IE6
+ *
+ * @returns {string}
+ */
+uki.imageSrc = function(url, dataUrl, alphaUrl) {
+    if (uki.image.dataUrlSupported && dataUrl) return dataUrl;
+    if (alphaUrl && uki.image.needAlphaFix) return alphaUrl;
+    return url;
+};
+
+/**
+ * Image html representation: '<img src="">'
+ *
+ * @param {string} url Image url
+ * @param {string=} dataUrl Data url representation of image, used if supported
+ * @param {boolean=} alphaUrl Gif image url for IE6
+ * @param {string=} html Additional html
+ *
+ * @returns {string} html
+ */
+uki.imageHTML = function(url, dataUrl, alphaUrl, html) {
+    if (uki.image.needAlphaFix && alphaUrl) {
+        url = alphaUrl;
+    } else if (uki.image.dataUrlSupported) {
+        url = dataUrl;
+    }
+    return '<img' + (html || '') + ' src="' + url + '" />';
+};
+
+
+/**
+ * @type boolean
+ */
+uki.image.dataUrlSupported = doc.createElement('canvas').toDataURL || (/MSIE (8)/).test(ua);
+
+/**
+ * @type boolean
+ */
+uki.image.needAlphaFix = /MSIE 6/.test(ua);
+if(uki.image.needAlphaFix) try { doc.execCommand("BackgroundImageCache", false, true); } catch(e){}
+
+
+
+
+(function() {
+    var nullRegexp = /^\s*null\s*$/,
+        themeRegexp  = /theme\s*\(\s*(.*\s*)\)/,
+        rowsRegexp = /rows\s*\(\s*(.*\s*)\)/,
+        cssBoxRegexp = /cssBox\s*\(\s*(.*\s*)\)/;
+        
+    /**
+     * Transforms a bg string into a background object
+     * <p>Supported strings:<br />
+     *   theme(bg-name) Takes background with bg-name from uki.theme<br />
+     *   rows(30, #CCFFFF, #FFCCFF, #FFFFCC) Creates Rows background with 30px rowHeight and 3 colors<br />
+     *   cssBox(border:1px solid red;background:blue) Creates CssBox background with given cssText<br />
+     *   url(i.png) or #FFFFFF Creates Css background with single property</p>
+     *
+     * @param {String} bg
+     * @name uki.background
+     * @namespace
+     * @returns {uki.background.Base} created background
+     */
+    var self = uki.background = function(bg) {
+        if (typeof(bg) === 'string') {
+            var match;
+            /*jsl:ignore*/
+            if ( match = bg.match(nullRegexp) ) return new self.Null();
+                       
+            if ( match = bg.match(themeRegexp) ) return uki.theme.background( match[1] );
+            if ( match = bg.match(rowsRegexp) ) return new self.Rows( match[1].split(',')[0], match[1].split(/\s*,\s*/).slice(1) );
+            if ( match = bg.match(cssBoxRegexp) ) return new self.CssBox( match[1] );
+            /*jsl:end*/
+            return new self.Css(bg);
+        }
+        return bg;
+    };    
+})();
+
+
+
+/**
+ * @class
+ */
+uki.background.Base = uki.background.Null = uki.newClass({
+    init: uki.F,
+    attachTo: uki.F,
+    detach: uki.F
+});
+
+
+
+/**
+ * Adds a div with 9 sliced images in the corners, sides and center
+ *
+ * @class
+ */
+uki.background.Sliced9 = uki.newClass(new function() {
+    var nativeCss = ['MozBorderImage', 'WebkitBorderImage', 'borderImage'],
+        dom = uki.dom;
+        
+    var LEFT = 'left:',
+        TOP  = 'top:',
+        RIGHT = 'right:',
+        BOTTOM = 'bottom:',
+        WIDTH = 'width:',
+        HEIGHT = 'height:',
+        PX = 'px',
+        P100 = '100%';
+        
+    var cache = {};
+    
+    /**#@+ @memberOf uki.background.Sliced9.prototype */
+    
+    this.init = function(partSettings, inset, options) {
+        this._settings  = uki.extend({}, partSettings);
+        this._inset     = Inset.create(inset);
+        this._size      = null;
+        this._inited    = false;
+        
+        options = options || {};
+        this._fixedSize = Size.create(options.fixedSize) || new Size();
+        this._bgInset   = Inset.create(options.inset)    || new Inset();
+        this._zIndex    = options.zIndex || -1;
+
+        this._container = this._getContainer();
+        this._container.style.zIndex = this._zIndex;
+    };
+    
+    /** @ignore */
+    function makeDiv (name, style, setting, imgStyle, bgSettings) {
+        var inner = setting[3] ? img(setting, imgStyle) : '';
+        if (!setting[3]) style += bgStyle(setting, bgSettings);
+        return '<div class="' +  name + '" style="position:absolute;overflow:hidden;' + style + '">' + inner + '</div>';
+    }
+    
+    /** @ignore */
+    function bgStyle (setting, bgSettings) {
+        return ';background: url(' + uki.imageSrc(setting[0], setting[1], setting[2]) + ') ' + bgSettings;
+    }
+
+    /** @ignore */
+    function img (setting, style) {
+        return uki.imageHTML(setting[0], setting[1], setting[2], ' ondragstart="return false;" galleryimg="no" style="-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;position:absolute;' + style + '"');
+    }
+    
+    /** @ignore */
+    this._getContainer = function() {
+        var key = this._getKey();
+        if (!cache[key]) {
+            return cache[key] = this._createContainer();
+        }
+        return cache[key].cloneNode(true);
+    };
+    
+    /** @ignore */
+    this._createContainer = function() {
+        var inset = this._inset,
+            bgInset = this._bgInset,
+            settings = this._settings,
+            width = inset.left + inset.right,
+            height = inset.top + inset.bottom,
+            css = [LEFT + bgInset.left + PX, RIGHT + bgInset.right + PX, TOP + bgInset.top + PX, BOTTOM + bgInset.bottom + PX].join(';'),
+            html = [];
+            
+        if (inset.top && inset.left) {
+            html[html.length] = makeDiv('tl',
+                [LEFT + 0, TOP + 0, WIDTH + inset.left + PX, HEIGHT + inset.top + PX].join(';'),
+                settings.c, [LEFT + 0, TOP + 0, WIDTH + width + PX, HEIGHT + height + PX].join(';'), 'top left'
+            );
+        }
+        if (inset.top) {
+            html[html.length] = makeDiv('t',
+                [LEFT + inset.left + PX, TOP + 0, HEIGHT + inset.top + PX, RIGHT + inset.right + PX].join(';'),
+                settings.h, [LEFT + 0, TOP + 0, WIDTH + P100, HEIGHT + height + PX].join(';'), 'repeat-x top'
+            );
+        }
+        if (inset.top && inset.right) {
+            html[html.length] = makeDiv('tr',
+                [RIGHT + 0, TOP + 0, WIDTH + inset.right + PX, HEIGHT + inset.top + PX].join(';'),
+                settings.c, [LEFT + '-' + inset.left + PX, TOP + 0, WIDTH + width + PX, HEIGHT + height + PX].join(';'), 'top right'
+            );
+        }
+        
+        if (inset.left) {
+            html[html.length] = makeDiv('l',
+                [LEFT + 0, TOP + inset.top + PX, WIDTH + inset.left + PX, BOTTOM + inset.bottom + PX].join(';'),
+                settings.v, [LEFT + 0, TOP + 0, HEIGHT + P100, WIDTH + width + PX].join(';'), 'repeat-y left'
+            );
+        }
+        if (settings.m) {
+            html[html.length] = makeDiv('m',
+                [LEFT + inset.left + PX, TOP + inset.top + PX, RIGHT + inset.right + PX, BOTTOM + inset.bottom + PX].join(';'),
+                settings.m, [LEFT + 0, TOP + 0, HEIGHT + P100, WIDTH + P100].join(';'), ''
+            );
+        }
+        if (inset.right) {
+            html[html.length] = makeDiv('r',
+                [RIGHT + 0, TOP + inset.top + PX, WIDTH + inset.right + PX, BOTTOM + inset.bottom + PX].join(';'),
+                settings.v, [LEFT + '-' + inset.left + PX, TOP + 0, HEIGHT + P100, WIDTH + width + PX].join(';'), 'repeat-y right'
+            );
+        }
+        
+        if (inset.bottom && inset.left) {
+            html[html.length] = makeDiv('bl',
+                [LEFT + 0, BOTTOM + 0, WIDTH + inset.left + PX, HEIGHT + inset.bottom + PX].join(';'),
+                settings.c, [LEFT + 0, TOP + '-' + inset.top + PX, WIDTH + width + PX, HEIGHT + height + PX].join(';'), 'left -' + inset.top + PX
+            );
+        }
+        if (inset.bottom) {
+            html[html.length] = makeDiv('b',
+                [LEFT + inset.left + PX, BOTTOM + 0, HEIGHT + inset.bottom + PX, RIGHT + inset.right + PX].join(';'),
+                settings.h, [LEFT + 0, TOP + '-' + inset.top + PX, WIDTH + P100, HEIGHT + height + PX].join(';'), 'repeat-x 0 -' + inset.top + PX
+            );
+        }
+        if (inset.bottom && inset.right) {
+            html[html.length] = makeDiv('br',
+                [RIGHT + 0, BOTTOM + 0, WIDTH + inset.right + PX, HEIGHT + inset.bottom + PX].join(';'),
+                settings.c, [LEFT + '-' + inset.left + PX, TOP + '-' + inset.top + PX, WIDTH + width + PX, HEIGHT + height + PX].join(';'), 'right -' + inset.top + PX
+            );
+        }
+        var container = uki.createElement('div', 'position:absolute;overflow:hidden;' + css, html.join(''));
+        container.className = 'uki-background-Sliced9';
+        return container;
+    };
+    
+    /** @ignore */
+    this._getKey = function() {
+        return uki.map(['v', 'h', 'm', 'c'], function(x) {
+            return this._settings[x] && this._settings[x][0] || '';
+        }, this).concat([this._inset, this._bgInset, this._fixedSize]).join(',');
+    };
+    
+    this.attachTo = function(comp) {
+        this._comp = comp;
+        
+        this._container.style.visibility = 'visible';
+        this._comp.dom().insertBefore(this._container, this._comp.dom().firstChild);
+        // this._comp.dom().appendChild(this._container);
+        
+        if (!uki.supportNativeLayout) {
+            this._layoutHandler = this._layoutHandler || uki.proxy(function(e) {
+                if (this._size && this._size.eq(e.rect)) return;
+                this._size = e.rect;
+                this.layout();
+            }, this);
+            this._comp.bind('layout', this._layoutHandler);
+            this.layout();
+        }
+    };
+    
+    this.detach = function() {
+        if (this._comp) {
+            // this._comp.dom().removeChild(this._container);
+            this._container.style.visibility = 'hidden';
+            if (!uki.supportNativeLayout) this._comp.unbind('layout', this._layoutHandler);
+            this._size = this._comp = null;
+            this._attached = this._inited = false;
+        }
+    };
+    
+    this.layout = function(e) {
+        var size = this._comp.rect(),
+            parts = this._parts,
+            inset = this._inset,
+            bgInset = this._bgInset,
+            fixedSize = this._fixedSize,
+            width = FLOOR(fixedSize.width || size.width - bgInset.left - bgInset.right),
+            height = FLOOR(fixedSize.height || size.height - bgInset.top - bgInset.bottom),
+            insetWidth = inset.left + inset.right,
+            insetHeight = inset.top + inset.bottom;
+            
+        if (!parts) {
+            parts = {};
+            uki.each(this._container.childNodes, function() {
+                if (this.className) parts[this.className] = this;
+            });
+            this._parts = parts;
+        }
+        // parts.b.style.bottom = ''
+        // parts.b.style.top = '100%';
+        // parts.b.style.marginTop = - inset.bottom + 'px';
+        if (parts.t) dom.layout(parts.t.style, { width: width - insetWidth });
+        if (parts.b) dom.layout(parts.b.style, { width: width - insetWidth });
+        if (parts.l) dom.layout(parts.l.style, { height: height - insetHeight });
+        if (parts.r) dom.layout(parts.r.style, { height: height - insetHeight });
+        if (parts.m) dom.layout(parts.m.style, {
+            height: height - insetHeight,
+            width: width - insetWidth
+        });
+        dom.layout(this._container.style, {
+            width: width,
+            height: height
+        });
+    };
+    
+    /**#@-*/    
+});
+
+
+
+/**
+ * Writes css properties to targets dom()
+ *
+ * @class
+ */
+uki.background.Css = uki.newClass(new function() {
+    
+    /**#@+ @memberOf uki.background.Css.prototype */
+    this.init = function(options) {
+        this._options = typeof options == 'string' ? {background: options} : options;
+        this._options = uki.browser.css(this._options);
+    };
+    
+    this.attachTo = function(comp) {
+        this._comp = comp;
+        this._originalValues = {};
+        
+        uki.each(this._options, function(name, value) {
+            // this._originalValues[name] = dom.style[name];
+            // dom.style[name] = value;
+            this._originalValues[name] = comp.style(name);
+            comp.style(name, value);
+        }, this);
+    };
+    
+    this.detach = function() {
+        if (this._comp) {
+            uki.each(this._options, function(name, value) {
+                this._comp.style(name, this._originalValues[name]);
+            }, this);
+        }
+        
+    };
+    /**#@-*/
+});
+
+
+
+/**
+ * Adds a div with given cssText to dom()
+ *
+ * @class
+ */
+uki.background.CssBox = uki.newClass(new function() {
+    
+    var cache = {};
+    /** @ignore */
+    function getInsets(options) {
+        if (!cache[options]) {
+            uki.dom.probe(
+                uki.createElement('div', options + ';position:absolute;overflow:hidden;left:-999em;width:10px;height:10px;'), 
+                function(c) {
+                    cache[options] = new Inset(
+                        c.offsetHeight - 10,
+                        c.offsetWidth - 10
+                    );
+                }
+            );
+        }
+        return cache[options];
+    }
+    
+    /**#@+ @memberOf uki.background.CssBox.prototype */
+    
+    this.init = function(options, ext) {
+        this._options = options;
+        ext = ext || {};
+        this._inset = inset = Inset.create(ext.inset) || new Inset();
+        this._insetWidth  = getInsets(options).left + inset.left + inset.right;
+        this._insetHeight = getInsets(options).top + inset.top + inset.bottom;
+
+        this._container = uki.createElement(
+            'div', 
+            'position:absolute;overflow:hidden;z-index:' + (ext.zIndex || '-1') + ';' + 
+            'left:' + inset.left + 'px;top:' + inset.top + 'px;right:' + inset.right + 'px;bottom:' + inset.bottom + 'px;' +
+            uki.browser.css(options),
+            ext.innerHTML
+        );
+        this._container.className = 'uki-background-CssBox';
+        this._attached = false;
+    };
+    
+    this.attachTo = function(comp) {
+        this._comp = comp;
+        this._comp.dom().insertBefore(this._container, this._comp.dom().firstChild);
+
+        if (uki.supportNativeLayout) return;
+        
+        this._layoutHandler = this._layoutHandler || uki.proxy(function(e) { this.layout(e.rect); }, this);
+        this._comp.bind('layout', this._layoutHandler);
+        this.layout(this._comp.rect());
+    };
+    
+    this.layout = function(size) {
+        this._prevLayout = uki.dom.layout(this._container.style, {
+            width: size.width - this._insetWidth,
+            height: size.height - this._insetHeight
+        }, this._prevLayout);
+    };
+    
+    this.detach = function() {
+        if (this._comp) {
+            this._comp.dom().removeChild(this._container);
+            if (!uki.supportNativeLayout) this._comp.unbind('layout', this._layoutHandler);
+            this._attached = false;
+        }
+    };
+    
+    /**#@-*/
+});
+
+
+/**
+ * Adds a div with given cssText to dom()
+ *
+ * @class
+ */
+uki.background.LinearGradient = uki.newClass(uki.background.CssBox, new function() {
+    
+    var CANVAS_GRADIENT_SIZE = 200;
+    
+    /**
+     * @param options Object { startColor: '#FFFFFF', endColor: '#CCCCCC', horizontal: true, css: 'border: 1px solid #CCC' }
+     */
+    this.init = function(options) {
+        this._options = options;
+        var inset = this._inset = uki.geometry.Inset.create(this._options.inset) || new uki.geometry.Inset();
+        
+        this._container = this._createContainer();
+        var clone = this._container.cloneNode(true);
+        clone.style.cssText += ';width:100px;height:100px;';
+        uki.dom.probe(clone, uki.proxy(function(c) {
+            this._insetWidth = c.offsetWidth - 100 + inset.width();
+            this._insetHeight = c.offsetHeight - 100 + inset.height();
+        }, this));
+        this._container.style.cssText += ';left:' + inset.left + 'px;top:' + inset.top + 'px;right:' + inset.right + 'px;bottom:' + inset.bottom + 'px;';
+
+        this._attached = false;
+    };
+    
+    var urlCache = {};
+    function getGradientURL (startColor, endColor, horizontal, stops) {
+        var key = startColor + endColor + (horizontal ? 1 : 0) + uki.map(stops, function(stop) { return stop.pos + '-' + stop.color; });
+        if (!urlCache[key]) {
+        
+            var canvas = document.createElement('canvas');
+            canvas.width = canvas.height = CANVAS_GRADIENT_SIZE;
+        
+            var context = canvas.getContext('2d'),
+                gradient = context.createLinearGradient(0, 0, horizontal ? CANVAS_GRADIENT_SIZE : 0, horizontal ? 0 : CANVAS_GRADIENT_SIZE);
+
+            gradient.addColorStop(0, startColor);
+            gradient.addColorStop(1, endColor);
+            for (var i=0; i < stops.length; i++) {
+                gradient.addColorStop(stops[i].pos, stops[i].color);
+            };
+            context.fillStyle = gradient;
+            context.fillRect(0, 0, CANVAS_GRADIENT_SIZE, CANVAS_GRADIENT_SIZE);
+            urlCache[key] = canvas.toDataURL && canvas.toDataURL();
+        }
+        return urlCache[key];
+    }
+    
+    function mosFilter (horizontal, startColor, endColor) {
+        return 'filter:progid:DXImageTransform.Microsoft.gradient(gradientType=' + (horizontal ? '1' : '0') + ', startColorstr=#FF' + startColor.substr(1) + ', endColorstr=#FF' + endColor.substr(1) + ');';
+    }
+    
+    
+    this._createContainer = function() {
+        var startColor = this._options.startColor || '#FFFFFF',
+            endColor   = this._options.endColor   || '#CCCCCC',
+            horizontal = this._options.horizontal,
+            stops      = this._options.stops      || [],
+            css        = '',
+            i          = 0,
+            cssProp    = uki.browser.cssLinearGradient(),
+            url;
+            
+        if (cssProp == '-moz-linear-gradient' || cssProp == 'linear-gradient') {
+            css += 'background-image:' + cssProp + '(' + (horizontal ? 'left' : 'top') + ', ' + startColor;
+            for (;i<stops.length;i++) {
+                css += ',' + stops[i].color + ' ' + (stops[i].pos * 100) + '%';
+            }
+            css += ', ' + endColor + ');';
+        } else if (cssProp == '-webkit-gradient') {
+            css += 'background-image:' + cssProp + '(linear, 0% 0%, ' + (horizontal ? '100% 0%' : '0% 100%') + ', from(' + startColor + '), to(' + endColor + ')';
+            for (;i<stops.length;i++) {
+                css += ',color-stop(' + (stops[i].pos * 100) + '%,' + stops[i].color +')';
+            }
+            css += ');';
+        } else if (!uki.browser.canvas() && uki.browser.cssFilter() && stops.length == 0) {
+            css += mosFilter(horizontal, startColor, endColor);
+        }
+        
+        var container = uki.createElement('div', uki.browser.css(this._options.css) + ';position:absolute;overflow:hidden;z-index:' + (this._options.zIndex || '-1') + ';' + css, this._options.innerHTML);
+        container.className = 'uki-background-CssBox';
+        if (css) return container;
+        
+        if (uki.browser.canvas() && (url = getGradientURL(startColor, endColor, horizontal, stops))) {
+            var img = uki.createElement('img', 'position:absolute;left:0;top:0;width:100%;height:100%;z-index:0;');
+            img.src = url;
+            container.appendChild(img);
+        } else if (uki.browser.cssFilter() && stops.length > 0) {
+            stops.unshift({ pos: 0, color: startColor });
+            stops.push({ pos: 1, color: endColor });
+            var child;
+            for (;i<stops.length-1;i++) {
+                child = uki.createElement('div', 'position:absolute;z-index:-1' + 
+                    ';left:'   + (horizontal ? stops[i].pos * 100 - (i && 1) + '%' : '0') +
+                    ';top:'    + (horizontal ? '0' : stops[i].pos * 100 - (i && 1) + '%') +
+                    ';width:'  + (horizontal ? (stops[i+1].pos - stops[i].pos) * 100 + 1 + '%' : '100%') +
+                    ';height:' + (horizontal ? '100%' : (stops[i+1].pos - stops[i].pos) * 100 + 1 + '%') +
+                    ';' + mosFilter(horizontal, stops[i].color, stops[i+1].color)
+                );
+                container.appendChild(child);
+            }
+        }
+        return container;
+    };
+    
+    /**#@-*/
+});
+
+
+/**
+ * Adds a div with colored rows to dom
+ *
+ * @class
+ */
+uki.background.Rows = uki.newClass(new function() {
+    var cache = [],
+        packSize = 100;
+    
+    /**#@+ @memberOf uki.background.Rows.prototype */
+    
+    this.init = function(height, colors) {
+        this._height = height || 20;
+        this._colors = uki.isArray(colors) ? colors : colors.split(' ');
+        this._packSize = CEIL(packSize/this._colors.length)*this._colors.length;
+        this._renderedHeight = 0;
+        this._visibleExt = 200;
+        if (this._colors.length == 1) this._colors = this._colors.concat(['#FFF']);
+    };
+    
+    this.attachTo = function(comp) {
+        this._comp && this.detach();
+        this._comp = comp;
+        if (!this._container) {
+            this._container = uki.createElement(
+                'div', 
+                'position:absolute;left:0;top:0;width:100%;z-index:-1'
+            );
+            this._container.className = 'uki-background-Rows' ;
+        }
+        this._layoutHandler = this._layoutHandler || uki.proxy(function(e) { this.layout(e.rect, e.visibleRect); }, this);
+        this._comp.dom().appendChild(this._container);
+        this._comp.bind('layout', this._layoutHandler);
+    };
+    
+    this.layout = function(rect, visibleRect) {
+        var height = visibleRect ? visibleRect.height + this._visibleExt*2 : rect.maxY();
+        while (this._renderedHeight < height) {
+            var h = packSize * this._height,
+                c = uki.createElement('div', 'height:' + h + 'px;overflow:hidden;width:100%;', getPackHTML(this._height, this._colors));
+            this._renderedHeight += h;
+            this._container.appendChild(c);
+        }
+        if (visibleRect) {
+            this._container.style.top = CEIL((visibleRect.y - this._visibleExt)/this._height/this._colors.length)*this._height*this._colors.length + 'px';
+        }
+    };
+    
+    this.detach = function() {
+        if (!this._comp) return;
+        this._comp.dom().removeChild(this._container);
+        this._comp.unbind('layout', this._layoutHandler);
+        this._comp = null;
+    };
+    
+    /**#@-*/
+    
+    function getPackHTML (height, colors) {
+        var key = height + ' ' + colors.join(' '),
+            rows = [],
+            html = [],
+            i, l = colors.length;
+        if (!cache[key]) {
+            for (i=0; i < l; i++) {
+                rows[i] = ['<div style="height:', height, 'px;width:100%;overflow:hidden;', 
+                            (colors[i] ? 'background:' + colors[i] : ''),
+                            '"></div>'].join('');
+            };
+            for (i=0; i < packSize; i++) {
+                html[i] = rows[i%l];
+            };
+            cache[key] = html.join('');
+        }
+        return cache[key];
+    }
+});
+
+
+/**
+ * @class
+ */
+uki.background.Multi = uki.newClass({
+    init: function() {
+        this._bgs = Array.prototype.slice.call(arguments, 0);
+    },
+    attachTo: function(comp) {
+        for (var i=0, $this$_bgs$length = this._bgs.length; i < $this$_bgs$length; i++) {
+            this._bgs[i].attachTo(comp);
+        };
+    },
+    detach: function() {
+        for (var i=0, $this$_bgs$length = this._bgs.length; i < $this$_bgs$length; i++) {
+            this._bgs[i].detach();
+        };
+    }
+});
+
+
+/**
+ * @namespace
+ */
+(function() {
+var self = uki.theme = {
+    themes: [],
+
+    register: function(theme, /* internal */ themes) {
+        (themes = self.themes)[ themes.length] = theme;
+    },
+
+    background: function(name, params) {
+        return self._namedResource(name, 'background', params) || new uki.background.Null();
+    },
+
+    image: function(name, params) {
+        return self._namedResource(name, 'image', params) || new Image();
+    },
+
+    imageSrc: function(name, params) {
+        return self._namedResource(name, 'imageSrc', params) || '';
+    },
+
+    style: function(name, params) {
+        return self._namedResource(name, 'style', params) || '';
+    },
+
+    dom: function(name, params) {
+        return self._namedResource(name, 'dom', params) || uki.createElement('div');
+    },
+
+    template: function(name, params) {
+        return self._namedResource(name, 'template', params) || '';
+    },
+
+    _namedResource: function(name, type, params, i, result) {
+        for ( i = self.themes.length - 1 ; i >= 0; i--) {
+            if (result = (self.themes[i] [type](name, params)))
+                               return result;
+        };
+        return null;
+    }
+};
+})();
+
+
+
+/**
+ * @class
+ */
+uki.theme.Base = {
+    images: [],
+    imageSrcs: [],
+    backgrounds: [],
+    doms: [],
+    styles: [],
+    templates: [],
+    
+    background: function(name, params) {
+        return this.backgrounds[name] && this.backgrounds[name](params);
+    },
+
+    image: function(name, params) {
+        if (this.images[name]) return this.images[name](params);
+        return this.imageSrcs[name] && uki.image.apply(uki, this.imageSrcs[name](params));
+    },
+    
+    imageSrc: function(name, params) {
+        if (this.imageSrcs[name]) return uki.imageSrc.apply(uki, this.imageSrcs[name](params));
+        return this.images[name] && this.images[name](params).src;
+    },
+    
+    dom: function(name, params) {
+        return this.doms[name] && this.doms[name](params);
+    },
+    
+    style: function(name, params) {
+        return this.styles[name] && this.styles[name](params);
+    },
+    
+    template: function(name, params) {
+        return this.templates[name] && this.templates[name](params);
+    }
+};
+
+
+/**
+ * Simple and fast (2x-15x faster than regexp) html template
+ * @example
+ *   var t = new uki.theme.Template('<p class="${className}">${value}</p>')
+ *   t.render({className: 'myClass', value: 'some html'})
+ */
+uki.theme.Template = function(code) {
+    var parts = code.split('${'), i, l, tmp;
+    this.parts = [parts[0]];
+    this.names = [];
+    for (i=1, l = parts.length; i < l; i++) {
+        tmp = parts[i].split('}');
+        this.names.push(tmp.shift());
+        this.parts.push('');
+        this.parts.push(tmp.join('}'));
+    };
+};
+
+uki.theme.Template.prototype.render = function(values) {
+    for (var i=0, names = this.names, l = names.length; i < l; i++) {
+        this.parts[i*2+1] = values[names[i]] || '';
+    };
+    return this.parts.join('');
+};
+
+
+/**
+ * @namespace
+ */
+uki.view.utils = new function() {
+    this.visibleRect = function (from, upTo) {
+        var queue = [],
+            rect, i, tmpRect, c = from;
+            
+        do {
+            queue[queue.length] = c;
+            c = c.parent();
+        } while (c && c != upTo);
+        
+        if (upTo && upTo != from) queue[queue.length] = upTo;
+
+        for (i = queue.length - 1; i >= 0; i--){
+            c = queue[i];
+            tmpRect = visibleRect(c);
+            rect = rect ? rect.intersection(tmpRect) : tmpRect;
+            rect.x -= c.rect().x;
+            rect.y -= c.rect().y;
+            
+        };
+        return rect;
+    };
+    
+    this.top = function(c) {
+        while (c.parent()) c = c.parent();
+        return c;
+    };
+    
+    this.offset = function(c, upTo) {
+        var offset = new Point(),
+            rect;
+        
+        while (c && c != upTo) {
+            rect = c.rect();
+            offset.x += rect.x;
+            offset.y += rect.y;
+            if (c.scrollTop) {
+                offset.x -= c.scrollLeft();
+                offset.y -= c.scrollTop();
+            }
+            c = c.parent();
+        }
+        return offset;
+    };
+    
+    this.scrollableParent = function(c) {
+        do {
+            if (uki.isFunction(c.scrollTop)) return c;
+            c = c.parent();
+        } while (c);
+        return null;
+    };
+    
+    /** @inner */
+    function visibleRect (c) {
+        return c.visibleRect ? c.visibleRect() : c.rect().clone();
+    }
+    /**#@-*/ 
+};
+
+uki.extend(uki.view, uki.view.utils);
+
+
+/**
+ * @class
+ */
+uki.view.Styleable = new function() {
+    /** @scope uki.view.Styleable.prototype */
+    
+    /**
+     * @name style
+     * @memberOf uki.view.Styleable#
+     * @function
+     */
+    this.style = function(name, value) {
+        if (typeof name == 'string') return this._style(name, value);
+        
+        uki.each(name, function(name, value) {
+            this._style(name, value);
+        }, this);
+        return this;
+    };
+    
+    this._style = function(name, value) {
+        if (value === undefined) return this._dom.style[name];
+        this._dom.style[name] = value;
+        return this;
+    };
+    
+    // TODO: is this really needed?
+    // uki.each('fontSize,textAlign,color,fontFamily,fontWeight,lineHeight,zIndex'.split(','), function(i, name) {
+    //     proto[name] = function(value) {
+    //         return this._style(name, value);
+    //     };
+    // });
+    
+    /**
+     * Sets whether text of the view can be selected.
+     *
+     * @memberOf uki.view.Styleable#
+     * @name textSelectable
+     * @function
+     * @param {boolean=} state 
+     * @returns {boolean|uki.view.Base} current textSelectable state of self
+     */
+    this.textSelectable = uki.newProp('_textSelectable', function(state) {
+        this._textSelectable = state;
+        if (uki.browser.cssUserSelect() != 'unsupported') {
+            this._dom.style[uki.camalize(uki.browser.cssUserSelect())] = (state ? '' : uki.browser.cssUserSelect() == '-moz-user-select' ? '-moz-none' : 'none');
+        } else {
+            uki.dom[state ? 'unbind' : 'bind'](this.dom(), 'selectstart', uki.dom.preventDefaultHandler);
+        }
+        this._dom.style.cursor = state ? '' : 'default';
+    });
+    
+    this.draggable = function(state) {
+        if (state === undefined) return this._dom.getAttribute('draggable');
+        this._dom.setAttribute('draggable', true);
+        this._dom.style.WebkitUserDrag = 'element';
+        return this;
+    };
+    
+    /**#@-*/ 
+};
+
+
+
+
+/**
+ * @interface
+ */
+uki.view.Focusable = new function() {/** @lends uki.view.Focusable.prototype */ 
+    
+    // dom: function() {
+    //     return null; // should implement
+    // },
+    this._focusable = true; // default value
+    this._focusOnClick = true;
+    
+    this.focusOnClick = uki.newProp('_focusOnClick');
+    
+    this.focusable = uki.newProp('_focusable', function(v) {
+        this._focusable = v;
+        if (v) this._initFocusable();
+        this._updateFocusable();
+    }),
+    
+    this.disabled = uki.newProp('_disabled', function(d) {
+        var changed = d !== !!this._disabled;
+        this._disabled = d;
+        if (d) this.blur();
+        this._updateFocusable();
+        if (changed && this._updateBg) this._updateBg();
+    }),
+    
+    this._updateFocusable = function() {
+        if (this._preCreatedFocusTarget || !this._focusTarget) return;
+        
+        if (this._focusable && !this._disabled) {
+            this._focusTarget.style.display = 'block';
+        } else {
+            this._focusTarget.style.display = 'none';
+        }
+    }, 
+    
+    this._initFocusable = function(preCreatedFocusTarget) {
+        if ((!preCreatedFocusTarget && !this._focusable) || this._focusTarget) return;
+        this._focusTarget = preCreatedFocusTarget;
+        this._preCreatedFocusTarget = preCreatedFocusTarget;
+        
+        if (!preCreatedFocusTarget) {
+            this._focusTarget = uki.createElement('input', 'position:absolute;left:-9999px;top:0;width:1px;height:1px;');
+            this._focusTarget.className = 'uki-view-Focusable';
+            this.dom().appendChild(this._focusTarget);
+        }
+        this._hasFocus = false;
+        this._firstFocus = true;
+            
+        uki.dom.bind(this._focusTarget, 'focus', uki.proxy(function(e) {
+            this._stopWatingForBlur();
+            if (!this._hasFocus) this._focus(e);
+        }, this));
+        
+        uki.dom.bind(this._focusTarget, 'blur', uki.proxy(function(e) {
+            if (this._hasFocus) {
+                this._hasFocus = false;
+                this._waitingForBlur = 
+                    setTimeout(uki.proxy(function() { // wait for mousedown refocusing
+                        this._waitingForBlur = false;
+                        if (!this._hasFocus) this._blur();
+                    }, this), 1);
+            }
+        }, this));
+        
+        if (!preCreatedFocusTarget) this.bind('mousedown', function(e) {
+            if (this._focusOnClick) this.focus();
+        });
+        this._updateFocusable();
+    }
+    
+    this._focus = function(e) {
+        this._hasFocus = true;
+        this._firstFocus = false;
+    }
+    
+    this._blur = function(e) {
+        this._hasFocus = false;
+    }
+    
+    this._stopWatingForBlur = function() {
+        if (this._waitingForBlur) {
+            clearTimeout(this._waitingForBlur);
+            this._waitingForBlur = false;
+            this._hasFocus = true;
+        }
+    };
+    
+    this.focus = function() {
+        if (this._focusable && !this._disabled) {
+            this._stopWatingForBlur();
+            if (!this._hasFocus) this._focus();
+            var target = this._focusTarget;
+            setTimeout(function() {
+                try {
+                    target.focus();
+                } catch(e) { }
+                target = null;
+            }, 1);
+        }
+        return this;
+    },
+    
+    this.blur = function() {
+        try {
+            this._focusTarget.blur();
+        } catch(e) {}
+        return this;
+    }
+    
+    this.hasFocus = function() {
+        return this._hasFocus;
+    }
+    
+    this._bindToDom = function(name) {
+        if (!this._focusTarget || 'keyup keydown keypress focus blur'.indexOf(name) == -1) return false;
+
+        return uki.view.Observable._bindToDom.call(this, name, this._focusTarget);
+    }
+    
+
+};
+
+
+
+
+
+
+
+
+var ANCHOR_TOP    = 1,
+    ANCHOR_RIGHT  = 2,
+    ANCHOR_BOTTOM = 4,
+    ANCHOR_LEFT   = 8,
+    ANCHOR_WIDTH  = 16,
+    ANCHOR_HEIGHT = 32;
+
+uki.view.declare('uki.view.Base', uki.view.Observable, uki.view.Styleable, function(Observable, Styleable) {
+
+    var layoutId = 1;
+
+    this.defaultCss = 'position:absolute;z-index:100;-moz-user-focus:none;';
+    
+    /**
+     * Base class for all uki views.
+     *
+     * <p>View creates and layouts dom nodes. uki.view.Base defines basic API for other views.
+     * It also defines common layout algorithms.</p>
+     *
+     * Layout
+     *
+     * <p>View layout is defined by rectangle and anchors.
+     * Rectangle is passed to constructor, anchors are set through the #anchors attribute.</p>
+     * 
+     * <p>Rectangle defines initial position and size. Anchors specify how view will move and
+     * resize when its parent is resized.</p>
+     *
+     * 2 phases of layout
+     *
+     * <p>Layout process has 2 phases. 
+     * First views rectangles are recalculated. This may happen several times before dom 
+     * is touched. This is done either explicitly through #rect attribute or through
+     * #parentResized callbacks. 
+     * After rectangles are set #layout is called. This actually updates dom styles.</p>
+     *
+     * @example
+     * uki({ view: 'Base', rect: '10 20 100 50', anchors: 'left top right' })
+     * // Creates Base view with initial x = 10px, y = 20px, width = 100px, height = 50px.
+     * // When parent resizes x, y and height will stay the same. Width will resize with parent.
+     *
+     *
+     * @see uki.view.Base#anchors
+     * @constructor
+     * @augments uki.view.Observable
+     * @augments uki.view.Styleable
+     *
+     * @name uki.view.Base
+     * @implements uki.view.Observable
+     * @param {uki.geometry.Rect} rect initial position and size
+     */
+    this.init = function(rect) {
+        this._parentRect = this._rect = Rect.create(rect);
+        this._setup();
+        uki.initNativeLayout();
+        this._createDom();
+    };
+    
+    /**#@+ @memberOf uki.view.Base# */
+    
+    /** @private */
+    this._setup = function() {
+        uki.extend(this, {
+           _anchors: 0,
+           _parent: null,
+           _visible: true,
+           _needsLayout: true,
+           _textSelectable: false,
+           _styleH: 'left',
+           _styleV: 'top',
+           _firstLayout: true
+        });
+        this.defaultCss += uki.theme.style('base');
+    };
+    
+    /**
+     * Get views container dom node.
+     * @returns {Element} dom
+     */
+    this.dom = function() {
+        return this._dom;
+    };
+    
+    /* ------------------------------- Common settings --------------------------------*/
+    /**
+     * Used for fast (on hash lookup) view searches: uki('#view_id');
+     *
+     * @param {string=} id New id value
+     * @returns {string|uki.view.Base} current id or self
+     */
+    this.id = function(id) {
+        if (id === undefined) return this._dom.id;
+        if (this._dom.id) uki.unregisterId(this);
+        this._dom.id = id;
+        uki.registerId(this);
+        return this;
+    };
+    
+    /**
+     * Accessor for dom().className
+     * @param {string=} className
+     *
+     * @returns {string|uki.view.Base} className or self
+     */
+    uki.delegateProp(this, 'className', '_dom');
+    
+    /**
+     * Accessor for view visibility. 
+     *
+     * @param {boolean=} state 
+     * @returns {boolean|uki.view.Base} current visibility state of self
+     */
+    this.visible = function(state) {
+        if (state === undefined) return this._dom.style.display != 'none';
+        
+        this._dom.style.display = state ? 'block' : 'none';
+        return this;
+    };
+    
+    /**
+     * Accessor for background attribute. 
+     * @param {string|uki.background.Base=} background
+     * @returns {uki.background.Base|uki.view.Base} current background or self
+     */
+    this.background = function(val) {
+        if (val === undefined && !this._background && this.defaultBackground) this._background = this.defaultBackground();
+        if (val === undefined) return this._background;
+        val = uki.background(val);
+        
+        if (val == this._background) return this;
+        if (this._background) this._background.detach(this);
+        val.attachTo(this);
+        
+        this._background = val;
+        return this;
+    };
+    
+    /**
+     * Accessor for default background attribute. 
+     * @name defaultBackground
+     * @function
+     * @returns {uki.background.Base} default background if not overridden through attribute
+     */
+    this.defaultBackground = function() {
+        return this._defaultBackground && uki.background(this._defaultBackground);
+    };
+    
+    /* ----------------------------- Container api ------------------------------*/
+    
+    /**
+     * Accessor attribute for parent view. When parent is set view appends its #dom
+     * to parents #domForChild
+     *
+     * @param {?uki.view.Base=} parent
+     * @returns {uki.view.Base} parent or self
+     */
+    this.parent = function(parent) {
+        if (parent === undefined) return this._parent;
+        
+        // if (this._parent) this._dom.parentNode.removeChild(this._dom);
+        this._parent = parent;
+        // if (this._parent) this._parent.domForChild(this).appendChild(this._dom);
+        return this;
+    };
+    
+    /**
+     * Accessor for childViews. @see uki.view.Container for implementation
+     * @returns {Array.<uki.view.Base>}
+     */
+    this.childViews = function() {
+        return [];
+    };
+    
+    /**
+     * Reader for previous view
+     * @returns {uki.view.Base}
+     */
+    this.prevView = function() {
+        if (!this.parent()) return null;
+        return this.parent().childViews()[this._viewIndex - 1] || null;
+    };
+    
+    /**
+     * Reader for next view
+     * @returns {uki.view.Base}
+     */
+    this.nextView = function() {
+        if (!this.parent()) return null;
+        return this.parent().childViews()[this._viewIndex + 1] || null;
+    };
+    
+    
+    /* ----------------------------- Layout ------------------------------*/
+    
+    /**
+     * Sets or retrieves view's position and size.
+     *
+     * @param {string|uki.geometry.Rect} newRect
+     * @returns {uki.view.Base} self
+     */
+    this.rect = function(newRect) {
+        if (newRect === undefined) return this._rect;
+
+        newRect = Rect.create(newRect);
+        this._parentRect = newRect;
+        this._rect = this._normalizeRect(newRect);
+        this._needsLayout = this._needsLayout || layoutId++;
+        return this;
+    };
+    
+    /**
+     * Set or get sides which the view should be attached to.
+     * When a view is attached to a side the distance between this side and views border
+     * will remain constant on resize. Anchor can be any combination of
+     * "top", "right", "bottom", "left", "width" and "height". 
+     * If you set both "right" and "left" than "width" is assumed.
+     *
+     * Anchors are stored as a bit mask. Though its easier to set them using strings
+     *
+     * @function
+     * @param {string|number} anchors
+     * @returns {number|uki.view.Base} anchors or self
+     */
+    this.anchors = uki.newProp('_anchors', function(anchors) {
+        if (anchors.indexOf) {
+            var tmp = 0;
+            if (anchors.indexOf('right'  ) > -1) tmp |= ANCHOR_RIGHT; 
+            if (anchors.indexOf('bottom' ) > -1) tmp |= ANCHOR_BOTTOM;
+            if (anchors.indexOf('top'    ) > -1) tmp |= ANCHOR_TOP;   
+            if (anchors.indexOf('left'   ) > -1) tmp |= ANCHOR_LEFT;  
+            if (anchors.indexOf('width'  ) > -1 || (tmp & ANCHOR_LEFT && tmp & ANCHOR_RIGHT)) tmp |= ANCHOR_WIDTH;  
+            if (anchors.indexOf('height' ) > -1 || (tmp & ANCHOR_BOTTOM && tmp & ANCHOR_TOP)) tmp |= ANCHOR_HEIGHT;
+            anchors = tmp;
+        }
+        this._anchors = anchors;
+        this._styleH = anchors & ANCHOR_LEFT ? 'left' : 'right';
+        this._styleV = anchors & ANCHOR_TOP ? 'top' : 'bottom';
+    });
+    
+    /**
+     * Returns rectangle for child layout. Usually equals to #rect. Though in some cases,
+     * client rectangle my differ from #rect. Example uki.view.ScrollPane.
+     *
+     * @param {uki.view.Base} child 
+     * @returns {uki.geometry.Rect}
+     */
+    this.rectForChild = function(child) {
+        return this.rect();
+    };
+    
+    /**
+     * Updates dom to match #rect property.
+     *
+     * Layout is designed to minimize dom writes. If view is anchored to the right then
+     * style.right is used, style.left for left anchor, etc. If browser supports this
+     * both style.right and style.left are used. Otherwise style.width will be updated
+     * manually on each resize. 
+     *
+     * @fires event:layout
+     * @see uki.dom.initNativeLayout
+     */
+    this.layout = function() {
+        this._layoutDom(this._rect);
+        this._needsLayout = false;
+        this.trigger('layout', {rect: this._rect, source: this});
+        this._firstLayout = false;
+    };
+    
+    this.layoutIfNeeded = function() {
+        if (this._needsLayout && this.visible()) this.layout();
+    };
+    
+    
+    /**
+     * @function uki.view.Base#minSize
+     * @function uki.view.Base#maxSize
+     */
+    uki.each(['min', 'max'], function(i, name) {
+        var attr = name + 'Size',
+            prop = '_' + attr;
+        this[attr] = function(s) {
+            if (s === undefined) return this[prop] || new Size();
+            this[prop] = Size.create(s);
+            this.rect(this._parentRect);
+            this._dom.style[name + 'Width'] = this[prop].width ? this[prop].width + PX : '';
+            this._dom.style[name + 'Height'] = this[prop].height ? this[prop].height + PX : '';
+            return this;
+        };
+    }, this);
+    
+    /**
+     * Resizes view when parent changes size according to anchors.
+     * Called from parent view. Usually after parent's #rect is called.
+     *
+     * @param {uki.geometry.Rect} oldSize
+     * @param {uki.geometry.Rect} newSize
+     */
+    this.parentResized = function(oldSize, newSize) {
+        var newRect = this._parentRect.clone(),
+            dX = (newSize.width - oldSize.width) /
+                ((this._anchors & ANCHOR_LEFT ^ ANCHOR_LEFT ? 1 : 0) +   // flexible left
+                (this._anchors & ANCHOR_WIDTH ? 1 : 0) +             
+                (this._anchors & ANCHOR_RIGHT ^ ANCHOR_RIGHT ? 1 : 0)),   // flexible right
+            dY = (newSize.height - oldSize.height) /
+                ((this._anchors & ANCHOR_TOP ^ ANCHOR_TOP ? 1 : 0) +      // flexible top
+                (this._anchors & ANCHOR_HEIGHT ? 1 : 0) + 
+                (this._anchors & ANCHOR_BOTTOM ^ ANCHOR_BOTTOM ? 1 : 0)); // flexible right
+                
+        if (this._anchors & ANCHOR_LEFT ^ ANCHOR_LEFT) newRect.x += dX;
+        if (this._anchors & ANCHOR_WIDTH) newRect.width += dX;
+
+        if (this._anchors & ANCHOR_TOP ^ ANCHOR_TOP) newRect.y += dY;
+        if (this._anchors & ANCHOR_HEIGHT) newRect.height += dY;
+        this.rect(newRect);
+    };
+    
+    /**
+     * Called when child changes it's size
+     */
+    this.childResized = function(child) {
+        // do nothing, extend in subviews
+    };
+    
+    /**
+     * Resizes view to its contents. Contents size is determined by view.
+     * View can be resized by width, height or both. This is specified through
+     * autosizeStr param.
+     * View will grow shrink according to its #anchors.
+     *
+     * @param {autosizeStr} autosize 
+     * @returns {uki.view.Base} self
+     */
+    this.resizeToContents = function(autosizeStr) {
+        var autosize = decodeAutosize(autosizeStr);
+        if (0 == autosize) return this;
+        
+        var oldRect = this.rect(),
+            newRect = this._calcRectOnContentResize(autosize);
+        // if (newRect.eq(oldRect)) return this;
+        // this.rect(newRect);
+        this._rect = this._parentRect = newRect;
+        this._needsLayout = true;
+        return this;
+    };
+    
+    /**
+     * Calculates view's contents size. Redefined in subclasses.
+     *
+     * @param {number} autosize Bitmask
+     * @returns {uki.geometry.Rect}
+     */
+    this.contentsSize = function(autosize) {
+        return this.rect();
+    };
+    
+    /** @private */
+    this._normalizeRect = function(rect) {
+        if (this._minSize) {
+            rect = new Rect(rect.x, rect.y, MAX(this._minSize.width, rect.width), MAX(this._minSize.height, rect.height));
+        }
+        if (this._maxSize) {
+            rect = new Rect(rect.x, rect.y, MIN(this._maxSize.width, rect.width), MIN(this._maxSize.height, rect.height));
+        }
+        return rect;
+    };
+    
+    
+    
+    /** @ignore */
+    function decodeAutosize (autosizeStr) {
+        if (!autosizeStr) return 0;
+        var autosize = 0;
+        if (autosizeStr.indexOf('width' ) > -1) autosize = autosize | ANCHOR_WIDTH;
+        if (autosizeStr.indexOf('height') > -1) autosize = autosize | ANCHOR_HEIGHT;
+        return autosize;
+    }
+    
+
+    /** @private */
+    this._initBackgrounds = function() {
+        if (this.background()) this.background().attachTo(this);
+    };
+    
+    /** @private */
+    this._calcRectOnContentResize = function(autosize) {
+        var newSize = this.contentsSize( autosize ),
+            oldSize = this.rect();
+
+        if (newSize.eq(oldSize)) return oldSize; // nothing changed
+        
+        // calculate where to resize
+        var newRect = this.rect().clone(),
+            dX = newSize.width - oldSize.width,
+            dY = newSize.height - oldSize.height;
+    
+        if (autosize & ANCHOR_WIDTH) {
+            if (this._anchors & ANCHOR_LEFT ^ ANCHOR_LEFT && this._anchors & ANCHOR_RIGHT ^ ANCHOR_RIGHT) {
+                newRect.x -= dX/2;
+            } else if (this._anchors & ANCHOR_LEFT ^ ANCHOR_LEFT) {
+                newRect.x -= dX;
+            }
+            newRect.width += dX;
+        }
+        
+        if (autosize & ANCHOR_HEIGHT) {
+            if (this._anchors & ANCHOR_TOP ^ ANCHOR_TOP && this._anchors & ANCHOR_BOTTOM ^ ANCHOR_BOTTOM) {
+                newRect.y -= dY/2;
+            } else if (this._anchors & ANCHOR_TOP ^ ANCHOR_TOP) {
+                newRect.y -= dY;
+            }
+            newRect.height += dY;
+        }
+        
+        return newRect;
+    };
+    
+    /** @function
+    @name uki.view.Base#width */
+    /** @function
+    @name uki.view.Base#height */
+    /** @function
+    @name uki.view.Base#minX */
+    /** @function
+    @name uki.view.Base#maxX */
+    /** @function
+    @name uki.view.Base#minY */
+    /** @function
+    @name uki.view.Base#maxY */
+    /** @function
+    @name uki.view.Base#left */
+    /** @function
+    @name uki.view.Base#top */
+    uki.each(['width', 'height', 'minX', 'maxX', 'minY', 'maxY', 'x', 'y', 'left', 'top'], function(index, attr) {
+        this[attr] = function(value) {
+            if (value === undefined) return uki.attr(this.rect(), attr);
+            uki.attr(this.rect(), attr, value);
+            return this;
+        };
+    }, this);
+    
+    /* ---------------------------------- Dom --------------------------------*/
+    /**
+     * Called through a second layout pass when _dom should be created
+     * @private
+     */
+    this._createDom = function() {
+        this._dom = uki.createElement('div', this.defaultCss);
+        this._initClassName();
+    };
+    
+    this._initClassName = function() {
+        this._dom.className = this.typeName().replace(/\./g, '-');
+    };
+    
+    /**
+     * Called through a second layout pass when _dom is already created
+     * @private
+     */
+    this._layoutDom = function(rect) {
+        var l = {}, s = uki.supportNativeLayout, relativeRect = this.parent().rectForChild(this);
+        if (s && this._anchors & ANCHOR_LEFT && this._anchors & ANCHOR_RIGHT) {
+            l.left = rect.x;
+            l.right = relativeRect.width - rect.x - rect.width;
+        } else {
+            l.width = rect.width;
+            l[this._styleH] = this._styleH == 'left' ? rect.x : relativeRect.width - rect.x - rect.width;
+        }
+        
+        if (s && this._anchors & ANCHOR_TOP && this._anchors & ANCHOR_BOTTOM) {
+            l.top = rect.y;
+            l.bottom = relativeRect.height - rect.y - rect.height;
+        } else {
+            l.height = rect.height;
+            l[this._styleV] = this._styleV == 'top'  ? rect.y : relativeRect.height - rect.y - rect.height;
+        }
+        this._lastLayout = uki.dom.layout(this._dom.style, l, this._lastLayout);
+        if (this._firstLayout) this._initBackgrounds();
+        return true;
+    };
+    
+    /** @private */
+    this._bindToDom = function(name) {
+        if ('resize layout'.indexOf(name) > -1) return true;
+        return uki.view.Observable._bindToDom.call(this, name);
+    };
+    
+    /**#@-*/
+});
+
+
+
+/**
+ * @class
+ * @augments uki.view.Base
+ * @name uki.view.Container
+ */
+uki.view.declare('uki.view.Container', uki.view.Base, function(Base) {
+    /**#@+ @memberOf uki.view.Container# */
+    
+    this._inset = new Inset();
+    
+    /** @private */
+    this._setup = function() {
+        this._childViews = [];
+        Base._setup.call(this);
+    };
+    
+    /** @ignore */
+    function maxProp (c, prop) {
+        var val = 0, i, l;
+        for (i = c._childViews.length - 1; i >= 0; i--){
+            if (!c._childViews[i].visible()) continue;
+            val = MAX(val, c._childViews[i].rect()[prop]());
+        };
+        return val;
+    }
+    
+    this.contentsWidth = function() {
+        return maxProp(this, 'maxX') + this.inset().right;
+    };
+    
+    this.contentsHeight = function() {
+        return maxProp(this, 'maxY') + this.inset().bottom;
+    };
+    
+    this.contentsSize = function() {
+        return new Size(this.contentsWidth(), this.contentsHeight());
+    };
+    
+    /**
+     * Sets or retrieves view child view.
+     * @param anything uki.build can parse
+     *
+     * Note: if setting on view with child views, all child view will be removed
+     */
+    this.childViews = function(val) {
+        if (val === undefined) return this._childViews;
+        uki.each(this._childViews, function(i, child) {
+            this.removeChild(child);
+        }, this);
+        uki.each(uki.build(val), function(tmp, child) {
+            this.appendChild(child);
+        }, this);
+        return this;
+    };
+    
+    /**
+     * Remove particular child
+     */
+    this.removeChild = function(child) {
+        child.parent(null);
+        this.domForChild(child).removeChild(child.dom());
+        var index = child._viewIndex,
+            i, l;
+        for (i=index+1, l = this._childViews.length; i < l; i++) {
+            this._childViews[i]._viewIndex--;
+        };
+        this._childViews = uki.grep(this._childViews, function(elem) { return elem != child; });
+        this._contentChanged();
+    };
+    
+    /**
+     * Adds a child.
+     */
+    this.appendChild = function(child) {
+        child._viewIndex = this._childViews.length;
+        this._childViews.push(child);
+        child.parent(this);
+        this.domForChild(child).appendChild(child.dom());
+        this._contentChanged();
+    };
+    
+    /**
+     * Insert child before target beforeChild
+     * @param {uki.view.Base} child Child to insert
+     * @param {uki.view.Base} beforeChild Existent child before which we should insert
+     */
+    this.insertBefore = function(child, beforeChild) {
+        var i, l;
+        child._viewIndex = beforeChild._viewIndex;
+        for (i=beforeChild._viewIndex, l = this._childViews.length; i < l; i++) {
+            this._childViews[i]._viewIndex++;
+        };
+        this._childViews.splice(beforeChild._viewIndex-1, 0, child);
+        child.parent(this);
+        this.domForChild(child).insertBefore(child.dom(), beforeChild.dom());
+        this._contentChanged();
+    };
+    
+    /**
+     * Should return a dom node for a child.
+     * Child should append itself to this dom node
+     */
+    this.domForChild = function(child) {
+        return this._dom;
+    };
+    
+    this.inset = uki.newProp('_inset', function(v) {
+        this._inset = Inset.create(v);
+    });
+    
+    /** @private */
+    this._contentChanged = function() {
+        // called on every insertBefore, appendChild, removeChild
+    };
+    
+    this._layoutDom = function(rect) {
+        Base._layoutDom.call(this, rect);
+        this._layoutChildViews(rect);
+    };
+
+    /** @private */
+    this._layoutChildViews = function() {
+        for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
+            childViews[i].layoutIfNeeded();
+        };
+    };
+    
+    /**
+     * @fires event:resize
+     */
+    this.rect = function(newRect) {
+        if (newRect === undefined) return this._rect;
+
+        newRect = Rect.create(newRect);
+        this._parentRect = newRect;
+        var oldRect = this._rect;
+        if (!this._resizeSelf(newRect)) return this;
+        this._needsLayout = true;
+        
+        if (oldRect.width != newRect.width || oldRect.height != newRect.height) this._resizeChildViews(oldRect);
+        this.trigger('resize', {oldRect: oldRect, newRect: this._rect, source: this});
+        return this;
+    };
+    
+    /** @private */
+    this._resizeSelf = function(newRect) {
+        // if (newRect.eq(this._rect)) return false;
+        this._rect = this._normalizeRect(newRect);
+        return true;
+    };
+    
+    /**
+     * Called to notify all interested parties: childViews and observers
+     * @private
+     */
+    this._resizeChildViews = function(oldRect) {
+        for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
+            childViews[i].parentResized(oldRect, this._rect);
+        };
+    };
+    
+   /**#@-*/ 
+});
+/**
+ * Basic container view
+ *
+ *
+ * @author voloko
+ * @name uki.view.Box
+ * @class
+ * @extends uki.view.Container
+ */
+uki.view.declare('uki.view.Box', uki.view.Container, {});
+
+/**
+ * Image
+ *
+ * @author voloko
+ * @name uki.view.Image
+ * @class
+ * @extends uki.view.Base
+ */
+uki.view.declare('uki.view.Image', uki.view.Base, function() {
+    this.typeName = function() { return 'uki.view.Image'; };
+    
+    /**
+     * @function
+     * @name uki.view.Image#src
+     */
+    uki.delegateProp(this, 'src', '_dom');
+    
+    this._createDom = function() {
+        this._dom = uki.createElement('img', this.defaultCss);
+        this._initClassName();
+    };
+});
+
+
+
+/**
+ * Label View
+ * Contains any html
+ * 
+ * @author voloko
+ * @name uki.view.Label
+ * @class
+ * @extends uki.view.Base
+ */
+uki.view.declare('uki.view.Label', uki.view.Base, function(Base) {
+
+    this._setup = function() {
+        Base._setup.call(this);
+        uki.extend(this, {
+            _scrollable: false,
+            _textSelectable: false,
+            _inset: new Inset()
+        });
+        this.defaultCss += uki.theme.style('label');
+    };
+    
+    this._style = function(name, value) {
+        if (value !== undefined && uki.inArray(name, uki.browser.textStyles) != -1) {
+            this._label.style[name] = value;
+        }
+        return Base._style.call(this, name, value);
+    };
+    
+    this.adaptToContents = uki.newProp('_adaptToContents');
+    
+    this.textSelectable = function(state) {
+        if (state !== undefined && !this._textSelectProp) {
+            this._label.unselectable = state ? '' : 'on';
+        }
+        return Base.textSelectable.call(this, state);
+    };  
+    
+    /**
+     * Warning! this operation is expensive
+     */
+    this.contentsSize = function(autosize) {
+        var clone = this._createLabelClone(autosize), inset = this.inset(), size;
+        
+        uki.dom.probe(clone, function() {
+            size = new Size(clone.offsetWidth + inset.width(), clone.offsetHeight + inset.height());
+        });
+        return size;
+    };
+    
+    /**
+     * Read/write escaped html contents 
+     * @function
+     * @name uki.view.Label#text
+     */
+    this.text = function(text) {
+        return text === undefined ? this.html() : this.html(uki.escapeHTML(text));
+    };
+    
+    /**
+     * Read/write html contents
+     * @function
+     * @name uki.view.Label#html
+     */
+    this.html = function(html) {
+        if (html === undefined) return this._label.innerHTML;
+        this._label.innerHTML = html;
+        return this;
+    };
+    
+    /**
+     * Insets between text and view borders
+     * @function
+     * @name uki.view.Label#inset
+     */
+    this.inset = uki.newProp('_inset', function(inset) {
+        this._inset = Inset.create(inset);
+    });
+
+    /**
+     * Whether label have inline scrollbars on not
+     * @function
+     * @name uki.view.Label#scrollable
+     */
+    this.scrollable = uki.newProp('_scrollable', function(state) {
+        this._scrollable = state;
+        this._label.style.overflow = state ? 'auto' : 'hidden';
+    });
+    
+    /**
+     * Whether can have multiline lines or not
+     * @function
+     * @name uki.view.Label#multiline
+     */
+    this.multiline = uki.newProp('_multiline', function(state) {
+        this._multiline = state;
+        this._label.style.whiteSpace = state ? '' : 'nowrap';
+    });
+    
+    this._createLabelClone = function(autosize) {
+        var clone = this._label.cloneNode(true),
+            inset = this.inset(), rect = this.rect();
+            
+        if (autosize & ANCHOR_WIDTH) {
+            clone.style.width = clone.style.right = '';
+        } else if (uki.supportNativeLayout) {
+            clone.style.right = '';
+            clone.style.width = rect.width - inset.width() + 'px';
+        }
+        if (autosize & ANCHOR_HEIGHT) {
+            clone.style.height = clone.style.bottom = '';
+        } else if (uki.supportNativeLayout) {
+            clone.style.bottom = '';
+            clone.style.height = rect.height - inset.height() + 'px';
+        }
+        clone.style.paddingTop = 0;
+        clone.style.visibility = 'hidden';
+        return clone;
+    };
+    
+    this._createDom = function() {
+        Base._createDom.call(this);
+        this._label = uki.createElement('div', this.defaultCss + 'white-space:nowrap;'); // text-shadow:0 1px 0px rgba(255,255,255,0.8);
+        this._dom.appendChild(this._label);
+        this.textSelectable(this.textSelectable());
+    };
+    
+    this._layoutDom = function() {
+        var inset = this._inset,
+            l,
+            a = this._anchors,
+            watchField = '', watchValue;
+        
+        if (uki.supportNativeLayout) {
+            l = {
+                left: inset.left, 
+                top: inset.top, 
+                right: inset.right,
+                bottom: inset.bottom
+            };
+        } else {
+            l = {
+                left: inset.left, 
+                top: inset.top, 
+                width: this._rect.width - inset.width(),
+                height: this._rect.height - inset.height()
+            };
+        }
+        
+        if (!(a & ANCHOR_BOTTOM)) {
+            l.height = l.bottom = undefined;
+            watchField = 'offsetHeight';
+        } else if (!(a & ANCHOR_TOP)) {
+            l.height = l.bottom = undefined;
+            watchField = 'offsetHeight';
+        } else if (!(a & ANCHOR_RIGHT)) {
+            l.right = l.width = undefined;
+            watchField = 'offsetWidth';
+        } else if (!(a & ANCHOR_LEFT)) {
+            l.left = l.width = undefined;
+            watchField = 'offsetWidth';
+        }
+        
+        Base._layoutDom.apply(this, arguments);
+        
+        if (!this.multiline()) {
+            var fz = parseInt(this.style('fontSize'), 10) || 12;
+            this._label.style.lineHeight = (this._rect.height - inset.top - inset.bottom) + 'px';
+            // this._label.style.paddingTop = MAX(0, this._rect.height/2 - fz/2) + 'px';
+        }
+        this._lastLabelLayout = uki.dom.layout(this._label.style, l, this._lastLabelLayout);
+        
+        if (this.adaptToContents() && watchField) {
+            watchValue = this._label[watchField];
+            if (watchValue != this._lastWatchValue && this.parent()) {
+                this.resizeToContents(watchField == 'offsetWidth' ? 'width' : 'height');
+                this.parent().childResized(this);
+            }
+            this._lastWatchValue = watchValue;
+        }
+    };
+    
+});
+
+
+/**
+ * Button view
+ *
+ *
+ * @author voloko
+ * @name uki.view.Button
+ * @class
+ * @extends uki.view.Label
+ * @implements uki.view.Focusable
+ */
+uki.view.declare('uki.view.Button', uki.view.Label, uki.view.Focusable, function(Base, Focusable) {
+    /** @lends uki.view.Button.prototype */
+    
+    this._backgroundPrefix = 'button-';
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        uki.extend(this, {
+            _inset: new Inset(0, 4)
+        });
+        this.defaultCss += "cursor:default;-moz-user-select:none;-webkit-user-select:none;" + uki.theme.style('button');
+    };
+    
+    /**
+     * @function
+     * @name uki.view.Button#backgroundPrefix
+     */
+    uki.addProps(this, ['backgroundPrefix']);
+    
+    /**
+     * @function
+     * @name uki.view.Button#"normal-background"
+     */
+    /**
+     * @function
+     * @name uki.view.Button#"hover-background"
+     */
+    /**
+     * @function
+     * @name uki.view.Button#"down-background"
+     */
+    /**
+     * @function
+     * @name uki.view.Button#"focus-background"
+     */
+     /**
+      * @function
+      * @name uki.view.Button#"disabled-background"
+      */
+    uki.each(['normal', 'hover', 'down', 'focus', 'disabled'], function(i, name) {
+        var property = name + '-background';
+        this[property] = function(bg) {
+            if (bg) this['_' + property] = bg;
+            return this['_' + property] = this['_' + property] || 
+                uki.theme.background(this._backgroundPrefix + name, {height: this.rect().height, view: this});
+        };
+    }, this);
+    
+    this._createLabelClone = function(autosize) {
+        var clone = Base._createLabelClone.call(this, autosize);
+        // clone.style.fontWeight = this.style('fontWeight');
+        return clone;
+    };
+    
+    this._layoutDom = function(rect) {
+        Base._layoutDom.call(this, rect);
+        if (this._firstLayout) {
+            this['hover-background']();
+            this['down-background']();
+
+            this._backgroundByName(this._backgroundName || 'normal');
+        }
+    };
+    
+    this._updateBg = function() {
+        var name = this._disabled ? 'disabled' : this._down ? 'down' : this._over ? 'hover' : 'normal';
+        this._backgroundByName(name);
+    };
+        
+    this._createDom = function() {
+        // dom
+        this._dom = uki.createElement('div', this.defaultCss);
+        this._initClassName();
+        this._label = uki.createElement('div', this.defaultCss); // text-shadow:0 1px 0px rgba(255,255,255,0.8);
+        this._dom.appendChild(this._label);
+        
+        this._dom.appendChild(uki.createElement('div', 'left:0;top:0;width:100%;height:100%;position:absolute;background:url(' + uki.theme.imageSrc('x') + ');'));
+        
+        this.textSelectable(this.textSelectable());
+        this._initFocusable();
+        
+        uki.dom.bind(document, 'mouseup', uki.proxy(this._mouseup, this));
+        this.bind('mousedown', this._mousedown);
+        this.bind('mouseenter', this._mouseenter);
+        this.bind('mouseleave', this._mouseleave);
+        this.bind('keyup', this._keyup);
+        this.bind('keydown', this._keydown);
+    };
+    
+    this._mouseup = function(e) {
+        if (!this._down) return;
+        this._down = false;
+        this._updateBg();
+    };
+    
+    this._mousedown = function(e) {
+        this._down = true;
+        this._updateBg();
+    };
+    
+    this._mouseenter = function(e) {
+        this._over = true;
+        this._updateBg();
+    };
+    
+    this._mouseleave = function(e) {
+        this._over = false;
+        this._updateBg();
+    };
+    
+    this._focus = function(e) {
+        this['focus-background']().attachTo(this);
+        Focusable._focus.call(this, e);
+    };
+    
+    this._keydown = function(e) {
+        if ((e.which == 32 || e.which == 13) && !this._down) this._mousedown();
+    };
+    
+    this._keyup = function(e) {
+        if ((e.which == 32 || e.which == 13) && this._down) {
+            this._mouseup();
+            this.trigger('click', {domEvent: e, source: this});
+        }
+        if (e.which == 27 && this._down) {
+            this._mouseup();
+        }
+    };
+    
+    this._blur = function(e) {
+       this['focus-background']().detach();
+       Focusable._blur.call(this, e);
+    };
+    
+    this._backgroundByName = function(name) {
+        var bg = this[name + '-background']();
+        if (this._background == bg) return;
+        if (this._background) this._background.detach();
+        bg.attachTo(this);
+        this._background = bg;
+        this._backgroundName = name;
+    };
+
+    this._bindToDom = function(name) {
+        return uki.view.Focusable._bindToDom.call(this, name) || uki.view.Label.prototype._bindToDom.call(this, name);
+    };
+});
+/**
+ * Checkbox
+ *
+ * @author voloko
+ * @name uki.view.Checkbox
+ * @class
+ * @extends uki.view.Button
+ */
+uki.view.declare('uki.view.Checkbox', uki.view.Button, function(Base) {
+    
+    this._backgroundPrefix = 'checkbox-';
+    
+    /**
+     * @function
+     * @name uki.view.Button#"checked-normal-background"
+     */
+    /**
+     * @function
+     * @name uki.view.Button#"checked-hover-background"
+     */
+    /**
+     * @function
+     * @name uki.view.Button#"checked-disabled-background"
+     */
+    uki.each(['checked-normal', 'checked-hover', 'checked-disabled'], function(i, name) {
+        var property = name + '-background';
+        this[property] = function(bg) {
+            if (bg) this['_' + property] = bg;
+            return this['_' + property] = this['_' + property] || 
+                uki.theme.background(this._backgroundPrefix + name, {height: this.rect().height, view: this});
+        };
+    }, this);
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        this._focusable = false;
+    };
+    
+    this._updateBg = function() {
+        var name = this._disabled ? 'disabled' : this._over ? 'hover' : 'normal';
+        if (this._checked) name = 'checked-' + name;
+        this._backgroundByName(name);
+    };
+    
+    /**
+     * @function
+     * @name uki.view.Button#value
+     */
+     /**
+      * @function
+      * @name uki.view.Button#checked
+      */
+    this.value = this.checked = uki.newProp('_checked', function(state) {
+        this._checked = !!state;
+        this._updateBg();
+    });
+    
+    this._mouseup = function(e) {
+        if (!this._down) return;
+        this._down = false;
+        if (!this._disabled) {
+            this.checked(!this.checked())
+            this.trigger('change', {checked: this._checked, source: this});
+        }
+    };
+    
+});
+
+
+
+(function() {
+    /**
+     * Radio button
+     *
+     * @author voloko
+     * @name uki.view.Radio
+     * @class
+     * @extends uki.view.Checkbox
+     */
+    var manager = uki.view.declare('uki.view.Radio', uki.view.Checkbox, function(base) {
+        
+        this._backgroundPrefix = 'radio-';
+        
+        /**
+        * @function
+        * @param {String} group
+        * @name uki.view.Popup#hide
+        */
+        this.group = uki.newProp('_group', function(g) {
+            manager.unregisterGroup(this);
+            this._group = g;
+            manager.registerGroup(this);
+            if (this.checked()) manager.clearGroup(this);
+        });
+
+        this.value = this.checked = uki.newProp('_checked', function(state) {
+            this._checked = !!state;
+            if (state) manager.clearGroup(this);
+            this._updateBg();
+        });
+
+        this._mouseup = function() {
+            if (!this._down) return;
+            this._down = false;
+            if (!this._checked && !this._disabled) {
+                this.checked(!this._checked);
+                this.trigger('change', { checked: this._checked, source: this });
+            }
+        }
+    });
+    
+    
+    manager.groups = {};
+
+    manager.registerGroup = function(radio) {
+        var group = radio.group();
+        if (!manager.groups[group]) {
+            manager.groups[group] = [radio];
+        } else {
+            manager.groups[group].push(radio);
+        }
+    };
+
+    manager.unregisterGroup = function(radio) {
+        var group = radio.group();
+        if (manager.groups[group]) manager.groups[group] = uki.grep(manager.groups[group], function(registered) {
+            return registered != radio;
+        });
+    };
+
+    manager.clearGroup = function(radio) {
+        uki.each(manager.groups[radio.group()] || [], function(i, registered) {
+            if (registered == radio) return;
+            if (registered.checked()) {
+                registered.checked(false);
+                this.trigger('change', { checked: false, source: registered });
+            }
+        });
+    };    
+    
+})();
+/**
+* Editable Text Field
+*
+* @author voloko
+* @name uki.view.TextField
+* @class
+* @extends uki.view.Base
+* @implements uki.view.Focusable
+*/
+uki.view.declare('uki.view.TextField', uki.view.Base, uki.view.Focusable, function(Base, Focusable) {
+    var emptyInputHeight = {};
+
+    function getEmptyInputHeight (css) {
+        if (!emptyInputHeight[css]) {
+            var node = uki.createElement('input', Base.defaultCss + "border:none;padding:0;border:0;margin:0;overflow:hidden;left:-999em;top:0;line-height:1;" + css);
+            uki.dom.probe(
+                node,
+                function(probe) {
+                    emptyInputHeight[css] = probe.offsetHeight;
+                }
+            );
+        }
+        return emptyInputHeight[css];
+    }
+
+    function nativePlaceholder (node) {
+        return typeof node.placeholder == 'string';
+    }
+    
+    this._backgroundPrefix = '';
+    this._tagName = 'input';
+    this._type = 'text';
+
+    this._setup = function() {
+        Base._setup.apply(this, arguments);
+        uki.extend(this, {
+            _value: '',
+            _multiline: false,
+            _placeholder: ''
+        });
+        this.defaultCss += "margin:0;border:none;outline:none;padding:0;left:2px;top:0;z-index:100;-moz-resize:none;resize:none;background: url(" + uki.theme.imageSrc('x') + ");" + uki.theme.style('input');
+    };
+    
+    this._updateBg = function() {
+        this._input.style.color = this._disabled ? '#999' : '#000';
+    };
+    
+    /**
+    * @function
+    * @name uki.view.TextField#name
+    */
+    uki.delegateProp(this, 'name', '_input');
+    
+    /**
+    * @function
+    * @name uki.view.TextField#value
+    */
+    this.value = function(value) {
+        if (value === undefined) return this._input.value;
+
+        this._input.value = value;
+        this._updatePlaceholderVis();
+        return this;
+    };
+    
+    /**
+    * Cross browser placeholder implementation
+    * @function
+    * @name uki.view.TextField#placeholder
+    */
+    this.placeholder = uki.newProp('_placeholder', function(v) {
+        this._placeholder = v;
+        if (!this._multiline && nativePlaceholder(this._input)) {
+            this._input.placeholder = v;
+        } else {
+            if (!this._placeholderDom) {
+                this._placeholderDom = uki.createElement('div', this.defaultCss + 'z-input:103;color:#999;cursor:text;-moz-user-select:none;', v);
+                if (!this._multiline) this._placeholderDom.style.whiteSpace = 'nowrap';
+                this._dom.appendChild(this._placeholderDom);
+                this._updatePlaceholderVis();
+                uki.each(['fontSize', 'fontFamily', 'fontWeight'], function(i, name) {
+                    this._placeholderDom.style[name] = this._input.style[name];
+                }, this);
+                
+                uki.dom.bind(this._placeholderDom, 'mousedown', uki.proxy(function(e) { 
+                    this.focus(); 
+                    e.preventDefault(); 
+                }, this));
+            } else {
+                this._placeholderDom.innerHTML = v;
+            }
+        }
+    });
+
+    this._style = function(name, value) {
+        if (uki.inArray(name, uki.browser.textStyles) != -1) {
+            if (value === undefined) return this._input.style[name];
+            this._input.style[name] = value;
+            if (this._placeholderDom) this._placeholderDom.style[name] = value;
+        }
+        return Base._style.call(this, name, value);
+    };
+
+    /**
+    * @function
+    * @name uki.view.TextField#backgroundPrefix
+    */
+    uki.addProps(this, ['backgroundPrefix']);
+    
+    /**
+    * @function
+    * @name uki.view.TextField#defaultBackground
+    */
+    this.defaultBackground = function() {
+        return uki.theme.background(this._backgroundPrefix + 'input');
+    };
+    
+    this._createDom = function() {
+        this._dom = uki.createElement('div', Base.defaultCss + ';cursor:text;overflow:visible;');
+        this._initClassName();
+        this._input = uki.createElement(this._tagName, this.defaultCss + (this._multiline ? '' : ';overflow:hidden;'));
+        
+        this._input.value = this._value;
+        if (this._type) this._input.type = this._type;
+        this._dom.appendChild(this._input);
+        
+        this._input.value = this.value();
+        
+        this._initFocusable(this._input);
+        this.bind('mousedown', function(e) {
+            if (e.target == this._input) return;
+            this.focus(); 
+        });
+    };
+    
+    this._layoutDom = function(rect) {
+        Base._layoutDom.apply(this, arguments);
+        uki.dom.layout(this._input.style, {
+            width: this._rect.width - 4
+        });
+        var margin;
+        if (this._multiline) {
+            this._input.style.height = this._rect.height - 4 + PX;
+            this._input.style.top = 2 + PX;
+            margin = '2px 0';
+        } else {
+            var o = (this._rect.height - getEmptyInputHeight( 'font-size:' + this.style('fontSize') + ';font-family:' + this.style('fontFamily') )) / 2;
+            margin = CEIL(o) + 'px 0 ' + FLOOR(o) + 'px 0';
+            this._input.style.padding = margin;
+        }
+        if (this._placeholderDom) this._placeholderDom.style.padding = margin;
+    };
+    
+    this._updatePlaceholderVis = function() {
+        if (this._placeholderDom) this._placeholderDom.style.display = this.value() ? 'none' : 'block';
+    };
+
+    this._focus = function(e) {
+        this._focusBackground = this._focusBackground || uki.theme.background(this._backgroundPrefix + 'input-focus');
+        this._focusBackground.attachTo(this);
+        if (this._placeholderDom) this._placeholderDom.style.display = 'none';
+        Focusable._focus.call(this, e);
+    };
+    
+    this._blur = function(e) {
+        this._focusBackground.detach();
+        this._updatePlaceholderVis();
+        Focusable._blur.call(this, e);
+    };
+
+    this._bindToDom = function(name) {
+        return Focusable._bindToDom.call(this, name) || Base._bindToDom.call(this, name);
+    };
+});
+
+/**
+* Multiline Editable Text Field (textarea)
+*
+* @author voloko
+* @name uki.view.MultilineTextField
+* @class
+* @extends uki.view.TextField
+*/
+uki.view.declare('uki.view.MultilineTextField', uki.view.TextField, function(Base) {
+    this._tagName = 'textarea';
+    this._type = '';
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        this._multiline = true;
+    };
+});
+
+/**
+* Password Field
+*
+* @author voloko
+* @name uki.view.PasswordTextField
+* @class
+* @extends uki.view.TextField
+*/
+uki.view.declare('uki.view.PasswordTextField', uki.view.TextField, function(Base) {
+    this._setup = function() {
+        Base._setup.call(this);
+        this._type = 'password';
+    };
+});
+
+uki.Collection.addAttrs(['placeholder']);
+
+(function() {
+    var scrollWidth, widthIncludesScrollBar;
+    
+    function initScrollWidth () {
+        if (!scrollWidth) {
+            uki.dom.probe(
+                uki.createElement(
+                    'div', 
+                    'position:absolute;left:-99em;width:100px;height:100px;overflow:scroll;',
+                    '<div style="position:absolute;left:0;width:100%;"></div>'
+                ),
+                function( probe ) {
+                    scrollWidth = probe.offsetWidth - probe.clientWidth;
+                    widthIncludesScrollBar = probe.firstChild.offsetWidth == 100;
+                }
+            );
+        }
+        return scrollWidth;
+    }
+        
+    /**
+    * Scroll pane. 
+    * Pane with scrollbars with content overflowing the borders.
+    * Works consistently across all supported browsers.
+    *
+    * @author voloko
+    * @name uki.view.ScrollPane
+    * @class
+    * @extends uki.view.Container
+    */
+    uki.view.declare('uki.view.ScrollPane', uki.view.Container, function(Base) {
+        uki.extend(this, {
+            _scrollableY: true,
+            _scrollableX: false,
+            _scrollY: false,
+            _scrollX: false,
+            _sbY: false,
+            _sbX: false
+        });
+        
+        this._setup = function() {
+            Base._setup.call(this);
+
+            this._clientRect = this.rect().clone();
+            this._rectForChild = this.rect().clone();
+        };
+    
+        /**
+        * @function
+        * @name uki.view.ScrollPane#scrollableY
+        */
+        /**
+        * @function
+        * @name uki.view.ScrollPane#scrollableX
+        */
+        /**
+        * @function
+        * @name uki.view.ScrollPane#scrollX
+        */
+        /**
+        * @function
+        * @name uki.view.ScrollPane#scrollY
+        */
+        uki.addProps(this, ['scrollableY', 'scrollableX', 'scrollX', 'scrollY']);
+        this.scrollV = this.scrollY;
+        this.scrollH = this.scrollX;
+        
+        this.scrollableV = this.scrollableY;
+        this.scrollableH = this.scrollableX;
+    
+        this.rectForChild = function() { return this._rectForChild; };
+        this.clientRect = function() { return this._clientRect; };
+    
+        /**
+        * @function
+        * @param {Number} dx
+        * @param {Number} dy
+        * @name uki.view.ScrollPane#scroll
+        */
+        this.scroll = function(dx, dy) {
+            if (dx) this.scrollLeft(this.scrollLeft() + dx);
+            if (dy) this.scrollTop(this.scrollTop() + dy);
+        };
+    
+        /**
+        * @function
+        * @name uki.view.ScrollPane#scrollTop
+        */
+        /**
+        * @function
+        * @name uki.view.ScrollPane#scrollLeft
+        */
+        uki.each(['scrollTop', 'scrollLeft'], function(i, name) {
+            this[name] = function(v) {
+                if (v == undefined) return this._dom[name];
+                this._dom[name] = v;
+                this.trigger('scroll', { source: this });
+                return this;
+            };
+        }, this);
+    
+        /**
+        * @function
+        * @return {uki.geometry.Rect}
+        * @name uki.view.ScrollPane#visibleRect
+        */
+        this.visibleRect = function() {
+            var tmpRect = this._clientRect.clone();
+            tmpRect.x = this.rect().x + this.scrollLeft();
+            tmpRect.y = this.rect().y + this.scrollTop();
+            return tmpRect;
+        };
+    
+        this.rect = function(newRect) {
+            if (newRect === undefined) return this._rect;
+        
+            newRect = Rect.create(newRect);
+            var oldRect = this._rect;
+            this._parentRect = newRect;
+            if (!this._resizeSelf(newRect)) return this;
+            this._updateClientRects();
+            this._needsLayout = true;
+            this.trigger('resize', {oldRect: oldRect, newRect: this._rect, source: this});
+            return this;
+        };
+        
+        this._createDom = function() {
+            Base._createDom.call(this);
+            if (ua.indexOf('Gecko/') > -1) this._dom.tabIndex = '-1';
+        };
+    
+        this._recalcClientRects = function() {
+            initScrollWidth();
+
+            var cw = this.contentsWidth(),
+                ch = this.contentsHeight(),
+                sx = this._scrollableX ? cw > this._rect.width : false,
+                sy = this._scrollableY ? ch > this._rect.height : false;
+            
+            this._sbX = sx || this._scrollX;
+            this._sbY = sy || this._scrollY;
+            this._clientRect = new Rect( this._rect.width +  (sy ? -1 : 0) * scrollWidth,
+                                         this._rect.height + (sx ? -1 : 0) * scrollWidth );
+            this._rectForChild = new Rect( this._rect.width +  ((sy && !widthIncludesScrollBar) ? -1 : 0) * scrollWidth,
+                                           this._rect.height + ((sx && !widthIncludesScrollBar) ? -1 : 0) * scrollWidth );
+        };
+    
+        this._updateClientRects = function() {
+            var oldClientRect = this._clientRect;
+            this._recalcClientRects();
+
+            if (oldClientRect.width != this._clientRect.width || oldClientRect.height != this._clientRect.height) {
+                this._resizeChildViews(oldClientRect);
+            }
+        };
+    
+        this._resizeChildViews = function(oldClientRect) {
+            for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
+                childViews[i].parentResized(oldClientRect, this._clientRect);
+            };
+        };
+    
+        this._layoutChildViews = function() {
+            for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
+                if (childViews[i]._needsLayout && childViews[i].visible()) {
+                    childViews[i].layout();
+                }
+            };
+        };
+        
+        this._layoutDom = function(rect) {
+            this._updateClientRects();
+        
+            if (this._layoutScrollX !== this._sbX) {
+                this._dom.style.overflowX = this._sbX ? 'scroll' : 'hidden';
+                this._layoutScrollX = this._sbX;
+            }
+
+            if (this._layoutScrollY !== this._sbY) {
+                this._dom.style.overflowY = this._sbY ? 'scroll' : 'hidden';
+                this._layoutScrollY = this._sbY;
+            }
+        
+            Base._layoutDom.call(this, rect);
+        };
+        
+        this.childResized = function() {
+            this._needsLayout = true;
+            uki.after(uki.proxy(this.layoutIfNeeded, this));
+        };
+
+        this._contentChanged = this.childResized;
+        
+    });
+
+    uki.view.ScrollPane.initScrollWidth = initScrollWidth;
+})();
+
+uki.Collection.addAttrs(['scrollTop', 'scrollLeft']);
+uki.fn.scroll = function(dx, dy) {
+    this.each(function() {
+        this.scroll(dx, dy);
+    });
+};
+
+uki.view.list = {};
+/**
+ * List View
+ * Progressevly renders list data. Support selection and drag&drop.
+ * Renders rows with plain html.
+ * 
+ * @author voloko
+ * @name uki.view.List
+ * @class
+ * @extends uki.view.Base
+ * @implements uki.view.Focusable
+ */
+uki.view.declare('uki.view.List', uki.view.Base, uki.view.Focusable, function(Base, Focusable) {
+    
+    this._throttle = 42; // do not try to render more often than every 42ms
+    this._visibleRectExt = 300; // extend visible rect by 300 px overflow
+    this._defaultBackground = 'theme(list)';
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        uki.extend(this, {
+            _rowHeight: 30,
+            _render: new uki.view.list.Render(),
+            _data: [],
+            _lastClickIndex: -1,
+            _selectedIndexes: []
+        });
+    };
+    
+    /**
+     * @function
+     * @name uki.view.List#defaultBackground
+     */
+    this.defaultBackground = function() {
+        return uki.theme.background('list', this._rowHeight);
+    };
+    
+    /**
+    * @type uki.view.list.Render
+    * @function
+    * @name uki.view.List#render
+    */
+    /**
+    * @function
+    * @name uki.view.List#packSize
+    */
+    /**
+    * @function
+    * @name uki.view.List#visibleRectExt
+    */
+    /**
+    * @function
+    * @name uki.view.List#throttle
+    */
+    /**
+    * @function
+    * @name uki.view.List#lastClickIndex
+    */
+    /**
+    * @function
+    * @name uki.view.List#multiselect
+    */
+    uki.addProps(this, ['render', 'packSize', 'visibleRectExt', 'throttle', 'lastClickIndex', 'multiselect']);
+    
+    /**
+    * @function
+    * @name uki.view.List#rowHeight
+    */
+    this.rowHeight = uki.newProp('_rowHeight', function(val) {
+        this._rowHeight = val;
+        this.minSize(new Size(this.minSize().width, this._rowHeight * this._data.length));
+        if (this._background) this._background.detach();
+        this._background = null;
+        if (this.background()) this.background().attachTo(this);
+        this._contentChanged();
+    });
+    
+    /**
+    * @example list.data(['row1', 'row2', ...])
+    * @function
+    * @name uki.view.List#data
+    */
+    this.data = function(d) {
+        if (d === undefined) return this._data;
+        this.clearSelection();
+        this._data = d;
+        this._packs[0].itemFrom = this._packs[0].itemTo = this._packs[1].itemFrom = this._packs[1].itemTo = 0;
+        
+        this.minSize(new Size(this.minSize().width, this._rowHeight * this._data.length));
+        this.trigger('selection', {source: this});
+        this._contentChanged();
+        return this;
+    };
+    
+    /**
+    * Forces list content update
+    * @function
+    * @name uki.view.List#relayout
+    */
+    this.relayout = function() {
+        this._packs[0].itemFrom = this._packs[0].itemTo = this._packs[1].itemFrom = this._packs[1].itemTo = 0;
+        this.layout();
+    };
+    
+    this.contentsSize = function() {
+        return new Size(this.rect().width, this._rowHeight * this._data.length);
+    };
+    
+    /**
+    * used in search. should be fast
+    * @function
+    * @param {Number} position
+    * @param {String} data
+    * @name uki.view.List#addRow
+    */
+    this.addRow = function(position, data) {
+        this._data.splice(position, 0, data);
+        var item = this._itemAt(position);
+        var container = doc.createElement('div');
+        
+        container.innerHTML = this._rowTemplate.render({ 
+            height: this._rowHeight, 
+            text: this._render.render(this._data[position], this._rowRect(position), position)
+        });
+        if (item) {
+            item.parentNode.insertBefore(container.firstChild, item);
+        } else {
+            this._dom.childNodes[0].appendChild(container.firstChild);
+        }
+
+        if (position <= this._packs[0].itemTo) {
+            this._packs[0].itemTo++;
+            this._packs[1].itemFrom++;
+            this._packs[1].itemTo++;
+            this._packs[1].dom.style.top = this._packs[1].itemFrom*this._rowHeight + 'px';
+        } else {
+            this._packs[1].itemTo++;
+        }
+        
+        // offset selection
+        var selectionPosition = uki.binarySearch(position, this.selectedIndexes());
+        for (var i = selectionPosition; i < this._selectedIndexes.length; i++) {
+            this._selectedIndexes[i]++;
+        };
+        
+        // needed for scrollbar
+        this.minSize(new Size(this.minSize().width, this._rowHeight * this._data.length));
+        this._contentChanged();
+
+        return this;
+    };
+    
+    /**
+    * @function
+    * @param {Number} position
+    * @name uki.view.List#removeRow
+    */
+    this.removeRow = function(position) {
+        this._data.splice(position, 1);
+        this.data(this._data);
+        return this;
+    };
+    
+    /**
+    * Forces one particular row to be redrawn
+    * @function
+    * @param {Number} position
+    * @name uki.view.List#removeRow
+    */
+    this.redrawRow = function(position) {
+        var item = this._itemAt(position);
+        if (item) item.innerHTML = this._render.render(this._data[position], this._rowRect(position), position);
+        return this;
+    };
+    
+    /**
+    * Read/write current selected index for selectable lists
+    * @function
+    * @param {Number} position
+    * @name uki.view.List#selectedIndex
+    */
+    this.selectedIndex = function(position) {
+        if (position === undefined) return this._selectedIndexes.length ? this._selectedIndexes[0] : -1;
+        this.selectedIndexes([position]);
+        this._scrollToPosition(position);
+        return this;
+    };
+    
+    /**
+    * Read/write all selected indexes for multiselectable lists
+    * @function
+    * @param {Array.<Number>} position
+    * @name uki.view.List#selectedIndex
+    */
+    this.selectedIndexes = function(indexes) {
+        if (indexes === undefined) return this._selectedIndexes;
+        this.clearSelection(true);
+        this._selectedIndexes = indexes;
+        for (var i=0; i < this._selectedIndexes.length; i++) {
+            this._setSelected(this._selectedIndexes[i], true);
+        };
+        this.trigger('selection', {source: this});
+        return this;
+    };
+    
+    /**
+    * Read contents of selected row
+    * @function
+    * @name uki.view.List#selectedRow
+    */
+    this.selectedRow = function() {
+        return this._data[this.selectedIndex()];
+    };    
+    
+    /**
+    * Read contents of all selected rows
+    * @function
+    * @name uki.view.List#selectedRows
+    */
+    this.selectedRows = function() {
+        return uki.map(this.selectedIndexes(), function(index) {
+            return this._data[index];
+        }, this)
+    };
+    
+    /**
+    * @function
+    * @name uki.view.List#clearSelection
+    */
+    this.clearSelection = function(skipClickIndex) {
+        for (var i=0; i < this._selectedIndexes.length; i++) {
+            this._setSelected(this._selectedIndexes[i], false);
+        };
+        this._selectedIndexes = [];
+        if (!skipClickIndex) this._lastClickIndex = -1;
+    };
+    
+    /**
+    * @function
+    * @param {Number} index
+    * @name uki.view.List#isSelected
+    */
+    this.isSelected = function(index) {
+        var found = uki.binarySearch(index, this._selectedIndexes);
+        return this._selectedIndexes[found] == index;
+    };
+    
+    this.layout = function() {
+        this._layoutDom(this._rect);
+        this._needsLayout = false;
+        // send visibleRect with layout
+        this.trigger('layout', { rect: this._rect, source: this, visibleRect: this._visibleRect });
+        this._firstLayout = false;
+    };
+    
+    function range (from, to) {
+        var result = new Array(to - from);
+        for (var idx = 0; from <= to; from++, idx++) {
+            result[idx] = from;
+        };
+        return result;
+    }
+    
+    function removeRange (array, from, to) {
+        var p = uki.binarySearch(from, array),
+            initialP = p;
+        while (array[p] <= to) p++;
+        if (p > initialP) array.splice(initialP, p - initialP);
+    }
+    
+    this._rowRect = function(p) {
+        return new Rect(0, p*this._rowHeight, this.rect().width, this._rowHeight);
+    };
+    
+    this._toggleSelection = function(p) {
+        var indexes = [].concat(this._selectedIndexes);
+        var addTo = uki.binarySearch(p, indexes);
+        if (indexes[addTo] == p) {
+            indexes.splice(addTo, 1);
+        } else {
+            indexes.splice(addTo, 0, p);
+        }
+        this.selectedIndexes(indexes);
+    };
+    
+    var updatingScroll = false;
+    this._scrollableParentScroll = function() {
+        if (updatingScroll) return;
+        if (this._throttle) {
+            if (this._throttleStarted) return;
+            this._throttleStarted = true;
+            setTimeout(uki.proxy(function() {
+                this._throttleStarted = false;
+                this.layout();
+            }, this), this._throttle);
+        } else {
+            this.layout();
+        }
+    };
+    
+    this._contentChanged = function() {
+        this._needsLayout = true;
+        uki.after(uki.proxy(this._relayoutParent, this));
+    };
+
+    this._relayoutParent = function() {
+        this.parent().childResized(this);
+        if (!this._scrollableParent) return;
+        var c = this;
+        while ( c && c != this._scrollableParent) {
+            c._needsLayout = true;
+            c = c.parent();
+        }
+        c.layout();
+    };
+    
+    
+    this.keyPressEvent = function() {
+        var useKeyPress = root.opera || (/mozilla/i.test( ua ) && !(/(compatible|webkit)/i).test( ua ));
+        return useKeyPress ? 'keypress' : 'keydown';
+    };
+    
+    this._bindSelectionEvents = function() {
+        this.bind('mousedown', this._mousedown);
+        this.bind('mouseup', this._mouseup);
+        this.bind(this.keyPressEvent(), this._keypress);
+    };
+    
+    this._mouseup = function(e) {
+        if (!this._multiselect) return;
+        
+        var o = uki.dom.offset(this._dom),
+            y = e.pageY - o.y,
+            p = y / this._rowHeight << 0;
+            
+        if (this._selectionInProcess && this._lastClickIndex == p && this.isSelected(p)) this.selectedIndexes([p]);
+        this._selectionInProcess = false;
+    };
+    
+    this._mousedown = function(e) {
+        var o = uki.dom.offset(this._dom),
+            y = e.pageY - o.y,
+            p = y / this._rowHeight << 0,
+            indexes = this._selectedIndexes;
+
+        if (this._multiselect) {
+            this._selectionInProcess = false;
+            if (e.shiftKey && indexes.length > 0) {
+                if (this.isSelected(p)) {
+                    indexes = [].concat(indexes);
+                    removeRange(indexes, Math.min(p+1, this._lastClickIndex), Math.max(p-1, this._lastClickIndex));
+                    this.selectedIndexes(indexes);
+                } else {
+                    this.selectedIndexes(range(
+                        Math.min(p, indexes[0]),
+                        Math.max(p, indexes[indexes.length - 1])
+                    ));
+                }
+            } else if (e.metaKey) {
+                this._toggleSelection(p);
+            } else {
+                if (!this.isSelected(p)) {
+                    this.selectedIndexes([p]);
+                } else {
+                    this._selectionInProcess = true;
+                }
+            }
+        } else {
+            this.selectedIndexes([p]);
+        }
+        this._lastClickIndex = p;
+    };    
+    
+    this._keypress = function(e) {
+        var indexes = this._selectedIndexes,
+            nextIndex = -1;
+        if (e.which == 38 || e.keyCode == 38) { // UP
+            nextIndex = Math.max(0, this._lastClickIndex - 1);
+            e.preventDefault();
+        } else if (e.which == 40 || e.keyCode == 40) { // DOWN
+            nextIndex = Math.min(this._data.length-1, this._lastClickIndex + 1);
+            e.preventDefault();
+        } else if (this._multiselect && (e.which == 97 || e.which == 65) && e.metaKey) {
+            e.preventDefault();
+            this.selectedIndexes(range(0, this._data.length -1));
+        }
+        if (nextIndex > -1 && nextIndex != this._lastClickIndex) {
+            if (e.shiftKey && this._multiselect) {
+                if (this.isSelected(nextIndex)) {
+                    this._toggleSelection(this._lastClickIndex);
+                } else {
+                    this._toggleSelection(nextIndex);
+                }
+                this._scrollToPosition(nextIndex);
+            } else {
+                this.selectedIndex(nextIndex);
+            }
+            this._lastClickIndex = nextIndex;
+        }
+    };
+    
+    this._createDom = function() {
+        this._dom = uki.createElement('div', this.defaultCss + 'overflow:hidden');
+        this._initClassName();
+        
+        var packDom = uki.createElement('div', 'position:absolute;left:0;top:0px;width:100%;overflow:hidden');
+        this._packs = [
+            {
+                dom: packDom,
+                itemTo: 0,
+                itemFrom: 0
+            },
+            {
+                dom: packDom.cloneNode(false),
+                itemTo: 0,
+                itemFrom: 0
+            }
+        ];
+        this._dom.appendChild(this._packs[0].dom);
+        this._dom.appendChild(this._packs[1].dom);
+        
+        this._initFocusable();
+        this._bindSelectionEvents();
+    };
+    
+    this._setSelected = function(position, state) {
+        var item = this._itemAt(position);
+        if (item) this._render.setSelected(item, this._data[position], state, this.hasFocus());
+    };
+    
+    this._scrollToPosition = function(position) {
+        if (!this._visibleRect) return;
+        var maxY, minY;
+        maxY = (position+1)*this._rowHeight;
+        minY = position*this._rowHeight;
+        updatingScroll = true;
+        if (maxY >= this._visibleRect.maxY()) {
+            this._scrollableParent.scroll(0, maxY - this._visibleRect.maxY());
+        } else if (minY < this._visibleRect.y) {
+            this._scrollableParent.scroll(0, minY - this._visibleRect.y);
+        }
+        updatingScroll = false;
+        this.layout();
+    };
+    
+    this._itemAt = function(position) {
+        if (position < this._packs[1].itemTo && position >= this._packs[1].itemFrom) {
+            return this._packs[1].dom.childNodes[position - this._packs[1].itemFrom];
+        } else if (position < this._packs[0].itemTo && position >= this._packs[0].itemFrom) {
+            return this._packs[0].dom.childNodes[position - this._packs[0].itemFrom];
+        }
+        return null;
+    };
+    
+    this._rowTemplate = new uki.theme.Template('<div style="width:100%;height:${height}px;overflow:hidden;">${text}</div>')
+    
+    this._renderPack = function(pack, itemFrom, itemTo) {
+        var html = [], position;
+        for (i=itemFrom; i < itemTo; i++) {
+            html[html.length] = this._rowTemplate.render({ 
+                height: this._rowHeight, 
+                text: this._render.render(this._data[i], this._rowRect(i), i)
+            });
+        };
+        pack.dom.innerHTML = html.join('');
+        pack.itemFrom = itemFrom;
+        pack.itemTo   = itemTo;
+        pack.dom.style.top = itemFrom*this._rowHeight + 'px';
+        this._restorePackSelection(pack, itemFrom, itemTo);
+    };
+    
+    //   xxxxx    |    xxxxx  |  xxxxxxxx  |     xxx
+    //     yyyyy  |  yyyyy    |    yyyy    |   yyyyyyy
+    this._restorePackSelection = function(pack) {
+        var indexes = this._selectedIndexes;
+        
+        if (
+            (indexes[0] <= pack.itemFrom && indexes[indexes.length - 1] >= pack.itemFrom) || // left index
+            (indexes[0] <= pack.itemTo   && indexes[indexes.length - 1] >= pack.itemTo) || // right index
+            (indexes[0] >= pack.itemFrom && indexes[indexes.length - 1] <= pack.itemTo) // within
+        ) {
+            var currentSelection = uki.binarySearch(pack.itemFrom, indexes);
+            currentSelection = Math.max(currentSelection, 0);
+            while(indexes[currentSelection] !== null && indexes[currentSelection] < pack.itemTo) {
+                var position = indexes[currentSelection] - pack.itemFrom;
+                this._render.setSelected(pack.dom.childNodes[position], this._data[position], true, this.hasFocus());
+                currentSelection++;
+            }
+        }
+    };
+    
+    this._swapPacks = function() {
+        var tmp = this._packs[0];
+        this._packs[0] = this._packs[1];
+        this._packs[1] = tmp;
+    };
+    
+    this._layoutDom = function(rect) {
+        if (!this._scrollableParent) {
+            this._scrollableParent = uki.view.scrollableParent(this);
+            this._scrollableParent.bind('scroll', uki.proxy(this._scrollableParentScroll, this));
+        }
+        
+        var totalHeight = this._rowHeight * this._data.length,
+            scrollableParent = this._scrollableParent;
+
+        this._visibleRect = uki.view.visibleRect(this, scrollableParent);
+        if (this._focusTarget) this._focusTarget.style.top = this._visibleRect.y + 'px';
+        var prefferedPackSize = CEIL((this._visibleRect.height + this._visibleRectExt*2) / this._rowHeight),
+        
+            minVisibleY  = MAX(0, this._visibleRect.y - this._visibleRectExt),
+            maxVisibleY  = MIN(totalHeight, this._visibleRect.maxY() + this._visibleRectExt),
+            minRenderedY = this._packs[0].itemFrom * this._rowHeight,
+            maxRenderedY = this._packs[1].itemTo * this._rowHeight,
+            
+            itemFrom, itemTo, startAt, updated = true;
+
+        Base._layoutDom.call(this, rect);
+        if (
+            maxVisibleY <= minRenderedY || minVisibleY >= maxRenderedY || // both packs below/above visible area
+            (maxVisibleY > maxRenderedY && this._packs[1].itemFrom * this._rowHeight > this._visibleRect.y && this._packs[1].itemTo > this._packs[1].itemFrom) || // need to render below, and pack 2 is not enough to cover
+            (minVisibleY < minRenderedY && this._packs[0].itemTo * this._rowHeight < this._visibleRect.maxY()) // need to render above, and pack 1 is not enough to cover the area
+            // || prefferedPackSize is not enough to cover the area above/below, can this actually happen?
+        ) { 
+            // this happens a) on first render b) on scroll jumps c) on container resize
+            // render both packs, move them to be at the center of visible area
+            // startAt = minVisibleY + (maxVisibleY - minVisibleY - prefferedPackSize*this._rowHeight*2) / 2;
+            startAt = minVisibleY - this._visibleRectExt / 2;
+            itemFrom = MAX(0, Math.round(startAt / this._rowHeight));
+            itemTo = MIN(this._data.length, itemFrom + prefferedPackSize);
+            
+            this._renderPack(this._packs[0], itemFrom, itemTo);
+            this._renderPack(this._packs[1], itemTo, itemTo);
+            // this._renderPack(this._packs[1], itemTo, MIN(this._data.length, itemTo + prefferedPackSize));
+        } else if (maxVisibleY > maxRenderedY && this._packs[1].itemTo > this._packs[1].itemFrom) { // we need to render below current area
+            // this happens on normal scroll down
+            // re-render bottom, swap
+            itemFrom = this._packs[1].itemTo;
+            itemTo   = MIN(this._data.length, this._packs[1].itemTo + prefferedPackSize);
+            
+            this._renderPack(this._packs[0], itemFrom, itemTo);
+            this._swapPacks();
+        } else if (maxVisibleY > maxRenderedY) { // we need to render below current area
+            itemFrom = this._packs[0].itemTo;
+            itemTo   = MIN(this._data.length, this._packs[1].itemTo + prefferedPackSize);
+            
+            this._renderPack(this._packs[1], itemFrom, itemTo);
+        } else if (minVisibleY < minRenderedY) { // we need to render above current area
+            // this happens on normal scroll up
+            // re-render top, swap
+            itemFrom = MAX(this._packs[0].itemFrom - prefferedPackSize, 0);
+            itemTo   = this._packs[0].itemFrom;
+            
+            this._renderPack(this._packs[1], itemFrom, itemTo);
+            this._swapPacks();
+        } else {
+            updated = false;
+        }
+        if (updated && /MSIE 6|7/.test(ua)) this.dom().className += '';
+    };
+    
+    this._bindToDom = function(name) {
+        return Focusable._bindToDom.call(this, name) || Base._bindToDom.call(this, name);
+    };
+    
+    this._focus = function(e) {
+        Focusable._focus.call(this, e);
+        if (this._selectedIndexes.length == 0 && this._data.length > 0) {
+            this.selectedIndexes([0]);
+        } else {
+            this.selectedIndexes(this.selectedIndexes());
+        }
+    };
+    
+    this._blur = function(e) {
+        Focusable._blur.call(this, e);
+        this.selectedIndexes(this.selectedIndexes());
+    };
+    
+});
+
+/** @function
+@name uki.Collection#data */
+/** @function
+@name uki.Collection#selectedIndex */
+/** @function
+@name uki.Collection#selectedIndexes */
+/** @function
+@name uki.Collection#selectedRow */
+/** @function
+@name uki.Collection#selectedRows */
+/** @function
+@name uki.Collection#lastClickIndex */
+uki.Collection.addAttrs(['data', 'selectedIndex', 'selectedIndexes', 'selectedRow', 'selectedRows', 'lastClickIndex']);
+
+/**
+ * Scrollable List View
+ * Puts a list into a scroll pane
+ * 
+ * @author voloko
+ * @name uki.view.ScrollableList
+ * @class
+ * @extends uki.view.ScrollPane
+ */
+uki.view.declare('uki.view.ScrollableList', uki.view.ScrollPane, function(Base) {
+
+    this._createDom = function() {
+        Base._createDom.call(this);
+        this._list = uki({ view: 'List', rect: this.rect().clone().normalize(), anchors: 'left top right bottom' })[0];
+        this.appendChild(this._list);
+    };
+    
+    uki.each('data rowHeight render packSize visibleRectExt throttle focusable selectedIndex selectedIndexes selectedRow selectedRows multiselect draggable textSelectable'.split(' '), 
+        function(i, name) {
+            uki.delegateProp(this, name, '_list');
+        }, this);
+    
+});
+
+
+/**
+ * Flyweight view rendering
+ * Used in lists, tables, grids
+ * @class
+ */
+uki.view.list.Render = uki.newClass({
+    init: function() {},
+    
+    /**
+     * Renders data to an html string
+     * @param Object data Data to render
+     * @return String html
+     */
+    render: function(data, rect, i) {
+        return '<div style="line-height: ' + rect.height + 'px; font-size: 12px; padding: 0 4px;">' + data + '</div>';
+    },
+    
+    setSelected: function(container, data, state, focus) {
+        container.style.backgroundColor = state && focus ? '#3875D7' : state ? '#CCC' : '';
+        container.style.color = state && focus ? '#FFF' : '#000';
+    }
+});
+
+
+
+
+uki.view.table = {};
+
+/**
+* Table
+*
+* Uses uki.view.List to render data. Wraps it into scroll pane and ads a header
+* @author voloko
+* @name uki.view.Table
+* @class
+* @extends uki.view.Container
+*
+* @lends uki.view.List#rowHeight as rowHeight
+* @lends uki.view.List#data as data
+* @lends uki.view.List#packSize as packSize
+* @lends uki.view.List#visibleRectExt as visibleRectExt
+* @lends uki.view.List#render as render
+* @lends uki.view.List#selectedIndex as selectedIndex
+* @lends uki.view.List#selectedIndexes as selectedIndexes
+* @lends uki.view.List#selectedRow as selectedRow
+* @lends uki.view.List#selectedRows as selectedRows
+* @lends uki.view.List#lastClickIndex as lastClickIndex
+* @lends uki.view.List#textSelectable as textSelectable
+* @lends uki.view.List#multiselect as multiselect
+* @lends uki.view.List#redrawRow as redrawRow
+* @lends uki.view.List#addRow as addRow
+* @lends uki.view.List#removeRow as removeRow
+* @lends uki.view.List#focusable as lastClickIndex
+* @lends uki.view.List#focus as focus
+* @lends uki.view.List#blur as blur
+* @lends uki.view.List#hasFocus as hasFocus
+*
+*/
+uki.view.declare('uki.view.Table', uki.view.Container, function(Base) {
+    var propertiesToDelegate = 'rowHeight data packSize visibleRectExt render selectedIndex selectedIndexes selectedRows selectedRow focus blur hasFocus lastClickIndex focusable textSelectable multiselect'.split(' ');
+    
+    this._rowHeight = 17;
+    this._headerHeight = 17;
+    this._listImpl = 'uki.view.List';
+    
+    uki.each(propertiesToDelegate, function(i, name) { uki.delegateProp(this, name, '_list'); }, this);
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        this._columns = [];
+        this.defaultCss += 'overflow:hidden;';
+    };
+    
+    this._style = function(name, value) {
+        this._header.style(name, value);
+        return Base._style.call(this, name, value);
+    };
+    
+    /**
+    * @function
+    * @return {uki.view.List}
+    * @name uki.view.Table#list
+    */
+    this.list = function() {
+        return this._list;
+    };
+    
+    /**
+    * @function
+    * @return {uki.view.table.Header}
+    * @name uki.view.Table#header
+    */
+    this.header = function() {
+        return this._header;
+    };
+    
+    /**
+    * @function
+    * @param {Array.<uki.view.table.Column>} c
+    * @name uki.view.Table#columns
+    */
+    this.columns = uki.newProp('_columns', function(c) {
+        for (var i = 0; i < this._columns.length; i++) {
+            this._columns[i].unbind();
+        }
+        this._columns = uki.build(c);
+        this._totalWidth = 0;
+        for (i = 0; i < this._columns.length; i++) {
+            this._columns[i].position(i);
+            this._columns[i].bind('beforeResize', uki.proxy(function() {
+                this._updateTotalWidth();
+                this._scrollPane.layout();
+            }, this));
+        };
+        this._updateTotalWidth();
+        this._header.columns(this._columns);
+    });
+    
+    /**
+    * @function
+    * @param {Number} row
+    * @param {Number} col
+    * @name uki.view.Table#redrawCell
+    */
+    this.redrawCell = function(row, col) {
+        var item = this._list._itemAt(row);
+        if (item) {
+            var cell, container = doc.createElement('div');
+            container.innerHTML = this.columns()[col].render(
+                this.data()[row],
+                new Rect(0, row*this.rowHeight(), this.list().width(), this.rowHeight()),
+                row
+            );
+            cell = container.firstChild;
+            item.replaceChild(cell, item.childNodes[col]);
+        }
+        return this;
+    };
+    
+    uki.each(['redrawRow', 'addRow', 'removeRow'], function(i, name) {
+        this[name] = function() {
+            this.list()[name].apply(this.list(), arguments);
+            return this;
+        };
+    }, this);
+    
+    /**
+    * @function
+    * @param {Number} col
+    * @name uki.view.Table#redrawColumn
+    */
+    this.redrawColumn = function(col) {
+        var from = this._list._packs[0].itemFrom,
+            to   = this._list._packs[1].itemTo;
+        for (var i=from; i < to; i++) {
+            this.redrawCell(i, col);
+        };
+        return this;
+    };
+    
+    this._updateTotalWidth = function() {
+        this._totalWidth = 0;
+        for (var i=0; i < this._columns.length; i++) {
+            this._columns[i].position(i);
+            this._totalWidth += this._columns[i].width();
+        };
+        this._list.minSize(new Size(this._totalWidth, this._list.minSize().height));
+        // this._list.rect(new Rect(this._totalWidth, this._list.height()));
+        this._header.minSize(new Size(this._totalWidth, 0));
+    };
+    
+    this._createDom = function() {
+        Base._createDom.call(this);
+        this._initClassName();
+        var scrollPaneRect = new Rect(0, this._headerHeight, this.rect().width, this.rect().height - this._headerHeight),
+            listRect = scrollPaneRect.clone().normalize(),
+            headerRect = new Rect(0, 0, this.rect().width, this._headerHeight),
+            listML = { view: this._listImpl, rect: listRect, anchors: 'left top bottom right', render: new uki.view.table.Render(this), className: 'table-list' },
+            paneML = { view: 'ScrollPane', rect: scrollPaneRect, anchors: 'left top right bottom', scrollableH: true, childViews: [listML], className: 'table-scroll-pane'},
+            headerML = { view: 'table.Header', rect: headerRect, anchors: 'top left right', className: 'table-header' };
+            
+        uki.each(propertiesToDelegate, function(i, name) { 
+            if (this['_' + name] !== undefined) listML[name] = this['_' + name];
+        }, this);
+        this._scrollPane = uki.build(paneML)[0];
+        this._list = this._scrollPane.childViews()[0];
+        this._header = uki.build(headerML)[0];
+        this._scrollPane.resizeToContents();
+        this.appendChild(this._header);
+        this.appendChild(this._scrollPane);
+        
+        this._scrollPane.bind('scroll', uki.proxy(function() {
+            // this is kinda wrong but faster than calling rect() + layout()
+            this._header.dom().style.left = -this._scrollPane.scrollLeft() + 'px'; 
+        }, this));
+        
+    };
+});
+
+uki.Collection.addAttrs(['columns']);
+
+
+/**
+ * @class
+ * @extends uki.view.list.Render
+ */
+uki.view.table.Render = uki.newClass(uki.view.list.Render, new function() {
+    this.init = function(table) {
+        this._table = table;
+    };
+    
+    this.render = function(row, rect, i) {
+        var table = this._table,
+            columns = table.columns();
+        return uki.map(columns, function(val, j) {
+            return columns[j].render(row, rect, i);
+        }).join('');
+    };
+});
+/**
+ * @class
+ * @extends uki.view.Observable
+ */
+uki.view.table.Column = uki.newClass(uki.view.Observable, new function() {
+    this._width = 100;
+    this._offset = 0;
+    this._position = 0;
+    this._minWidth = 0;
+    this._maxWidth = 0;
+    this._css = 'float:left;white-space:nowrap;text-overflow:ellipsis;';
+    this._inset = new Inset(3, 5);
+    this._templatePrefix = 'table-';
+
+    this.init = function() {};
+    
+    uki.addProps(this, ['position', 'css', 'formatter', 'label', 'resizable', 'maxWidth', 'minWidth', 'maxWidth', 'key', 'sort']);
+    
+    this.template = function() {
+        // cache
+        return this._template || (this._template = uki.theme.template(this._templatePrefix + 'cell'));
+    };
+    
+    this.headerTemplate = function() {
+        var suffix = '';
+        if (this.sort() == 'ASC') suffix = '-asc';
+        if (this.sort() == 'DESC') suffix = '-desc';
+        return uki.theme.template(this._templatePrefix + 'header-cell' + suffix);
+    };
+    
+    this.sortData = function(data) {
+        var _this = this;
+        return data.sort(function(a, b) {
+            return _this._key ? 
+                _this.compare(uki.attr(a, _this._key), uki.attr(b, _this._key)) : 
+                _this.compare(a[_this._position], b[_this._position]);
+        });
+        
+    };
+    
+    this.compare = function(a, b) {
+        return (a >= b ? 1 : a == b ? 0 : -1) * (this._sort == 'DESC' ? -1 : 1);
+    };
+    
+    /**
+     * @fires event:beforeResize
+     * @fires event:resize
+     */
+    this.width = uki.newProp('_width', function(w) {
+        var e = {
+            oldWidth: this._width,
+            source: this
+        };
+        this._width = this._normailizeWidth(w);
+        e.newWidth = this._width;
+        this.trigger('beforeResize', e);
+        if (this._stylesheet && e.newWidth != e.oldWidth) {
+            var rules = this._stylesheet.styleSheet ? this._stylesheet.styleSheet.rules : this._stylesheet.sheet.cssRules;
+            rules[0].style.width = this._clientWidth() + PX;
+        }
+        this.trigger('resize', e);
+    });
+    
+    this._bindToDom = uki.F;
+    
+    this._normailizeWidth = function(w) {
+        if (this._maxWidth) w = MIN(this._maxWidth, w);
+        if (this._minWidth) w = MAX(this._minWidth, w);
+        return w;
+    };
+    
+    this.inset = uki.newProp('_inset', function(i) {
+        this._inset = Inset.create(i);
+    });
+    
+    this.render = function(row, rect, i) {
+        this._prerenderedTemplate || this._prerenderTemplate(rect);
+        var value = this._key ? uki.attr(row, this._key) : row[this._position];
+        this._prerenderedTemplate[1] = this._formatter ? this._formatter(value, row, i) : value;
+        return this._prerenderedTemplate.join('');
+    };
+    
+    this.appendResizer = function(dom, height) {
+        var resizer = uki.theme.dom('resizer', height);
+        dom.appendChild(resizer);
+        return resizer;
+    };
+    
+    this.renderHeader = function(height) {
+        this._className || this._initStylesheet();
+        var x = this.headerTemplate().render({
+            data: '<div style="overflow:hidden;text-overflow:ellipsis;*width:100%;height:100%;padding-top:' + this._inset.top + 'px">' + this.label() + '</div>',
+            style: '*overflow-y:hidden;' + this._cellStyle(true, height),
+            className: this._className
+        });
+        return x;
+    };
+    
+    this._prerenderTemplate = function(rect) {
+        this._className || this._initStylesheet();
+        this._prerenderedTemplate = this.template().render({
+            data: '\u0001\u0001',
+            style: 'overflow:hidden;' + this._cellStyle(false, rect.height),
+            className: this._className
+        }).split('\u0001');
+    };
+    
+    this._cellPadding = function(skipVertical) {
+        var inset = this._inset;
+        return ['padding:', (skipVertical ? '0' : inset.top), 'px ', inset.right, 'px ', (skipVertical ? '0' : inset.bottom), 'px ', inset.left, 'px;'].join('');
+    };
+    
+    this._cellHeight = function(skipVertical, height) {
+        return 'height:' + (height - (uki.dom.offset.boxModel && !skipVertical ? this._inset.height() : 0)) + 'px;';
+    };
+    
+    this._cellStyle = function(skipVertical, height) {
+        return this._css + this._cellPadding(skipVertical) + ';' + this._cellHeight(skipVertical, height);
+    };
+    
+    this._clientWidth = function() {
+        return this._width - (uki.dom.offset.boxModel ? this._inset.width() + 1 : 0);
+    };
+    
+    this._initStylesheet = function() {
+        if (!this._className) {
+            uki.dom.offset.initializeBoxModel();
+            this._className = 'uki-table-column-' + (uki.guid++);
+            var css = '.' + this._className + ' {width:' + this._clientWidth() + 'px;}';
+            this._stylesheet = uki.dom.createStylesheet(css);
+        }
+    };
+});
+
+uki.view.table.NumberColumn = uki.newClass(uki.view.table.Column, new function() {
+    var Base = uki.view.table.Column.prototype;
+
+    this._css = Base._css + 'text-align:right;';
+    
+    this.compare = function(a, b) {
+        a*=1;
+        b*=1;
+        return (a >= b ? 1 : a == b ? 0 : -1) * (this._sort == 'DESC' ? -1 : 1);
+    };
+});
+
+uki.view.table.CustomColumn = uki.view.table.Column;
+
+
+/**
+ * @class
+ * @extends uki.view.Label
+ */
+uki.view.declare('uki.view.table.Header', uki.view.Label, function(Base) {
+    this._defaultBackground = 'theme(table-header)';
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        this._multiline = true;
+        this._resizers = [];
+    };
+    
+    this.columns = uki.newProp('_columns', function(v) {
+        this._columns = v;
+        this.html(this._createColumns());
+        this._createResizers();
+    });
+    
+    this._createDom = function() {
+        Base._createDom.call(this);
+        this.bind('click', this._click);
+    };
+    
+    this._click = function(e) {
+        if (this._dragging) return;
+        
+        var target = e.target;
+        if (target == this.dom() || target == this._label) return;
+        while (target.parentNode != this._label) target = target.parentNode;
+        var i = uki.inArray(target, this._label.childNodes);
+        if (i > -1) {
+            this.trigger('columnClick', { source: this, columnIndex: i, column: this._columns[i] });
+        }
+    };
+    
+    this.redrawColumn = function(col) {
+        if (this._resizers[col]) uki.dom.unbind(this._resizers[col]);
+        var container = doc.createElement('div');
+        container.innerHTML = this._columns[col].renderHeader(this.rect().height);
+        this._label.replaceChild(container.firstChild, this._label.childNodes[col]);
+        if (this._columns[col].resizable()) this._createResizers(col);
+    };
+    
+    this._createColumns = function() {
+        var html = [];
+        for(var i = 0, offset = 0, columns = this._columns, l = columns.length; i < l; i++) {
+            html[html.length] = columns[i].renderHeader(this.rect().height);
+        }
+        return html.join('');
+    };
+    
+    this._createResizer = function(i) {
+        var column = this._columns[i];
+        if (column.resizable()) {
+            var resizer = column.appendResizer(this._label.childNodes[i], this.rect().height);
+            this._bindResizerDrag(resizer, i);
+            this._resizers[i] = resizer;
+        }
+    };
+    
+    this._createResizers = function() {
+        uki.each(this._columns, this._createResizer, this);
+    };
+    
+    this._bindResizerDrag = function(resizer, columnIndex) {
+        var _this = this;
+        
+        uki.dom.bind(resizer, 'draggesture', function(e) {
+            _this._dragging = true;
+            var headerOffset = uki.dom.offset(_this.dom()),
+                offsetWithinHeader = e.pageX - headerOffset.x,
+                columnOffset = 0, i, column = _this._columns[columnIndex];
+            for (i=0; i < columnIndex; i++) {
+                columnOffset += _this._columns[i].width();
+            };
+            column.width(offsetWithinHeader - columnOffset);
+        });
+        
+        uki.dom.bind(resizer, 'draggestureend', function() {
+            setTimeout(function() {
+                _this._dragging = false;
+            }, 1);
+        });
+    };
+});
+
+/**
+* Horizontal Slider. 
+*
+* @author voloko
+* @name uki.view.Slider
+* @class
+* @extends uki.view.Base
+* @implements uki.view.Focusable
+*/
+uki.view.declare('uki.view.Slider', uki.view.Container, uki.view.Focusable, function(Base, Focusable) {
+    
+    this._handleSize = new Size(10,18);
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        uki.extend(this, {
+            _min: 0,
+            _max: 1,
+            _value: 0,
+            _values: null,
+            _keyStep: 0.01
+        });
+    };
+    
+    /**
+    * @function
+    * @name uki.view.Slider#min
+    */
+    /**
+    * @function
+    * @name uki.view.Slider#max
+    */
+    /**
+    * @function
+    * @name uki.view.Slider#values
+    */
+    /**
+    * @function
+    * @name uki.view.Slider#keyStep
+    */
+    uki.addProps(this, ['min', 'max', 'values', 'keyStep']);
+    
+    this.values = uki.newProp('_values', function(val) {
+        this._values = val;
+        this._min = val[0];
+        this._max = val[val.length - 1];
+    });
+    
+    /**
+    * @function
+    * @fires event:change
+    * @name uki.view.Slider#value
+    */
+    this.value = uki.newProp('_value', function(val) {
+        this._value = MAX(this._min, MIN(this._max, val));
+        this._position = this._val2pos(this._value);
+        this._moveHandle();
+    });
+    
+    this._pos2val = function(pos, cacheIndex) {
+        if (this._values) {
+            var index = Math.round(1.0 * pos / (this._rect.width - this._handleSize.width) * (this._values.length - 1));
+            if (cacheIndex) this._cachedIndex = index;
+            return this._values[index];
+        }
+        return pos / (this._rect.width - this._handleSize.width) * (this._max - this._min) + this._min;
+    };
+    
+    this._val2pos = function(val) {
+        if (this._values) {
+            var index = this._cachedIndex !== undefined ? this._cachedIndex : uki.binarySearch(val, this._values);
+            return index / (this._values.length - 1) * (this._rect.width - this._handleSize.width);
+        }
+        return (val - this._min) / (this._max - this._min) * (this._rect.width - this._handleSize.width);
+    };
+    
+    this._createDom = function() {
+        this._dom = uki.createElement('div', this.defaultCss + 'height:18px;-moz-user-select:none;-webkit-user-select:none;overflow:visible;');
+        this._initClassName();
+        this._handle = uki({ 
+            view: 'SliderHandle', 
+            rect: new Rect(0, (this._rect.height-this._handleSize.height)/2, this._handleSize.width, this._handleSize.height),
+            anchors: 'left top'
+        })[0];
+        this.appendChild(this._handle);
+        
+        uki.theme.background('slider-bar').attachTo(this);
+        uki.each(['draggesturestart', 'draggesture', 'draggestureend'], function(i, name) {
+            this._handle.bind(name, uki.proxy(this['_' + name], this));
+        }, this);
+        
+        this.bind(uki.view.List.prototype.keyPressEvent(), this._keypress);
+        this.bind('click', this._click);
+        
+        this._initFocusable();
+    };
+    
+    this._focus = function(e) {
+        this._handle._focus();
+        Focusable._focus.call(this, e);
+    };
+
+    this._blur = function(e) {
+        this._handle._blur();
+        Focusable._blur.call(this, e);
+    };
+    
+    this._click = function(e) {
+        var x = e.pageX - uki.dom.offset(this._dom).x - this._handleSize.width/2;
+        this.value(this._pos2val(x, true));
+        this._cachedIndex = undefined;
+        this.trigger('change', {source: this, value: this._value});
+    };
+    
+    this._keypress = function(e) {
+        if (e.which == 39 || e.keyCode == 39) {
+            this.value(this.value() + this._keyStep * (this._max - this._min));
+        } else if (e.which == 37 || e.keyCode == 37) {
+            this.value(this.value() - this._keyStep * (this._max - this._min));
+        }
+    };
+    
+    this._moveHandle = function() {
+        var rect = this._handle.rect().clone();
+        rect.x = this._position;
+        rect.y = (this._rect.height - this._handleSize.height) / 2;
+        this._handle.rect(rect).layout();
+    };
+    
+    this._draggesturestart = function(e) {
+        this._dragging = true;
+        this._initialPosition = this._handle.rect().clone();
+        return true;
+    };
+    
+    /**
+     * @fires event:change
+     */
+    this._draggesture = function(e) {
+        var position = MAX(0, MIN(this._rect.width - this._handleSize.width, this._initialPosition.x + e.dragOffset.x));
+        this.value(this._pos2val(position, true));
+        this._cachedIndex = undefined;
+    };
+    
+    this._draggestureend = function(e) {
+        this._dragging = false;
+        this._initialPosition = null;
+        this.value(this._pos2val(this._position, true));
+        this._cachedIndex = undefined;
+        this.trigger('change', {source: this, value: this._value});
+    };
+    
+    this._layoutDom = function(rect) {
+        Base._layoutDom.call(this, rect);
+        this._position = this._val2pos(this._value);
+        this._moveHandle();
+        return true;
+    };
+
+    this._bindToDom = function(name) {
+        if (name == 'change') return true;
+        return uki.view.Focusable._bindToDom.call(this, name) || Base._bindToDom.call(this, name);
+    };
+    
+});
+
+uki.view.declare('uki.view.SliderHandle', uki.view.Button, { _backgroundPrefix: 'slider-handle-', _focusable: false });
+
+/**
+* Horizontal Split Pane
+*
+* @author voloko
+* @name uki.view.HSplitPane
+* @class
+* @extends uki.view.Container
+*/
+uki.view.declare('uki.view.HSplitPane', uki.view.Container, function(Base) {
+    this._throttle = 0; // do not try to render more often than every Xms
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        this._originalRect = this._rect;
+        uki.extend(this, {
+            _vertical: false,
+            _handlePosition: 200,
+            _autogrowLeft: false,
+            _autogrowRight: true,
+            _handleWidth: 7,
+            _leftMin: 100,
+            _rightMin: 100,
+            
+            _panes: []
+        });
+    };
+    
+    /**
+    * @function
+    * @name uki.view.HSplitPane#leftMin
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#rightMin
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#autogrowLeft
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#autogrowRight
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#throttle
+    */
+    uki.addProps(this, ['leftMin', 'rightMin', 'autogrowLeft', 'autogrowRight', 'throttle']);
+    this.topMin = this.leftMin;
+    this.bottomMin = this.rightMin;
+    
+    /**
+    * @function
+    * @fires event:handleMove
+    * @name uki.view.HSplitPane#handlePosition
+    */
+    this.handlePosition = uki.newProp('_handlePosition', function(val) {
+        this._handlePosition = this._normalizePosition(val);
+        this.trigger('handleMove', {source: this, handlePosition: this._handlePosition, dragValue: val });
+        this._resizeChildViews();
+    });
+    
+    /**
+    * @function
+    * @name uki.view.HSplitPane#handleWidth
+    */
+    this.handleWidth = uki.newProp('_handleWidth', function(val) {
+        if (this._handleWidth != val) {
+            this._handleWidth = val;
+            var handle = this._createHandle();
+            this._dom.insertBefore(handle, this._handle);
+            this._removeHandle();
+            this._handle = handle;
+            this._resizeChildViews();
+        }
+    });
+    
+    
+    this._normalizePosition = function(val) {
+        var prop = this._vertical ? 'height' : 'width';
+        return MAX(
+                this._leftMin,
+                MIN(
+                    this._rect[prop] - this._rightMin - this._handleWidth,
+                    MAX(0, MIN(this._rect ? this._rect[prop] : 1000, val * 1))
+                ));
+    };
+    
+    
+    this._removeHandle = function() {
+        this._dom.removeChild(this._handle);
+    };
+    
+    this._createHandle = function() {
+        var handle;
+        if (this._vertical) {
+            handle = uki.theme.dom('splitPane-vertical', {handleWidth: this._handleWidth});
+            handle.style.top = this._handlePosition + PX;
+        } else {
+            handle = uki.theme.dom('splitPane-horizontal', {handleWidth: this._handleWidth});
+            handle.style.left = this._handlePosition + PX;
+        }
+        
+        uki.each(['draggesturestart', 'draggesture', 'draggestureend'], function(i, name) {
+            uki.dom.bind(handle, name, uki.proxy(this['_' + name], this));
+        }, this);
+        
+        return handle;
+    };
+    
+    this._createDom = function() {
+        this._dom = uki.createElement('div', this.defaultCss);
+        this._initClassName();
+        for (var i=0, paneML; i < 2; i++) {
+            paneML = { view: 'Container' };
+            paneML.anchors = i == 1         ? 'left top bottom right' :
+                             this._vertical ? 'left top right' :
+                                              'left top bottom';
+            paneML.rect = i == 0 ? this._leftRect() : this._rightRect();
+            this._panes[i] = uki.build(paneML)[0];
+            this.appendChild(this._panes[i]);
+        };
+        this._dom.appendChild(this._handle = this._createHandle());
+    };
+    
+    this._normalizeRect = function(rect) {
+        rect = Base._normalizeRect.call(this, rect);
+        var newRect = rect.clone();
+        if (this._vertical) {
+            newRect.height = MAX(newRect.height, this._leftMin + this._rightMin); // force min width
+        } else {
+            newRect.width = MAX(newRect.width, this._leftMin + this._rightMin); // force min width
+        }
+        return newRect;
+    };
+    
+    this._resizeSelf = function(newRect) {
+        var oldRect = this._rect,
+            dx, prop = this._vertical ? 'height' : 'width';
+        if (!Base._resizeSelf.call(this, newRect)) return false;
+        if (this._autogrowLeft) {
+            dx = newRect[prop] - oldRect[prop];
+            this._handlePosition = this._normalizePosition(this._handlePosition + (this._autogrowRight ? dx / 2 : dx));
+        }
+        if (this._vertical) {
+            if (newRect.height - this._handlePosition < this._rightMin) {
+                this._handlePosition = MAX(this._leftMin, newRect.height - this._rightMin);
+            }
+        } else {
+            if (newRect.width - this._handlePosition < this._rightMin) {
+                this._handlePosition = MAX(this._leftMin, newRect.width - this._rightMin);
+            }
+        }
+        return true;
+    };
+    
+    this._draggesturestart = function(e) {
+        var offset = uki.dom.offset(this.dom());
+        this._posWithinHandle = (e[this._vertical ? 'pageY' : 'pageX'] - offset[this._vertical ? 'y' : 'x']) - this._handlePosition;
+        return true;
+    };
+    
+    this._draggesture = function(e) {
+        this._updatePositionOnDrag(e);
+    };
+    
+    this._draggestureend = function(e, offset) {
+        this._updatePositionOnDrag(e);
+    };
+    
+    this._updatePositionOnDrag = function(e) {
+        var offset = uki.dom.offset(this.dom());
+        this.handlePosition(e[this._vertical ? 'pageY' : 'pageX'] - offset[this._vertical ? 'y' : 'x'] - this._posWithinHandle);
+        if (this._throttle) {
+            this._throttleHandler = this._throttleHandler || uki.proxy(function() {
+                this.layout();
+                this._trottling = false;
+            }, this);
+            if (this._trottling) return;
+            this._trottling = true;
+            setTimeout(this._throttleHandler, this._throttle);
+        } else {
+            this.layout();
+        }
+    };
+    
+    
+    /**
+    * @function
+    * @name uki.view.HSplitPane#topPane
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#leftPane
+    */
+    this.topPane = this.leftPane = function(pane) {
+        return this._paneAt(0, pane);
+    };
+    
+    /**
+    * @function
+    * @name uki.view.HSplitPane#bottomPane
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#rightPane
+    */
+    this.bottomPane = this.rightPane = function(pane) {
+        return this._paneAt(1, pane);
+    };
+    
+    /**
+    * @function
+    * @name uki.view.HSplitPane#topChildViews
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#leftChildViews
+    */
+    this.topChildViews = this.leftChildViews = function(views) {
+        return this._childViewsAt(0, views);
+    };
+    
+    /**
+    * @function
+    * @name uki.view.HSplitPane#rightChildViews
+    */
+    /**
+    * @function
+    * @name uki.view.HSplitPane#bottomChildViews
+    */
+    this.bottomChildViews = this.rightChildViews = function(views) {
+        return this._childViewsAt(1, views);
+    };
+    
+    this._childViewsAt = function(i, views) {
+        if (views === undefined) return this._panes[i].childViews();
+        this._panes[i].childViews(views);
+        return this;
+    };
+    
+    this._paneAt = function(i, pane) {
+        if (pane === undefined) return this._panes[i];
+        uki.build.copyAttrs(this._panes[i], pane);
+        return this;
+    };
+    
+    this._leftRect = function() {
+        if (this._vertical) {
+            return new Rect(this._rect.width, this._handlePosition);
+        } else {
+            return new Rect(this._handlePosition, this._rect.height);
+        }
+    };
+    
+    this._rightRect = function() {
+        if (this._vertical) {
+            return new Rect(
+                0, this._handlePosition + this._handleWidth,
+                this._rect.width, this._rect.height - this._handleWidth - this._handlePosition
+            );
+        } else {
+            return new Rect(
+                this._handlePosition + this._handleWidth, 0, 
+                this._rect.width - this._handleWidth - this._handlePosition, this._rect.height
+            );
+        }
+    };
+    
+    this._resizeChildViews = function() {
+        this._panes[0].rect(this._leftRect());
+        this._panes[1].rect(this._rightRect());
+    };
+    
+    this._layoutDom = function(rect) {
+        Base._layoutDom.call(this, rect);
+        this._handle.style[this._vertical ? 'top' : 'left'] = this._handlePosition + 'px';
+    };
+    
+    this._bindToDom = function(name) {
+        if (name == 'handleMove') return true;
+        return Base._bindToDom.call(this, name);
+    };
+    
+});
+
+/**
+* Vertical Split Pane
+*
+* @author voloko
+* @name uki.view.VSplitPane
+* @class
+* @extends uki.view.HSplitPane
+*/
+uki.view.declare('uki.view.VSplitPane', uki.view.HSplitPane, function(Base) {
+    this._setup = function() {
+        Base._setup.call(this);
+        this._vertical = true;
+    };
+});
+
+
+uki.Collection.addAttrs(['handlePosition']);
+
+
+/**
+ * Popup
+ * 
+ * @author voloko
+ * @name uki.view.Popup
+ * @class
+ * @extends uki.view.Container
+ */
+uki.view.declare('uki.view.Popup', uki.view.Container, function(Base) {
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        uki.extend(this, {
+            _offset: 2,
+            _relativeTo: null,
+            _horizontal: false,
+            _flipOnResize: true,
+            _defaultBackground: 'theme(popup-normal)'
+        });
+    };
+    
+    this._createDom = function() {
+        Base._createDom.call(this);
+        this.hideOnClick(true);
+    };
+    
+    /**
+    * @function
+    * @name uki.view.Popup#offset
+    */
+    /**
+    * @function
+    * @name uki.view.Popup#relativeTo
+    */
+    /**
+    * @function
+    * @name uki.view.Popup#horizontal
+    */
+    /**
+    * @function
+    * @name uki.view.Popup#flipOnResize
+    */
+    uki.addProps(this, ['offset', 'relativeTo', 'horizontal', 'flipOnResize']);
+    
+    /**
+    * @function
+    * @name uki.view.Popup#hideOnClick
+    */
+    this.hideOnClick = function(state) {
+        if (state === undefined) return this._clickHandler;
+        if (state != !!this._clickHandler) {
+            if (state) {
+                this._clickHandler = this._clickHandler || uki.proxy(function(e) {
+                    if (uki.dom.contains(this._relativeTo.dom(), e.target)) return;
+                    if (uki.dom.contains(this.dom(), e.target)) return;
+                    this.hide();
+                }, this);
+                uki.dom.bind(doc.body, 'mousedown', this._clickHandler);
+                uki.dom.bind(root, 'resize', this._clickHandler);
+            } else {
+                uki.dom.unbind(doc.body, 'mousedown', this._clickHandler);
+                uki.dom.unbind(root, 'resize', this._clickHandler);
+                this._clickHandler = false;
+            }
+        }
+        return this;
+    };
+    
+    /**
+    * @function
+    * @name uki.view.Popup#toggle
+    */
+    this.toggle = function() {
+        if (this.parent() && this.visible()) {
+            this.hide();
+        } else {
+            this.show();
+        }
+    };
+    
+    /**
+    * @function
+    * @name uki.view.Popup#show
+    */
+    this.show = function() {
+        this.visible(true);
+        if (!this.parent()) {
+            new uki.Attachment( root, this );
+        } else {
+            this.rect(this._recalculateRect());
+            this.layout(this._rect);
+        }
+        this.trigger('toggle', { source: this });
+    };
+    
+    /**
+    * @function
+    * @name uki.view.Popup#hide
+    */
+    this.hide = function() {
+        this.visible(false);
+        this.trigger('toggle', { source: this });
+    };
+    
+    this.parentResized = function() {
+        this.rect(this._recalculateRect());
+    };
+    
+    this._resizeSelf = function(newRect) {
+        this._rect = this._normalizeRect(newRect);
+        return true;
+    };
+    
+    this._layoutDom = function(rect) {
+        return Base._layoutDom.call(this, rect);
+    };
+    
+    this._recalculateRect = function() {
+        if (!this.visible()) return this._rect;
+        var relativeOffset = uki.dom.offset(this._relativeTo.dom()),
+            relativeRect = this._relativeTo.rect(),
+            rect = this.rect().clone(),
+            attachment = uki.view.top(this),
+            attachmentRect = attachment.rect(),
+            attachmentOffset = uki.dom.offset(attachment.dom()),
+            position = new Point(),
+            hOffset = this._horizontal ? this._offset : 0,
+            vOffset = this._horizontal ? 0 : this._offset;
+
+        relativeOffset.offset(-attachmentOffset.x, -attachmentOffset.y);
+
+        if (this._anchors & ANCHOR_RIGHT) {
+            position.x = relativeOffset.x + relativeRect.width - (this._horizontal ? 0 : rect.width) + hOffset;
+        } else if (this._anchors & ANCHOR_LEFT) {
+            position.x = relativeOffset.x - (this._horizontal ? rect.width : 0) - hOffset;
+        } else {
+            position.x = relativeOffset.x + ((relativeRect.width - rect.width) >> 1) - hOffset;
+        }
+        
+        if (this._anchors & ANCHOR_BOTTOM) {
+            position.y = relativeOffset.y + (this._horizontal ? relativeRect.height : 0) - rect.height - vOffset;
+        } else if (this._anchors & ANCHOR_TOP) {
+            position.y = relativeOffset.y + (this._horizontal ? 0 : relativeRect.height) + vOffset;
+        } else {
+            position.y = relativeOffset.y + ((relativeRect.height - rect.height) >> 1) + vOffset;
+        }
+        
+        return new Rect(position.x, position.y, rect.width, rect.height);
+    };
+});
+
+
+uki.each(['show', 'hide', 'toggle'], function(i, name) {
+    uki.fn[name] = function() {
+        this.each(function() { this[name](); });
+    };
+});
+/**
+ * Vertical Flow
+ * Arranges child views verticaly, one after another
+ *
+ * @author voloko
+ * @name uki.view.VFlow
+ * @class
+ * @extends uki.view.Container
+ */
+uki.view.declare('uki.view.VFlow', uki.view.Container, function(Base) {
+    this.contentsSize = function() {
+        var value = uki.reduce(0, this._childViews, function(sum, e) { 
+                return sum + (e.visible() ? e.rect().height : 0); 
+            } );
+        return new Size( this.contentsWidth(), value );
+    };
+    
+    this.hidePartlyVisible = uki.newProp('_hidePartlyVisible');
+    
+    this.resizeToContents = function(autosizeStr) {
+        this._resizeChildViews(this._rect);
+        return Base.resizeToContents.call(this, autosizeStr);
+    }
+    
+    this.layout = function() {
+        return Base.layout.call(this);
+    };
+    
+    // resize in layout
+    this._resizeChildViews = function(oldRect) {
+        var offset = 0, rect, view;
+        for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
+            view = childViews[i];
+            view.parentResized(oldRect, this._rect);
+            view.rect().y = offset;
+            // view.rect(new Rect(view._rect.x, offset, view._rect.width, view._rect.height));
+            if (this._hidePartlyVisible) {
+                view.visible(view._rect.height + offset <= this._rect.height);
+            }
+            if (view.visible()) offset += view._rect.height;
+        };
+    };
+    
+    this.childResized = function() {
+        this._needsLayout = true;
+        uki.after(uki.proxy(this._afterChildResized, this));
+    };
+    
+    this._contentChanged = this.childResized;
+    
+    this._afterChildResized = function() {
+        this.resizeToContents('height');
+        this.parent().childResized(this);
+        this.layoutIfNeeded();
+    };
+    
+});
+
+/**
+ * Horizontla Flow
+ * Arranges child views horizontally
+ *
+ * @author voloko
+ * @name uki.view.HFlow
+ * @class
+ * @extends uki.view.VFlow
+ */
+uki.view.declare('uki.view.HFlow', uki.view.VFlow, function(Base) {
+    this.contentsSize = function() {
+        var value = uki.reduce(0, this._childViews, function(sum, e) { 
+                return sum + (e.visible() ? e.rect().width : 0); 
+            } );
+        return new Size( value, this.contentsHeight() );
+    };
+    
+    this._resizeChildViews = function(oldRect) {
+        var offset = 0, rect, view;
+        for (var i=0, childViews = this.childViews(); i < childViews.length; i++) {
+            view = childViews[i];
+            view.parentResized(oldRect, this._rect);
+            view.rect().x = offset;
+            // view.rect(new Rect(offset, view._rect.y, view._rect.width, view._rect.height));
+            if (this._hidePartlyVisible) {
+                view.visible(view._rect.width + offset <= this._rect.width);
+            }
+            if (view.visible()) offset += view._rect.width;
+        };
+    };
+    
+    this._afterChildResized = function() {
+        this.resizeToContents('width');
+        this.parent().childResized(this);
+        this.layoutIfNeeded();
+    };
+});
+
+
+
+
+uki.view.toolbar = {};
+
+/**
+* Toolbar
+*
+* @author voloko
+* @name uki.view.Toolbar
+* @class
+* @extends uki.view.Container
+*/
+uki.view.declare('uki.view.Toolbar', uki.view.Container, function(Base) {
+
+    this.typeName = function() { return 'uki.view.Toolbar'; };
+    
+    this._moreWidth = 30;
+    
+    this._setup = function() {
+        Base._setup.call(this);
+        this._buttons = [];
+        this._widths = [];
+    };
+    
+    /**
+    * @function
+    * @name uki.view.Toolbar#buttons
+    */
+    this.buttons = uki.newProp('_buttons', function(b) {
+        this._buttons = b;
+        var buttons = uki.build(uki.map(this._buttons, this._createButton, this)).resizeToContents('width');
+        this._flow.childViews(buttons);
+        this._totalWidth = uki.reduce(0, this._flow.childViews(), function(s, v) { return s + v.rect().width; });
+    });
+    
+    /**
+    * @function
+    * @name uki.view.Toolbar#moreWidth
+    */
+    uki.moreWidth = uki.newProp('_moreWidth', function(v) {
+        this._moreWidth = v;
+        this._updateMoreVisible();
+    });
+    
+    this._createDom = function() {
+        Base._createDom.call(this);
+        
+        var rect = this.rect(),
+            flowRect = rect.clone().normalize(),
+            moreRect = new Rect(rect.width - this._moreWidth, 0, this._moreWidth, rect.height),
+            flowML = { view: 'HFlow', rect: flowRect, anchors: 'left top right', className: 'toolbar-flow', hidePartlyVisible: true },
+            moreML = { view: 'Button', rect: moreRect, anchors: 'right top', className: 'toolbar-button',  visible: false, backgroundPrefix: 'toolbar-more-', text: '>>', focusable: false },
+            popupML = { view: 'Popup', rect: '0 0', anchors: 'right top', className: 'toolbar-popup', background: 'theme(toolbar-popup)', 
+                childViews: { view: 'VFlow', rect: '0 5 0 0', anchors: 'right top left bottom' }
+            };
+            
+        this._flow = uki.build(flowML)[0];
+        this._more = uki.build(moreML)[0];
+        this.appendChild(this._flow);
+        this.appendChild(this._more);
+        popupML.relativeTo = this._more;
+        this._popup = uki.build(popupML)[0];
+        
+        this._more.bind('click', uki.proxy(this._showMissingButtons, this));
+    };
+    
+    this._showMissingButtons = function() {
+        var maxWith = this._flow.rect().width,
+            currentWidth = 0,
+            missing = [];
+        for (var i=0, childViews = this._flow.childViews(), l = childViews.length; i < l; i++) {
+            currentWidth += childViews[i].rect().width;
+            if (currentWidth > maxWith) missing.push(i);
+        };
+        var newButtons = uki.map(missing, function(i) {
+            var descr = { html: childViews[i].html(), backgroundPrefix: 'toolbar-popup-button-' };
+            uki.each(['fontSize', 'fontWeight', 'color', 'textAlign', 'inset'], function(j, name) {
+                descr[name] = uki.attr(childViews[i], name);
+            });
+            return this._createButton(descr);
+        }, this);
+        uki('VFlow', this._popup).childViews(newButtons).resizeToContents('width height');
+        this._popup.resizeToContents('width height').height(this._popup.height() + 5).toggle();
+    };
+    
+    this._updateMoreVisible = function() {
+        var rect = this._rect;
+        if (this._more.visible() != rect.width < this._totalWidth) {
+            this._more.visible(rect.width < this._totalWidth);
+            var flowRect = this._flow.rect();
+            flowRect.width += (rect.width < this._totalWidth ? -1 : 1)*this._moreWidth;
+            this._flow.rect(flowRect);
+        }
+    };
+    
+    this.rect = function(rect) {
+        var result = Base.rect.call(this, rect);
+        if (rect) this._updateMoreVisible();
+        return result;
+    };
+    
+    this._createButton = function(descr) {
+        var rect = this.rect().clone().normalize();
+        rect.width = 100;
+        return uki.extend({ 
+                view: 'Button', rect: rect, focusable: false, align: 'left',
+                anchors: 'left top', backgroundPrefix: 'toolbar-button-', autosizeToContents: 'width', focusable: false
+            }, descr);
+    };    
+});
+
+}());
+(function() {
+    var defaultCss = 'position:absolute;z-index:100;-moz-user-focus:none;font-family:Arial,Helvetica,sans-serif;',
+        reflex     = '<div style="position:absolute;z-index:1;left:1px;bottom:0px;right:1px;height:1px;overflow:hidden;background:rgba(255,255,255,0.4)"></div>';
+
+    uki.theme.airport = uki.extend({}, uki.theme.Base, {
+        imagePath: 'http://static.ukijs.org/pkg/0.3.8/uki-theme/airport/i/',
+        
+        backgrounds: {
+            // basic button
+            'button-normal': function() {
+                return new uki.background.LinearGradient({
+                    startColor: '#FDFEFF',
+                    stops: [ 
+                        { pos: 0.15, color: '#F5F7FD' },
+                        { pos: 0.8, color: '#C9CACF' }
+                    ],
+                    endColor: '#C7CBD2',
+                    innerHTML: reflex,
+                    css: 'border:1px solid #666;border-radius:3px;box-shadow:0 1px 0 rgba(255,255,255,0.5);'
+                });
+            },
+            
+            'button-hover': function() {
+                return new uki.background.LinearGradient({
+                    startColor: '#FFFFFF',
+                    stops: [ 
+                        { pos: 0.7, color: '#D7DAE4' }
+                    ],
+                    endColor: '#D9DEE6',
+                    innerHTML: reflex,
+                    css: 'border:1px solid #666;border-radius:3px;box-shadow:0 1px 0 rgba(255,255,255,0.5);'
+                });
+            },
+
+            'button-down': function() {
+                return new uki.background.LinearGradient({
+                    startColor: '#9C9DA1',
+                    stops: [ 
+                        { pos: 0.6, color: '#C5C7CD' }
+                    ],
+                    endColor: '#CCCFD6',
+                    css: 'border:1px solid #666;border-radius:3px;box-shadow:inset 0 1px 3px rgba(0,0,0,0.5);'
+                });
+            },
+            
+            'button-focus': function() {
+                if (uki.browser.cssBoxShadow() == 'unsupported') {
+                    return new uki.background.CssBox( 
+                        'background:#7594D2;' + (uki.browser.cssFilter() && uki.image.needAlphaFix ? 'filter:Alpha(opacity=70);' : 'opacity:0.7;'), 
+                        { inset: '-2 -2', zIndex: -2 } 
+                    );
+                }
+                return new uki.background.Css({ 
+                    // WebkitTransition: '-webkit-box-shadow 0.2s linear',
+                    boxShadow: '0 0 6px #0244D4',
+                    borderRadius: '3px'
+                });
+            },
+            
+            'button-disabled': function() {
+                return new uki.background.Multi(
+                    uki.theme.background('button-normal'),
+                    new uki.background.Css({ color: '#999' })
+                );
+            },
+            
+            
+            // checkbox
+            'checkbox-normal': function() {
+                return checkboxBg(18);
+            },
+            
+            'checkbox-hover': function() {
+                return checkboxBg(54);
+            },
+            
+            'checkbox-disabled': function() {
+                return checkboxBg(90);
+            },
+            
+            'checkbox-checked-normal': function() {
+                return checkboxBg(0);
+            },
+            
+            'checkbox-checked-hover': function() {
+                return checkboxBg(36);
+            },
+            
+            'checkbox-checked-disabled': function() {
+                return checkboxBg(72);
+            },
+            
+            'checkbox-focus': function() {
+                if (uki.image.needAlphaFix) {
+                    return new uki.background.CssBox('', 
+                        { innerHTML: '<div style="position:absolute;left:50%;top:50%;width:19px;height:19px;overflow:hidden;' +
+                        'margin:-10px 0 0 -10px;background:#7594D2;filter:Alpha(opacity=70);"></div>', zIndex: -2  }
+                    );
+                }
+                return new uki.background.CssBox('', 
+                    { innerHTML: '<div style="position:absolute;left:50%;top:50%;width:24px;height:24px;overflow:hidden;' +
+                    'margin:-12px 0 0 -12px; background: url(' + uki.theme.imageSrc('checkbox-focus') + ') 0 0"></div>', zIndex: -2  }
+                );
+            },
+            
+
+            // radio button
+            'radio-normal': function() {
+                return radioBg(18);
+            },
+            
+            'radio-hover': function() {
+                return radioBg(54);
+            },
+            
+            'radio-disabled': function() {
+                return radioBg(90);
+            },
+            
+            'radio-checked-normal': function() {
+                return radioBg(0);
+            },
+            
+            'radio-checked-hover': function() {
+                return radioBg(36);
+            },
+            
+            'radio-checked-disabled': function() {
+                return radioBg(72);
+            },    
+            
+            'radio-focus': function() {
+                if (uki.image.needAlphaFix) return uki.theme.airport.background('checkbox-focus');
+                var src = uki.theme.imageSrc('radio-focus');
+                return new uki.background.CssBox('', 
+                    { innerHTML: '<div style="position:absolute;left:50%;top:50%;width:24px;height:24px;overflow:hidden;' +
+                    'margin:-12px 0 0 -12px; background: url(' + src + ') 0 0"></div>', zIndex: -2  }
+                );
+            },
+            
+            
+                    
+            // toolbar button
+            'toolbar-button-normal': function() {
+                return new uki.background.Css('#CCC');
+            },
+            
+            'toolbar-button-hover': function() {
+                return new uki.background.Css('#E0E0E0');
+            },
+            
+            'toolbar-button-down': function() {
+                return new uki.background.Css('#AAA');
+            },
+            
+            'toolbar-button-focus': function() {
+                return new uki.background.Css('#CCC');
+            },
+            
+            'toolbar-popup-button-normal': function() {
+                return new uki.background.Css({ textAlign: 'left' });
+            },
+            
+            'toolbar-popup-button-down': function() {
+                return new uki.background.Css({ background: '#AAA', textAlign: 'left' });
+            },
+            
+            'toolbar-popup-button-hover': function() {
+                return new uki.background.Css({ background: '#4086FF', color: '#FFF', textAlign: 'left', textShadow: 'none' });
+            },
+            
+            
+            // panel
+            'popup-normal': function() {
+                return new uki.background.CssBox('background:#ECEDEE;border-radius:5px;border:1px solid #CCC;box-shadow:0 3px 8px rgba(0,0,0,0.6)');
+            },
+            
+            'panel': function() {
+                return new uki.background.LinearGradient({
+                    startColor: '#DEDEDF',
+                    stops: [ 
+                        { pos: 0.2, color: '#D4D4D5' },
+                        { pos: 0.9, color: '#989899' }
+                    ],
+                    endColor: '#989899',
+                    css: 'box-shadow:0 1px 0 rgba(0,0,0,0.5);'
+                });
+            },
+            
+            // text field
+            'input': function() {
+                return new uki.background.CssBox(
+                   'background:white;border: 1px solid #999;border-top-color:#777;box-shadow:0 1px 0 rgba(255, 255, 255, 0.4), inset 0 1px 2px rgba(0,0,0,0.2);'
+               );
+            },
+            
+            'input-focus': function() {
+                if (uki.browser.cssBoxShadow() == 'unsupported') {
+                    return new uki.background.CssBox( 
+                        'background:#7594D2;' + (uki.browser.cssFilter() && uki.image.needAlphaFix ? 'filter:Alpha(opacity=70);' : 'opacity:0.7;'), 
+                        { inset: '-2 -2', zIndex: -2 } 
+                    );
+                }
+                return new uki.background.Css({ 
+                    // WebkitTransition: '-webkit-box-shadow 0.2s linear',
+                    boxShadow: '0 0 6px #0244D4'
+                });
+            },
+            
+            
+            
+            // slider
+            'slider-handle-normal': function() {
+                return new uki.background.LinearGradient({
+                    startColor: '#FFFFFF',
+                    endColor: '#B1CEEA',
+                    innerHTML: sliderPin(),
+                    css: 'border:1px solid #8393A6;border-bottom-color:#687482;box-shadow:0 0 2px rgba(0,0,0,0.5);'
+                });
+            },
+            
+            'slider-handle-hover': function() {
+                return new uki.background.LinearGradient({
+                    startColor: '#DFF6FF',
+                    endColor: '#8AC5F3',
+                    innerHTML: sliderPin(),
+                    css: 'border:1px solid #8393A6;border-bottom-color:#687482;box-shadow:0 0 2px rgba(0,0,0,0.5);'
+                });
+            },
+
+            'slider-handle-focus': function() {
+                if (uki.browser.cssBoxShadow() == 'unsupported') {
+                    return new uki.background.CssBox( 
+                        'background:#7594D2;' + (uki.browser.cssFilter() && uki.image.needAlphaFix ? 'filter:Alpha(opacity=70);' : 'opacity:0.7;'), 
+                        { inset: '-2 -2', zIndex: -2 } 
+                    );
+                }
+                return new uki.background.CssBox(
+                    'box-shadow: 0 0 6px #0244D4;',
+                    { zIndex: 2, inset: '1' }
+                );
+            },
+            
+            'slider-bar': function() {
+                uki.dom.offset.initializeBoxModel();
+                return new uki.background.CssBox(
+                    'overflow:visible;',
+                    { 
+                        inset: uki.dom.offset.boxModel ? '0 2 0 0' : '0 0',
+                        innerHTML: '<div style="' + uki.browser.css('position:absolute;left:0;overflow:hidden;width:100%;top:50%;height:3px;margin-top:-2px;background:#C6C7CD;border:1px solid #777;border-radius:3px;box-shadow:0 1px 0 rgba(255,255,255,0.5),inset 0 1px 1px rgba(0,0,0,0.2)') + '"></div>' 
+                    }
+                );
+            },
+            
+            // list
+            list: function(rowHeight) {
+                return new uki.background.Rows(rowHeight, '#EDF3FE');
+            },
+            
+            // table
+            'table-header': function() {
+                return new uki.background.LinearGradient({
+                    startColor: '#FFFFFF',
+                    stops: [
+                        { pos: 0.8, color: '#e0e0e0' }
+                    ],
+                    endColor: '#EEEEEE',
+                    css: 'border-bottom:1px solid #CCC;'
+                });
+            }
+        },
+        
+        images: {
+            checkbox: function() {
+                var prefix = "checkbox/normal";
+                return uki.image(u(prefix + ".png"), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAABsCAYAAABn5uLmAAAF1UlEQVRYw+2Y3VMTVxjGve9N/4T+CU4vetErpxfauzp1puNFZ3Ckah1jibWOGqhFhYpY8AvBIoygiErwIxAI+UCQhhgkEQwgIQKiIXwJBEKABIJP913dlJNddE8602mrO/ObIee8z8Oe3cnL87JuXZLXJ+vXf/zp519kyjYM7nHwsOtgDrakaLDhy6+3xU30jucgXr2CKsorq7H3yGno8sqwZZtmc9zoWpMP0eUVVdxrcWDHT1k4VVaP/KsWMMe63PAY4UhM5Okzf/znRDp7fEjV/oLjRdUorm5GidHNGhUb2hCcX0azw41NX22Fy+MVP6/GPx7Ed2kZyMi/gtMVFjz2+UE6xqigqgUvQ1GcLa7A9n2Z2K45hGcjU+KahFaXhf1ZhcgrN6Hufoe4RjrGiM46EoxgYCSIvNI70B49jx1pOvj8k+J6ztlL2H04FydLa1F4wyquEbJnlFNSg+eTiyLtvX5kF+mxJ/0UUn84jIo7ZqTsTcexwirhburFfamWdIzRcaGof3whjtXZi8xzlUg7cga7DmQjI68cuaU1uNPoZupIxxj9LDzA3pEwQ2W9A1nCnWULbyjn0l0U3TDLakjHGB08UQyPf07GhesNwnMxIPuiHu39k7J90jFG+46eg3soJMPRN4ETRTdxu6lTcZ90jNEeXS6cAzPckI4x2rn/GIieQFg1kkb27d+m0YGXde/Z1eruBi8yk8ZWF4iVlVeqkTSMkanJgdjKCjekYxt/wz0sx2LckI4xqjKYsLS8zA3pGKMKvSEpI9IxRiVXbyK6tMQN6RijotIriESj3JCOMTpTeAkLkSg3pGOMTuYXYH4xwg3p2J594jeEFxa5IR1jlHE0G3PzC9yQjjE6oDuCUHieG9IxRmn7D4HgefWSRvbF3a3Rgpf/aSP8ZzKk2uutGTImhEw1NNudb8+QS0IfJob8gfjPifT09SN135sMeeu+coYMR1dgb3uETZu3iqGTPq9mbGoWO7R/ZciegWHlDDknpNaC0mtihkzdewijkzPimsSP6dnxDGm2d76uV8qQs4sxjE7P4fTl1xlylzYdgZcz4nrehdJ4hrxYZRPXCMUMORVeEukZCIjBijLkTq0OeqM1niHzhQxJ+1KtYoacEOKuhL2jTzFDmloeMXWKGXJkJsJw2+pkMmSJ3iqrUcyQL6YWZRRXWcQM+atwVF8gKNtXzJDPJxdk9A1P41RxFawPuhT3FTPk4MswN2tmyEBwUTUfMuTfuHg7JCEz4Z2yCUnDGPFM2ash3ZpTNg+kU5yyeVlzyuZlzSmbl7dO2Ty8c8pWi6opWw2qp+x3oXrKfhcfpuz/2rVx48aPUlK+/Ua2MRwYAA93DXpkHsvQazTfb4ibDA4+AREVZjA19PV5UVNbDZutDpmZ6Z/Fjbx9j1WbjI2NCgZmPHnigdOZ0EY8Xe2riiNrmszNhdDQUIfOThf6+73o6HSyRi5Xq1g4MTGKiopyzMxOKxqZTEY8fNiK3t4uLCyEQTrGyOFoFgs7OlyoN9XCaKyRmdhsFvxhb0J3dydGR4fFNdIxRk1N5rjA43GjudmGurpa4fOiuNbW5oDVZhL3vN7ueC3pGCOzxRjfnBWO5XI5hTswCXd2Fz5fr/CGbgtrD9DV9Ujcl2pJxxjVGm+9ecivGRsfFu7CjsZGM0wNRuEILcKx2+H3DzJ1pGOMqm9dFzbCDPRW2oWH6XI5xDukZ5NYQzrGqLLyivAWQjLoKC73AzxstyMUmpLtk44xKisrQTg8LSM4MyEecWioX3GfdOxf2uILwm+c5IZ0jNH5gjMgIpGwaiSN7Nufl58LXt63DDk8/AK8yEwGBp6CiAn/yVOLpGGMvN4eLhMJ0rEd0tORlBHpEjpkW1JGpEvokPakjEiX0CFtSRmRjjGyWExJGZGOMTIaDUkZkY7tkNX6pIxIl9AhryVlRLqEDnk5KSPSJXTI35MyIh07ixScB8FjImlkX9z8/Dzw8u/uZ38Cqx5HdHgrjesAAAAASUVORK5CYII=", u(prefix + ".gif"));
+            },
+            'checkbox-focus': function() {
+                return uki.image(u("checkbox/focus.png"), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABdklEQVRIie2Wv0rDUBSHUxRFRHQRFxWRioTeZHEXVxHqYgaRNrmrj+Ai+AAdi6trH0AQh6K05uYpfBQ934m0EnHQcIuDB+5y7/l9J+dPchMEU2sER8P5VjJY2ExeljY6D8v77dHKbvK4+t3iHD/80aFXzhdLBnPN4/tFBE37vN7qjrbDzO2ZTh6GnbExXRdXl+5zLn74o1O9cOB9xjfY3MmGa8Y+bSGO0vGhSYuT2Lqz2BbnUZpfVBf7nOOn/gQWPRwNMslE0iIyh1HqDiLrTuPMXZqsuI5s3hNQX/Zuq6vcz3v44Y8OPRx4H+UKAmpHevrkCi+ubu5e33660KGHo2WmJ5g2SGpImjzJb+DTIJKJcODB1QBMgTZUakm6dQKghwMPrgbQ+ss00DBqWidA2TNpvPC0DxjzzMiVU1H0a5VI9HDgwZ0EKEdTxk+mo14GMmHCgfcf4I8F8DpF3t8D72+y92+R96+p9/vA/402gzt5Bn8VHu0d2HhIetPffvAAAAAASUVORK5CYII=");
+            },
+            radio: function() {
+                var prefix = "radio/normal";
+                return uki.image(u(prefix + ".png"), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAABsCAYAAABn5uLmAAAGiklEQVRYw+2X/08TdxjH/RP6Z+2XJWeyzMTMjGkkEkmsog4VzSGiA0FUQAPIUHEqMC0i3Rgt66Sj8q0wkG9tgV4RAflij7b0C198du8Ozn6uPe66uGSJXvLO5fk8r+cNP/TeeT579vxXj8nmNTxzvuVbnEtCy8ASyZJqnKOvadLg8HKNDp/Y61khf2id3r8nWahxjj44VZN66xjX2OmmRTFMsY0tVaEPDnySSZnJZrhjdoqzyyEKRTc1BQ485hijyiY739btIv/ahm6BxxxjdO2BRXDPvqOlQExWR1c/fbnvO/riq2/jb9SJffCYY4x+uNNCc/4oowNHTtOpK9WUX9EQf6NWMphjjPIrHpNvOcLImF9O5Q1/UFVzd/yNWslgjjE6V1JHk2/DjPJK66j8sY0qJBO8USsZzDFGOQWVQtfIaxqfC8my9E7Q1dpnVFJnjr9RJ/bBY44xyj57lS+//5yGXwd1CzzmGKMMI2/IzLkotvdPkVMIaAoceMwl/Si/yTrNZZ7gydztpu4pUVXogwOv+pl8fSib23fomMhfryPTn+Nkd/tlocY5+uA0P1yOyzBw+zP5vfsPC3v3Z9IHHRZwjv6eT/yx2QYN9r5XfJdzRJBECRJwjr6mibWzn7N29Yujbi+tBtdoa+u9LNQ4Rx+cqskvVgdnsfeQPxCkza0tVaEPDnxy4JtMhua2DnHFv0obm5uaAgcec2zom8x8t3OI1jc2dAs85hijB03NwuLySlpG4DHHGN2pb6DY+nrawhxjdLv2PkVjsbSFOcbo+q0aCkdjaQtzjFFRaYXgm52jtUhUt8Bjjg3/wqv802etFApHdAs85hgjI88bzuUXilOCj4JrYU2BA4+5pB9lzpk87vzFQvJMeykQWlMV+uDAq34m2TmnuWM534t36x+RyzMlfV8hWahxjj44zQ83I8NoOJJt5LOOGoWsbCPJkmqco/+pJyR2RFPfPN/sXMDOSDtCjfM0dkhB7PMskxiKUeKDGufoa+yQLq7RPknLgShtSsunmtAHBz7FDjlo+LHtL/GttGiuS/msJXDgMccYVZle8lZpMQjFtnQLPOYYo4rGTsG3uEpBaWPdkb1nQNodD27vkAfjdWIfPObYZbS+nVYjm4wOZCl2SKlWMphLWkZXpM0+Ual2SCWTtIwW3GqkRWnlTdT50rvMDolayWCOMcorvS+8ml6gOTEqyzHkoZLtHRJv1Il98JhjjE4XVvF1TzpoZiWiW+Axp0jIMsPx/Jti/8SstD+HNQUOPOaSfpRZZ4q4E/k3qGdMoOmlkKrQBwde9TPJzOE5bKs3655Q1/A0uReCslDjHH1wuhIyI/ssn3H0rCC9SdY/9eeE/NgJ2SsloV9KRPaWHaPedBJyUYxo3LIj2gmZzi1bNSHb+ybTumWDT5mQnjd+5hatJfApE1J5g9ajlAmpvEHrUcqEVN6g9ShlQjrG3jC3aC2BT5mQVY3WtG7Z4FUT0jog6Lplg9s1IY/z1+nXvqldb9nog9OVkEXVTdTy0sPcslHj/HNCfsTH6x00eL3jvFeYEASfi3aEOn7u1XHLdrkGOZd7SJyfn6FwBLfsLVmocY4+OFWTweEebnRsgNbWQoyBUuiDA59kYrPZDL19nWIoFNjVZEfgwGOOMXrxwsK7XKO6THYEHnOMkcXSKqyuikl/1e0ZoQnXUPyt/G/BY44xet76NOkvjow6aWi4VxZqJYM5xqjp54dJUE+PPUlKBnOM0U8P70qNTUYOxwuy2ztkoVYymGOMamurhaWleakZlbW8PE+/236jdos5/kad2AePOcaosvIGb7W20fp6WLfAY44x4qXbcum1YnH2jZei0YCmwIHnU92yL18u4K6VFdPMzCSFw6Kq0AcHXvUzycs7w124cE5sanpE09MTFAyuyEKNc/TBaX64RqPRkJt7kpck5Oaeog9CfZJH/3NCfqSEdG8nZESRkJHthHRrJeTwcD83Pj6oKyHBgU+ZkP1Oh5SQQZ0JGSTwSQnZ1WXjPZ6JtBISPOYYo85OixAIrKZlBB5zyqhNy2RHmGOMWs2mf2WEOcboqakhKf30CHOM0aPH9cLKuwUmAbUEHnOM0b17NbzdbksrIcFjjjEqKyszVFffln7RPl0JCQ485pJ+lOXlZVxNzW2anZ3eNSHRBwde9TMpKStC3IpmczP5fB4mIVHjHH1wmh8uAv1KUQFfVHxJKCoupA+6JOA8ZeD/L5+/ASNtA71vTxEVAAAAAElFTkSuQmCC", u(prefix + ".gif"));
+            },
+            'radio-focus': function() {
+                return uki.image(u("radio/focus.png"), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABcElEQVRIx92VP0oEMRTGt9ADeAERq21E8ACLNp7AUrRbWdzdDDa6IBhnM5YqaCPCiiewyPzBA1gK1oIsHsNCNL9owBl1HYWM6MBjSPK+7+W99yWp1X7jm5dyrLUVTwTyYqotdb0dJjOdUM86Y8w86/jhX5q82TwZF1JPrkd6rtNPF40tiTBb7qps1Rlj5lnHD39wpchbMp4WKmmIKF0x/+0gSg8DlZ0KlZ05Y8w8669+DXAjgzjy7m68IPrJmoiS/Y29S907vr7fGdw+hOfDJ2eMmWcdP/zBfRqEGpKm3TnkKj3aPLi6kYO7x7fERWMdP/xfcAZveN71hEbZWpKu2RGgUcRFs0HIxODhgS8XADXQMGpK2l/t/KNMbLkMHh74cgGQHKqgcdT2O+TOwIGHB758AKNrpIc6ig0ta+Cs2gwPfLkAHB6rbyPBn5A7Aw8PfP8sgPceeFeR93Pg/SR7v4u836aVvAeVvGiVvMl/7nsGaBHOn+3vxvEAAAAASUVORK5CYII=");
+            }
+        },
+    
+        imageSrcs: {
+            x: function() {
+                return [u("x.gif"), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII="]; 
+            },
+            'splitPane-horizontal': function() {
+                return [u("splitPane/horizontal.gif"), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAICAYAAAA870V8AAAAFUlEQVQIW2MoLy//zwAEYJq6HGQAAJuVIXm0sEPnAAAAAElFTkSuQmCC"];
+            },
+            'splitPane-vertical': function() {
+                return [u("splitPane/vertical.gif"), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAADCAYAAABfwxXFAAAAE0lEQVQIHWMsLy//z0AOYMSnEwAIngTLoazFLgAAAABJRU5ErkJggg=="];
+            }
+        },
+        
+        templates: {
+            'table-header-cell': function() {
+                return new uki.theme.Template(
+                    '<div style="position:relative;border-right:1px solid #CCC;'+
+                    '${style}" class="${className}">${data}</div>');
+            },
+            
+            'table-cell': function() {
+                return new uki.theme.Template(
+                    '<div style="position:relative;border-right:1px solid #CCC;height:100%;'+
+                    '${style}" class="${className}">${data}</div>');
+            },
+            
+            'table-header-cell-asc': function() {
+                return new uki.theme.Template(
+                    '<div style="position:relative;border-right:1px solid #CCC;background: rgba(0,0,128,0.1);'+
+                    '${style}" class="${className}"><div style="padding-right:7px">${data}</div><span style="position:absolute;right:0;top:50%;margin-top:-7px;">&darr;</span></div>');
+            },
+            
+            'table-header-cell-desc': function() {
+                return new uki.theme.Template(
+                    '<div style="position:relative;border-right:1px solid #CCC;background: rgba(0,0,128,0.1);'+
+                    '${style}" class="${className}"><div style="padding-right:7px">${data}</div><span style="position:absolute;right:0;top:50%;margin-top:-7px;">&uarr;</span></div>');
+            }
+        },
+        
+        doms: {
+            'resizer': function(height) {
+                var template = new uki.theme.Template('position:absolute;width:5px;top:0;right:-3px;height:${height}px;cursor:col-resize;cursor:ew-resize;z-index:101;background:url(' + uki.theme.imageSrc('x') + ')'),
+                    node = uki.createElement('div', template.render({height:height}));
+                    
+                if (!node.style.cursor || window.opera) node.style.cursor = 'e-resize';
+                return node;
+            },
+            'splitPane-vertical': function(params) {
+                var commonVerticalStyle = 'cursor:row-resize;cursor:ns-resize;z-index:200;overflow:hidden;',
+                    handle = params.handleWidth == 1 ?
+                    uki.createElement('div', 
+                        defaultCss + 'width:100%;height:5px;margin-top:-2px;' + 
+                        commonVerticalStyle + 'background: url(' + uki.theme.imageSrc('x') + ')',
+                        '<div style="' + 
+                        defaultCss + 'background:#999;width:100%;height:1px;left:0px;top:2px;overflow:hidden;' + 
+                        '"></div>') :
+                    uki.createElement('div', 
+                        defaultCss + 'width:100%;height:' + (params.handleWidth - 2) + 'px;' +
+                        'border: 1px solid #CCC;border-width: 1px 0;' + commonVerticalStyle +
+                        'background: url(' + uki.theme.imageSrc('splitPane-vertical') + ') 50% 50% no-repeat;');
+                if (!handle.style.cursor || window.opera) handle.style.cursor = 'n-resize';
+                return handle;
+            },
+            
+            'splitPane-horizontal': function(params) {
+                var commonHorizontalStyle = 'cursor:col-resize;cursor:ew-resize;z-index:200;overflow:hidden;',
+                    handle = params.handleWidth == 1 ?
+                    uki.createElement('div', 
+                        defaultCss + 'height:100%;width:5px;margin-left:-2px;' + 
+                        commonHorizontalStyle + 'background: url(' + uki.theme.imageSrc('x') + ')',
+                        '<div style="' + 
+                        defaultCss + 'background:#999;height:100%;width:1px;top:0px;left:2px;overflow:hidden;' + 
+                        '"></div>') :
+                    uki.createElement('div', 
+                        defaultCss + 'height:100%;width:' + (params.handleWidth - 2) + 'px;' +
+                        'border: 1px solid #CCC;border-width: 0 1px;' + commonHorizontalStyle + 
+                        'background: url(' + uki.theme.imageSrc('splitPane-horizontal') + ') 50% 50% no-repeat;');
+                if (!handle.style.cursor || window.opera) handle.style.cursor = 'e-resize';
+                return handle;
+                
+            }
+        },
+        styles: {
+            base: function() {
+                return 'font-family:Arial,Helvetica,sans-serif;';
+            },
+            'label': function() {
+                return 'font-size:12px;';
+            },
+            'button': function() {
+                return 'color:#333;text-align:center;font-weight:bold;text-shadow:0 1px 0 rgba(255,255,255,0.6);';
+            },
+            'input': function() {
+                return 'font-size:12px;';
+            }
+        }
+    });
+
+    function u(url) {
+        return uki.theme.airport.imagePath + url;
+    }
+    
+    function checkboxBg (offset) {
+        var src = uki.theme.imageSrc('checkbox');
+        return new uki.background.CssBox('', 
+            { innerHTML: '<div style="position:absolute;left:50%;top:50%;width:18px;height:18px;overflow:hidden;' +
+            'margin:-9px 0 0 -9px; background: url(' + src + ') 0 -' + offset + 'px"></div>' }
+        );
+    }
+    
+    function radioBg (offset) {
+        var src = uki.theme.imageSrc('radio');
+        return new uki.background.CssBox('', 
+            { innerHTML: '<div style="position:absolute;left:50%;top:50%;width:18px;height:18px;overflow:hidden;' +
+            'margin:-9px 0 0 -9px; background: url(' + src + ') 0 -' + offset + 'px"></div>' }
+        );
+    }
+    
+    function sliderPin () {
+        return '<div style="' + uki.browser.css('position:absolute;overflow:hidden;z-index:10;left:50%;top:50%;width:2px;height:11px;margin:-6px 0 0 -1px;background:#8599AE;border-top:1px solid #6A7A8C;') + '"></div>';
+    }
+    
+    uki.theme.airport.backgrounds['slider-handle-down'] = uki.theme.airport.backgrounds['slider-handle-hover'];
+    uki.theme.airport.backgrounds['toolbar-popup'] = uki.theme.airport.backgrounds['popup-normal'];
+    uki.theme.airport.backgrounds['toolbar-popup-button-disabled'] = uki.theme.airport.backgrounds['toolbar-popup-button-normal'];
+
+    uki.theme.register(uki.theme.airport);
+})();
diff --git a/lib/uki-0.3.8.min.js b/lib/uki-0.3.8.min.js
new file mode 100644 (file)
index 0000000..aa8cd04
--- /dev/null
@@ -0,0 +1,232 @@
+(function(){var t=this,r=document,A=navigator.userAgent,B="uki"+ +new Date,q=Math.max,w=Math.min,H=Math.floor,F=Math.ceil;t.uki=t.uki||function(b,a){if(typeof b==="string"){var c=b.match(/^#((?:[\w\u00c0-\uFFFF_-]|\\.)+)$/),e=c&&uki._ids[c[1]];if(c&&!a)return new uki.Collection(e?[e]:[]);return uki.find(b,a)}if(b.length===undefined)b=[b];if(b.length>0&&uki.isFunction(b[0].typeName))return new uki.Collection(b);return uki.build(b)};uki.version="0.3.8";uki.guid=1;uki.F=function(){return false};uki._ids=
+{};uki.registerId=function(b){uki._ids[uki.attr(b,"id")]=b};uki.unregisterId=function(b){uki._ids[uki.attr(b,"id")]=undefined};(function(){function b(){}var a=Object.prototype.toString,c=String.prototype.trim,e=Array.prototype.slice,g=/^\s+|\s+$/g,d={attr:function(f,h,j){if(j!==undefined){if(f[h]&&f[h].apply)f[h](j);else f[h]=j;return f}else return f[h]&&f[h].apply?f[h]():f[h]},proxy:function(f,h){var j=e.call(arguments,2),k=function(){return f.apply(h,j.concat(e.call(arguments,0)))};k.huid=f.huid=
+f.huid||uki.guid++;return k},isFunction:function(f){return a.call(f)==="[object Function]"},isArray:function(f){return a.call(f)==="[object Array]"},trim:function(f){f=f||"";return c?c.call(f):f.replace(g,"")},escapeHTML:function(f){var h={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"};return(f+"").replace(/[&<>\"\']/g,function(j){return h[j]})},each:function(f,h,j){var k,n=0,m=f.length;if(m===undefined)for(k in f){if(!(!k||f[k]===undefined||!f.hasOwnProperty(k)))if(h.call(j||f[k],k,
+f[k])===false)break}else for(k=f[0];n<m&&h.call(j||k,n,k)!==false;k=f[++n]);return f},inArray:function(f,h){for(var j=0,k=h.length;j<k;j++)if(h[j]===f)return j;return-1},unique:function(f){if(f.length&&(typeof f[0]=="object"||typeof f[0]=="function")){var h=[],j;for(j=0;j<f.length;j++){f[j].__uki_marked||(h[h.length]=f[j]);f[j].__uki_marked=true}for(j=0;j<h.length;j++)delete h[j].__uki_marked;return h}else{h=[];var k={};j=0;for(var n=f.length;j<n;j++){var m=f[j];if(!k[m]){k[m]=true;h.push(f[j])}}return h}},
+grep:function(f,h){for(var j=[],k=0,n=f.length;k<n;k++)h(f[k],k)&&j.push(f[k]);return j},map:function(f,h,j){for(var k=[],n=uki.isFunction(h)?h:function(o){return uki.attr(o,h)},m=0,p=f.length;m<p;m++){var l=n.call(j||f[m],f[m],m);if(l!=null)k[k.length]=l}return k},reduce:function(f,h,j,k){for(var n=0,m=h.length;n<m;n++)f=j.call(k||h[n],f,h[n],n);return f},extend:function(){for(var f=arguments[0]||{},h=1,j=arguments.length,k;h<j;h++)if((k=arguments[h])!=null)for(var n in k){var m=k[n];if(m!==undefined)f[n]=
+m}return f},newClass:function(){var f=function(){this.init.apply(this,arguments)},h;h=0;var j,k=[],n,m,p=arguments,l;if((l=p.length)>1){n=p[0];if(n.prototype){b.prototype=n.prototype;f.prototype=new b;h=1;k=[b.prototype];for(j in n){m=n[j];!n.hasOwnProperty(j)||m===undefined||j=="prototype"||(f[j]=m)}}}for(h=h;h<l;h++){n=p[h];if(this.isFunction(n)){j={};n.apply(j,k);n=j}k[k.length]=n;uki.extend(f.prototype,n)}if(!f.prototype.init)f.prototype.init=function(){};return f},binarySearch:function(f,h){for(var j=
+0,k=h.length,n;j<k;){n=j+k>>1;h[n]<f?(j=n+1):(k=n)}return j},newProp:function(f,h){return function(j){if(j===undefined)return this[f];if(h)h.call(this,j);else this[f]=j;return this}},addProps:function(f,h){for(var j=0,k=h.length;j<k;j++)f[h[j]]=uki.newProp("_"+h[j])},toArray:function(f){return e.call(f,0)},delegateProp:function(f,h,j){var k="_"+h;f[h]=function(n){if(n===undefined){if(this[j])return uki.attr(this[j],h,n);return this[k]}if(this[j])uki.attr(this[j],h,n);else this[k]=n;return this}},
+camalize:function(f){return f.replace(/[-_]\S/g,function(h){return h.substr(1).toUpperCase()})},dasherize:function(f){return f.replace(/[A-Z]/g,function(h){return"-"+h.toLowerCase()})}};d.extend(uki,d)})();uki.geometry={};var z=uki.geometry.Point=function(b,a){this.x=b*1||0;this.y=a*1||0};z.prototype={toString:function(){return this.x+" "+this.y},clone:function(){return new z(this.x,this.y)},eq:function(b){return this.x==b.x&&this.y==b.y},offset:function(b,a){if(typeof b=="object"){a=b.y;b=b.x}this.x+=
+b;this.y+=a;return this},constructor:z};z.fromString=function(b){b=b.split(/\s+/);return new z(b[0],b[1])};var u=uki.geometry.Size=function(b,a){this.width=b*1||0;this.height=a*1||0};u.prototype={toString:function(){return this.width+" "+this.height},clone:function(){return new u(this.width,this.height)},eq:function(b){return this.width==b.width&&this.height==b.height},empty:function(){return this.width<=0||this.height<=0},constructor:u};u.fromString=function(b){b=b.split(/\s+/);return new u(b[0],
+b[1])};u.create=function(b,a){if(b===undefined)return null;if(b.width!==undefined)return b;if(/\S+\s+\S+/.test(b+""))return u.fromString(b,a);return new u(b,a)};var s=uki.geometry.Rect=function(b,a,c,e){if(c!==undefined){this.x=b*1||0;this.y=a*1||0;this.width=c*1||0;this.height=e*1||0}else if(b===undefined||b.x===undefined){this.y=this.x=0;this.width=b*1||0;this.height=a*1||0}else{this.x=b?b.x*1:0;this.y=b?b.y*1:0;this.width=a?a.width*1:0;this.height=a?a.height*1:0}};s.prototype={toString:function(){return[this.x,
+this.y,this.width,this.height].join(" ")},toCoordsString:function(){return[this.x,this.y,this.maxX(),this.maxY()].join(" ")},clone:function(){return new s(this.x,this.y,this.width,this.height)},minX:function(){return this.x},maxX:function(){return this.x+this.width},midX:function(){return this.x+this.width/2},minY:function(){return this.y},midY:function(){return this.y+this.height/2},maxY:function(){return this.y+this.height},normalize:function(){this.x=this.y=0;return this},empty:u.prototype.empty,
+eq:function(b){return b&&this.x==b.x&&this.y==b.y&&this.height==b.height&&this.width==b.width},inset:function(b,a){this.x+=b;this.y+=a;this.width-=b*2;this.height-=a*2;return this},offset:z.prototype.offset,intersection:function(b){var a=new z(q(this.x,b.x),q(this.y,b.y));b=new u(w(this.maxX(),b.maxX())-a.x,w(this.maxY(),b.maxY())-a.y);return b.empty()?new s:new s(a,b)},union:function(b){return s.fromCoords(w(this.x,b.x),w(this.y,b.y),q(this.maxX(),b.maxX()),q(this.maxY(),b.maxY()))},containsPoint:function(b){return b.x>=
+this.minX()&&b.x<=this.maxX()&&b.y>=this.minY()&&b.y<=this.maxY()},containsRect:function(b){return this.eq(this.union(b))},constructor:s};s.prototype.left=s.prototype.minX;s.prototype.top=s.prototype.minY;s.fromCoords=function(b,a,c,e){if(c===undefined)return new s(b.x,b.y,a.x-b.x,a.y-b.y);return new s(b,a,c-b,e-a)};s.fromCoordsString=function(b){b=b.split(/\s+/);return s.fromCoords(b[0],b[1],b[2],b[3])};s.fromString=function(b){b=b.split(/\s+/);if(b.length>2)return new s(b[0],b[1],b[2],b[3]);return new s(b[0],
+b[1])};s.create=function(b,a,c,e){if(b===undefined)return null;if(b.x!==undefined)return b;if(/\S+\s+\S+/.test(b+""))return s.fromString(b,a);if(c===undefined)return new s(b,a);return new s(b,a,c,e)};var x=uki.geometry.Inset=function(b,a,c,e){this.top=b*1||0;this.right=a*1||0;this.bottom=c===undefined?this.top*1:c*1;this.left=e===undefined?this.right*1:e*1};x.prototype={toString:function(){return[this.top,this.right,this.bottom,this.left].join(" ")},clone:function(){return new x(this.top,this.right,
+this.bottom,this.left)},width:function(){return this.left+this.right},height:function(){return this.top+this.bottom},negative:function(){return this.top<0||this.left<0||this.right<0||this.bottom<0},empty:function(){return!this.top&&!this.left&&!this.right&&!this.bottom}};x.fromString=function(b){b=b.split(/\s+/);if(b.length<3)b[2]=b[0];if(b.length<4)b[3]=b[1];return new x(b[0],b[1],b[2],b[3])};x.create=function(b,a,c,e){if(b===undefined)return null;if(b.top!==undefined)return b;if(/\S+\s+\S+/.test(b+
+""))return x.fromString(b,a);if(c===undefined)return new x(b,a);return new x(b,a,c,e)};uki.dom={createElement:function(b,a,c){b=r.createElement(b);if(a)b.style.cssText=a;if(c)b.innerHTML=c;b[B]=uki.guid++;return b},probe:function(b,a){var c=r.body;c.appendChild(b);a=a(b);c.removeChild(b);return a},layout:function(b,a,c){c=c||{};if(c.left!=a.left)b.left=a.left+"px";if(c.top!=a.top)b.top=a.top+"px";if(c.right!=a.right)b.right=a.right+"px";if(c.bottom!=a.bottom)b.bottom=a.bottom+"px";if(c.width!=a.width)b.width=
+q(a.width,0)+"px";if(c.height!=a.height)b.height=q(a.height,0)+"px";return a},computedStyle:function(b){if(r&&r.defaultView&&r.defaultView.getComputedStyle)return r.defaultView.getComputedStyle(b,null);else if(b.currentStyle)return b.currentStyle},contains:function(b,a){try{if(b.contains)return b.contains(a);if(b.compareDocumentPosition)return!!(b.compareDocumentPosition(a)&16)}catch(c){}for(;a&&a!=b;)try{a=a.parentNode}catch(e){a=null}return b==a},createStylesheet:function(b){var a=r.createElement("style");
+r.getElementsByTagName("head")[0].appendChild(a);if(a.styleSheet)a.styleSheet.cssText=b;else a.appendChild(document.createTextNode(b));return a}};uki.each(["createElement"],function(b,a){uki[a]=uki.dom[a]});uki.dom.special={};uki.dom.Event=function(b){b=b||{};this.domEvent=b.domEvent||b;for(var a=uki.dom.props.length,c;a;){c=uki.dom.props[--a];this[c]=b[c]}};uki.dom.Event.prototype=new (function(){function b(){return true}this.preventDefault=function(){var a=this.domEvent;a.preventDefault&&a.preventDefault();
+a.returnValue=false;this.isDefaultPrevented=b};this.stopPropagation=function(){var a=this.domEvent;a.stopPropagation&&a.stopPropagation();a.cancelBubble=true;this.isPropagationStopped=b};this.isDefaultPrevented=this.isPropagationStopped=uki.F});uki.extend(uki.dom,{bound:{},handlers:{},props:"type altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which dragOffset dataTransfer".split(" "),
+events:"blur focus load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error draggesturestart draggestureend draggesture dragstart dragend drag drop dragenter dragleave dragover".split(" "),bind:function(b,a,c){if(b.setInterval&&b!=window)b=window;c.huid=c.huid||uki.guid++;var e=b[B]=b[B]||uki.guid++,g=uki.dom.handlers[e]=uki.dom.handlers[e]||function(){uki.dom.handler.apply(arguments.callee.elem,
+arguments)},d,f;g.elem=b;uki.dom.bound[e]||(uki.dom.bound[e]={});a=a.split(" ");for(d=0;d<a.length;d++){f=a[d];if(!uki.dom.bound[e][f]){uki.dom.bound[e][f]=[];if(!uki.dom.special[f]||uki.dom.special[f].setup.call(b)===false)b.addEventListener?b.addEventListener(f,g,false):b.attachEvent("on"+f,g)}uki.dom.bound[e][f].push(c)}c=g=b=null},unbind:function(b,a,c){var e=b[B],g=c&&c.huid,d,f;if(a)a=a.split(" ");else{a=[];uki.each(uki.dom.bound[e]||[],function(j){a.push(j)})}for(d=0;d<a.length;d++){f=a[d];
+if(!(!e||!uki.dom.bound[e]||!uki.dom.bound[e][f])){uki.dom.bound[e][f]=c?uki.grep(uki.dom.bound[e][f],function(j){return j.huid!==g}):[];if(uki.dom.bound[e][f].length==0){var h=uki.dom.handlers[e];if(!uki.dom.special[f]||uki.dom.special[f].teardown.call(b)===false)b.removeEventListener?b.removeEventListener(f,h,false):b.detachEvent("on"+f,h);uki.dom.bound[e][f]=null}}}},handler:function(b){b=b||t.event;var a=b.type,c=this[B],e=uki.dom.bound[c];if(!b.domEvent){b=new uki.dom.Event(b);b=uki.dom.fix(b)}if(!(!c||
+!e||!e[a])){uki.after.start();c=0;for(e=e[a];c<e.length;c++)e[c].call(this,b);uki.after.stop()}},fix:function(b){if(!b.target)b.target=b.srcElement||r;if(b.target.nodeType==3)b.target=b.target.parentNode;if(!b.relatedTarget&&b.fromElement)b.relatedTarget=b.fromElement==b.target?b.toElement:b.fromElement;if(b.pageX==null&&b.clientX!=null){var a=r.documentElement,c=r.body;b.pageX=b.clientX+(a&&a.scrollLeft||c&&c.scrollLeft||0)-(a.clientLeft||0);b.pageY=b.clientY+(a&&a.scrollTop||c&&c.scrollTop||0)-
+(a.clientTop||0)}if(!b.which&&(b.charCode||b.charCode===0?b.charCode:b.keyCode))b.which=b.charCode||b.keyCode;if(!b.metaKey&&b.ctrlKey)try{b.metaKey=b.ctrlKey}catch(e){}if(!b.which&&b.button)b.which=b.button&1?1:b.button&2?3:b.button&4?2:0;return b},preventDefaultHandler:function(b){b&&b.preventDefault();return false}});uki.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(b,a){var c=function(e){if(!uki.dom.contains(this,e.relatedTarget)){e.type=a;uki.dom.handler.apply(this,arguments)}};
+uki.dom.special[a]={setup:function(){uki.dom.bind(this,b,c)},teardown:function(){uki.dom.unbind(this,b,c)}}});t.attachEvent&&t.attachEvent("onunload",function(){uki.each(uki.dom.bound,function(b,a){uki.each(a,function(c){try{uki.dom.handlers[b].elem.detachEvent("on"+c,uki.dom.handlers[b])}catch(e){}})})});(function(){function b(k){if(!d.draggable){d.draggable=k;uki.dom.bind(r,"mousemove scroll",e);uki.dom.bind(r,j,g);uki.dom.bind(r,"selectstart mousedown",uki.dom.preventDefaultHandler)}}function a(){d.draggable=
+null;uki.dom.unbind(r,"mousemove scroll",e);uki.dom.unbind(r,j,g);uki.dom.unbind(r,"selectstart mousedown",uki.dom.preventDefaultHandler)}function c(k){k=new uki.dom.Event(k);k.type="draggesturestart";uki.dom.handler.apply(this,arguments);if(!k.isDefaultPrevented()){b(this);d.position=new z(-k.pageX,-k.pageY)}}function e(k){k=new uki.dom.Event(k);k.type="draggesture";k.dragOffset=(new z(k.pageX,k.pageY)).offset(d.position);uki.dom.handler.apply(d.draggable,arguments);k.isDefaultPrevented()&&a(d.draggable)}
+function g(k){k=new uki.dom.Event(k);k.type="draggestureend";k.dragOffset=(new z(k.pageX,k.pageY)).offset(d.position);uki.dom.handler.apply(d.draggable,arguments);a(d.draggable)}var d=uki.dom.dnd={draggable:null,nativeDnD:false,position:null};try{if(typeof r.createEvent("MouseEvent").dataTransfer=="object"||r.createEvent("DragEvent").initDragEvent)d.nativeDnD=!A.match(/Chrome\/4/)}catch(f){}var h={setup:function(){if(this.__draggesturebound)this.__draggesturebound++;else{this.__draggesturebound=1;
+uki.dom.bind(this,"mousedown",c);if(!d.nativeDnD&&typeof this.ondragstart=="object")this.ondragstart=function(){event.returnValue=false}}},teardown:function(){this.__draggesturebound--;this.__draggesturebound||uki.dom.unbind(this,"mousedown",c)}};uki.extend(uki.dom.special,{draggesturestart:h,draggestureend:h,draggesture:h});var j="mouseup "+(d.nativeDnD?" dragend":"")})();(function(){function b(l){if(uki.isFunction(l.dom)){if(l.parent().length)return l.dom();var o=uki.createElement("div","width:1px;height:1px;position:absolute;left:-999em;top:0");
+r.body.appendChild(o);l.attachTo(o);return o}return l}function a(l){l=new uki.dom.Event(l);var o=l.dataTransfer;l.dataTransfer=new uki.dom.DataTransferWrapper(o);uki.dom.handler.apply(this,arguments);o.effectAllowed=l.dataTransfer.effectAllowed;o.dropEffect=l.dataTransfer.dropEffect}function c(l){uki.dom.bind(l,"draggestureend",h)}function e(l){if(j.dataTransfer){j.dataTransfer.cleanup();j.dragOver=j.dataTransfer=j.target=null;uki.dom.unbind(l,"draggestureend",h)}}function g(l){if(!(!j.dataTransfer||
+l.domEvent.__dragEntered||!k)){l=new uki.dom.Event(l);l.domEvent.__dragEntered=true;if(j.dragOver!=this){j.dragOver=this;l.type="dragenter";uki.dom.handler.apply(this,arguments)}}}function d(l){if(k){if(!l.domEvent.__dragEntered&&j.dragOver){l=new uki.dom.Event(l);l.type="dragleave";uki.dom.handler.apply(j.dragOver,arguments);j.dragOver=null}}else{if(j.dataTransfer){l.type="drag";l.target=j.target}else if(l.dragOffset.x>j.dragDelta||l.dragOffset.y>j.dragDelta){var o=l.target,y=this.parentNode;try{for(;o&&
+o!=y&&!o.getAttribute("draggable");)o=o.parentNode}catch(v){o=null}if(o&&o.getAttribute("draggable")){j.target=l.target=o;l.type="dragstart";j.dataTransfer=l.dataTransfer=new uki.dom.DataTransfer(l.domEvent.dataTransfer);c(this)}else return}else return;l=new uki.dom.Event(l);uki.dom.handler.apply(this,arguments);l.isDefaultPrevented()?e(this):f(l)}}function f(l){var o=j.dataTransfer._dragImage.style,y=l.domEvent.type;l.stopPropagation();l.preventDefault();o.left="-999em";o=r.elementFromPoint(l.pageX,
+l.pageY);j.dataTransfer.update(l);try{var v;k=true;try{if(r.createEventObject){v=r.createEventObject();for(var G=p.length,E;G;){E=uki.dom.props[--G];v[E]=l.domEvent[E]}o.fireEvent("on"+y,v)}else{v=r.createEvent("MouseEvents");v.initMouseEvent(y,true,true,r.defaultView,l.detail,l.screenX,l.screenY,l.clientX,l.clientY,l.ctrlKey,l.altKey,l.shiftKey,l.metaKey,l.button,null);o.dispatchEvent(v)}}catch(I){}k=false}catch(J){}}function h(l){if(!k)if(j.dataTransfer){l.type="dragend";l.target=j.target;l.dataTransfer=
+j.dataTransfer;uki.dom.handler.apply(this,arguments);f(l);e(this)}}var j=uki.dom.dnd,k=false;uki.extend(j,{dragDelta:5,initNativeDnD:function(){var l=uki.createElement("div","position: absolute;left:-999em;");r.body.appendChild(l);j.dragImageContainer=l;j.initNativeDnD=uki.F;return true},dragImageContainer:null,dataTransfer:null,target:null,dragOver:null});var n=["dropEffect","effectAllowed","types","files"];uki.dom.DataTransferWrapper=uki.newClass(new (function(){this.init=function(l){this.dataTransfer=
+l;for(var o=n.length-1;o>=0;o--)this[n[o]]=l[n[o]]};this.setData=function(l,o){return this.dataTransfer.setData(l,o)};this.clearData=function(l){return this.dataTransfer.clearData(l)};this.getData=function(l){return this.dataTransfer.getData(l)};this.setDragImage=function(l,o,y){j.initNativeDnD();l=b(l);var v=l.cloneNode(true);l=v.style;l.left=l.right=l.top=l.bottom="";l.position="static";j.dragImageContainer.appendChild(v);setTimeout(function(){j.dragImageContainer.removeChild(v)},1);return this.dataTransfer.setDragImage(v,
+o,y)}}));uki.dom.DataTransfer=uki.newClass(new (function(){this.init=function(){uki.extend(this,{dropEffect:"none",effectAllowed:"none",types:[],files:[],dragImage:new Image,imagePosition:new z,data:{}})};this.setData=function(l,o){this.data[l]=o;uki.inArray(l,this.types)==-1&&this.types.push(l)};this.clearData=function(l){if(l){delete this.data[l];this.types=uki.grep(this.types,function(o){return o!=l})}else{this.data={};this.types=[]}};this.getData=function(l){return this.data[l]};this.setDragImage=
+function(l,o,y){this._dragImage=this._initDragImage(l);this._imagePosition=new z(o||0,y||0)};this.update=function(l){if(this._dragImage){this._dragImage.style.left=l.pageX-this._imagePosition.x+"px";this._dragImage.style.top=l.pageY-this._imagePosition.y+"px"}};this.cleanup=function(){this._dragImage&&this._dragImage.parentNode.removeChild(this._dragImage);this._dragImage=undefined};this._initDragImage=function(l){l=b(l);l=l.cloneNode(true);var o=l.style;o.left=o.right=o.top=o.bottom="";o.position=
+"absolute";o.left="-999em";o.zIndex="9999";r.body.appendChild(l);return l}}));var m={setup:function(){if(this.__w3cdragbound)this.__w3cdragbound++;else{this.__w3cdragbound=1;uki.dom.bind(this,"draggesture",d)}},teardown:function(){this.__w3cdragbound--;this.__draggesturebound||uki.dom.unbind(this,"draggesture",d)}};if(j.nativeDnD)uki.extend(uki.dom.special,{dragstart:{setup:function(){this.addEventListener("dragstart",a,false)},teardown:function(){this.removeEventListener("dragstart",a,false)}}});
+else{uki.extend(uki.dom.special,{dragstart:m,drag:m,dragend:m});uki.each({dragover:"mousemove",drop:"mouseup"},function(l,o){var y=function(v){if(j.dataTransfer&&k){v=new uki.dom.Event(v);v.type=l;v.dataTransfer=j.dataTransfer;if(l=="dragover")j.__canDrop=false;else{e(this);if(!j.__canDrop)return}uki.dom.handler.apply(this,arguments);if(v.isDefaultPrevented())j.__canDrop=true}};uki.dom.special[l]={setup:function(){uki.dom.bind(this,o,y)},teardown:function(){uki.dom.unbind(this,o,y)}}});uki.dom.special.dragenter=
+{setup:function(){uki.dom.bind(this,"mousemove",g)},teardown:function(){uki.dom.unbind(this,"mousemove",g)}};uki.dom.special.dragleave={setup:function(){},teardown:function(){}}}var p="detail screenX screenY clientX clientY ctrlKey altKey shiftKey metaKey button".split(" ")})();(function(){var b;b=r.documentElement.getBoundingClientRect?(uki.dom.offset=function(a){if(!a||a==t)return new z;if(a===a.ownerDocument.body)return b.bodyOffset(a);b.boxModel===undefined&&b.initializeBoxModel();var c=a.getBoundingClientRect(),
+e=a.ownerDocument;a=e.body;e=e.documentElement;return new z(c.left+(b.pageXOffset||b.boxModel&&e.scrollLeft||a.scrollLeft)-(e.clientLeft||a.clientLeft||0),c.top+(b.pageYOffset||b.boxModel&&e.scrollTop||a.scrollTop)-(e.clientTop||a.clientTop||0))}):(uki.dom.offset=function(a){if(!a||a==t)return new z;if(a===a.ownerDocument.body)return b.bodyOffset(a);b.initialized||b.initialize();var c=a.offsetParent,e=a.ownerDocument,g,d=e.documentElement,f=e.body;e=e.defaultView;g=e.getComputedStyle(a,null);for(var h=
+a.offsetTop,j=a.offsetLeft;(a=a.parentNode)&&a!==f&&a!==d;){g=e.getComputedStyle(a,null);h-=a.scrollTop;j-=a.scrollLeft;if(a===c){h+=a.offsetTop;j+=a.offsetLeft;if(b.doesNotAddBorder&&!(b.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(a.tagName))){h+=parseInt(g.borderTopWidth,10)||0;j+=parseInt(g.borderLeftWidth,10)||0}c=a.offsetParent}if(b.subtractsBorderForOverflowNotVisible&&g.overflow!=="visible"){h+=parseInt(g.borderTopWidth,10)||0;j+=parseInt(g.borderLeftWidth,10)||0}g=g}if(g.position===
+"relative"||g.position==="static"){h+=f.offsetTop;j+=f.offsetLeft}if(g.position==="fixed"){h+=q(d.scrollTop,f.scrollTop);j+=q(d.scrollLeft,f.scrollLeft)}return new z(j,h)});uki.extend(b,{initialize:function(){if(!this.initialized){var a=r.body,c=r.createElement("div"),e,g,d,f=a.style.marginTop;e={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(g in e)c.style[g]=e[g];c.innerHTML='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
+a.insertBefore(c,a.firstChild);e=c.firstChild;g=e.firstChild;d=e.nextSibling.firstChild.firstChild;this.doesNotAddBorder=g.offsetTop!==5;this.doesAddBorderForTableAndCells=d.offsetTop===5;e.style.overflow="hidden";e.style.position="relative";this.subtractsBorderForOverflowNotVisible=g.offsetTop===-5;a.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=a.offsetTop===0;a.style.marginTop=f;a.removeChild(c);this.boxModel===undefined&&this.initializeBoxModel();this.initialized=true}},initializeBoxModel:function(){if(this.boxModel===
+undefined){var a=r.createElement("div");a.style.width=a.style.paddingLeft="1px";r.body.appendChild(a);this.boxModel=a.offsetWidth===2;r.body.removeChild(a).style.display="none"}},bodyOffset:function(a){b.initialized||b.initialize();var c=a.offsetTop,e=a.offsetLeft;if(uki.dom.doesNotIncludeMarginInBodyOffset){c+=parseInt(uki.dom.elem.currentStyle(a).marginTop,10)||0;e+=parseInt(uki.dom.elem.currentStyle(a).marginLeft,10)||0}return new z(e,c)}})})();uki.browser=new (function(){function b(k,n,m){var p=
+k[n];k[n]=undefined;k[m]=p}function a(k){for(var n=uki.createElement("div").style,m=["","-webkit-","-moz-"],p=0;p<m.length;p++)if(n[uki.camalize(m[p]+k)]!==undefined)return m[p]+k;return"unsupported"}function c(){var k=uki.createElement("div","background-image:-moz-linear-gradient(right,red,red);background-image:linear-gradient(right,red,red);background-image:-webkit-gradient(linear,0 0,100% 0,from(red),to(red))").style.backgroundImage+"";return k.indexOf("-moz-linear-gradient")>-1?"-moz-linear-gradient":
+k.indexOf("-webkit-gradient")>-1?"-webkit-gradient":k.indexOf("linear-gradient")>-1?"linear-gradient":"unsupported"}var e;this.cssBoxShadow=function(){return e=e||(t.opera?"unsupported":a("box-shadow"))};var g;this.cssBorderRadius=function(){return g=g||a("border-radius")};var d;this.cssUserSelect=function(){return d=d||a("user-select")};var f;this.cssLinearGradient=function(){return f=f||c()};var h;this.canvas=function(){if(h===undefined)h=!!uki.createElement("canvas").getContext;return h};var j;
+this.cssFilter=function(){if(j===undefined)j=typeof uki.createElement("div").style.filter!="undefined";return j};this.css=function(k){if(!k)return"";if(typeof k=="string")return k.replace(/(^|[^-])(box-shadow|border-radius|user-select)/g,function(n){var m;if((m=n.indexOf("box-shadow"))>-1)return n.substr(0,m)+uki.browser.cssBoxShadow();if((m=n.indexOf("border-radius"))>-1)return n.substr(0,m)+uki.browser.cssBorderRadius();if((m=n.indexOf("user-select"))>-1)return n.substr(0,m)+uki.browser.cssUserSelect()});
+uki.each(["boxShadow","borderRadius","userSelect"],function(n,m){k[m]&&b(k,m,uki.camalize(uki.browser[uki.camalize("css-"+m)]()))});return k};this.textStyles="font fontFamily fontWeight fontSize textDecoration textOverflow textAlign textShadow overflow color".split(" ")});uki.initNativeLayout=function(){uki.supportNativeLayout===undefined&&uki.dom.probe(uki.createElement("div","position:absolute;width:100px;height:100px;left:-999em;",'<div style="position:absolute;left:0;right:0"></div>'),function(b){uki.supportNativeLayout=
+b.childNodes[0].offsetWidth==100&&!t.opera})};uki.after=function(){var b=function(a){a.huid=a.huid||uki.guid++;if(!b._bound[a.huid]){b._bound[a.huid]=true;b._queue.push(a);b._running||b._startTimer()}};b._bound={};b._running=false;b._timer=0;b._queue=[];b.start=function(){b._clearTimer();b._running++};b.stop=function(){--b._running||b._runCallbacks()};b._runCallbacks=function(){b._clearTimer();var a=b._queue;b._queue=[];b._bound={};for(var c=0;c<a.length;c++)a[c]()};b._startTimer=function(){if(!b._timer)b._timer=
+setTimeout(b._runCallbacks,1)};b._clearTimer=function(){if(b._timer){clearTimeout(b._timer);b._timer=0}};return b}();uki.view={declare:function(){var b=uki.toArray(arguments),a=b.shift();b=uki.newClass.apply(uki,b);var c=a.split("."),e=t,g,d,f=c.length-1;b.prototype.typeName=function(){return a};for(g=0;g<f;g++){d=c[g];e[d]||(e[d]={});e=e[d]}return e[c[f]]=b}};uki.view.Observable={bind:function(b,a){a.huid=a.huid||uki.guid++;uki.each(b.split(" "),function(c,e){this._bound(e)||this._bindToDom(e);this._observersFor(e).push(a)},
+this);return this},unbind:function(b,a){if(this._observers){var c;if(b)c=b.split(" ");else{c=[];uki.each(this._observers,function(e){c.push(e)})}uki.each(c,function(e,g){this._observers[g]=!a?[]:uki.grep(this._observersFor(g,true),function(d){return d!=a&&d.huid!=a.huid});if(this._observers[g].length==0){this._observers[g]=undefined;this._unbindFromDom(g)}},this);return this}},trigger:function(b){var a=Array.prototype.slice.call(arguments,1);uki.each(this._observersFor(b,true),function(c,e){e.apply(this,
+a)},this);return this},_unbindFromDom:function(b){this._domHander&&this._eventTargets[b]&&uki.dom.unbind(this._eventTargets[b],b,this._domHander)},_bindToDom:function(b,a){if(!a&&!this.dom)return false;this._domHander=this._domHander||uki.proxy(function(c){c.source=this;this.trigger(c.type,c)},this);this._eventTargets=this._eventTargets||{};this._eventTargets[b]=a||this.dom();uki.dom.bind(this._eventTargets[b],b,this._domHander);return true},_bound:function(b){return this._observers&&this._observers[b]},
+_observersFor:function(b,a){if(a&&(!this._observers||!this._observers[b]))return[];if(!this._observers)this._observers={};this._observers[b]||(this._observers[b]=[]);return this._observers[b]}};(function(){function b(){return r.compatMode=="CSS1Compat"&&r.documentElement||r.body}var a=uki.Attachment=uki.newClass(uki.view.Observable,{init:function(c,e,g){uki.initNativeLayout();this._dom=c=c||t;this._view=e;this._rect=s.create(g)||this.rect();uki.dom.offset.initialize();e.parent(this);this.domForChild().appendChild(e.dom());
+if(c!=t&&c.tagName!="BODY"){e=c.runtimeStyle||c.ownerDocument.defaultView.getComputedStyle(c,null);if(!e.position||e.position=="static")c.style.position="relative"}a.register(this);this.layout()},domForChild:function(){return this._dom===t?r.body:this._dom},rectForChild:function(){return this.rect()},scroll:function(){},scrollTop:function(){return this._dom.scrollTop||0},scrollLeft:function(){return this._dom.scrollLeft||0},parent:uki.F,childResized:uki.F,layout:function(){var c=this._rect,e=this._rect=
+this.rect();this._view.parentResized(c,e);this._view._needsLayout&&this._view.layout();this.trigger("layout",{source:this,rect:e})},dom:function(){return this._dom},view:function(){return this._view},rect:function(){var c=this._dom===t||this._dom===r.body?q(b().clientWidth,this._dom.offsetWidth||0):this._dom.offsetWidth,e=this._dom===t||this._dom===r.body?q(b().clientHeight,this._dom.offsetHeight||0):this._dom.offsetHeight;return new s(c,e)}});a.instances=[];a.register=function(c){if(a.instances.length==
+0){var e=false;uki.dom.bind(t,"resize",function(){if(!e){e=true;setTimeout(function(g,d){uki.after.start();e=false;g=0;for(d=a.instances.length;g<d;g++)a.instances[g].layout();uki.after.stop()},1)}})}a.instances.push(c)};a.childViews=function(){return uki.map(a.instances,"view")};uki.top=function(){return[a]}})();uki.Collection=function(b){this.length=0;Array.prototype.push.apply(this,b)};uki.fn=uki.Collection.prototype=new (function(){var b=this;this.each=function(a){return uki.each(this,a)};this.grep=
+function(a){return new uki.Collection(uki.grep(this,a))};this.attr=function(a,c){if(c!==undefined){for(var e=this.length-1;e>=0;e--)uki.attr(this[e],a,c);return this}else return this[0]?uki.attr(this[0],a):""};this.find=function(a){return uki.find(a,this)};this.attachTo=function(a,c){this.each(function(){new uki.Attachment(a,this,c)});return this};this.append=function(a){var c=this[0];if(!c)return this;a=a.length!==undefined?a:[a];for(var e=a.length-1;e>=0;e--)c.appendChild(a[e]);return this};this.appendTo=
+function(a){a=uki(a)[0];this.each(function(){a.appendChild(this)});return this};uki.Collection.addAttrs=function(a){uki.each(a,function(c,e){b[e]=function(g){return this.attr(e,g)}})};uki.Collection.addAttrs("dom html text background value rect checked anchors childViews typeName id name visible disabled focusable style draggable textSelectable width height minX maxX minY maxY left top x y contentsSize".split(" "));uki.each([["parent","parent"],["next","nextView"],["prev","prevView"]],function(a,
+c){b[c[0]]=function(){return new uki.Collection(uki.unique(uki.map(this,c[1])))}});uki.each("bind unbind trigger layout appendChild removeChild insertBefore addRow removeRow resizeToContents toggle".split(" "),function(a,c){b[c]=function(){for(var e=this.length-1;e>=0;e--)this[e][c].apply(this[e],arguments);return this}});uki.each(uki.dom.events,function(a,c){b[c]=function(e){if(e)this.bind(c,e);else for(e=this.length-1;e>=0;e--)this[e][c]?this[e][c]():this[e].trigger(c);return this}})});(function(){function b(e){return uki.map(e,
+function(g){return a(g)})}function a(e){if(uki.isFunction(e.typeName))return e;var g=e.view||e.type,d;if(uki.isFunction(g))d=new g(e.rect);else if(typeof g==="string"){for(var f=0,h=uki.viewNamespaces,j=h.length;f<j;f++){for(var k=(h[f]+g).split("."),n=t,m=0,p=k.length;n&&m<p;m++)n=n[k[m]];if(n){d=new n(e.rect);break}}if(!n)throw"No view of type "+g+" found";}else d=g;c(d,e);return d}function c(e,g){uki.each(g,function(d,f){d=="view"||d=="type"||d=="rect"||uki.attr(e,d,f)});return e}uki.build=function(e){return new uki.Collection(b(e.length===
+undefined?[e]:e))};uki.viewNamespaces=["uki.view.",""];uki.build.copyAttrs=c})();(function(){function b(m){return m.parent().childViews().slice((m._viewIndex||0)+1)}function a(m){return c(uki.map(m,function(p){return[p].concat(a(d(p,"childViews")))}))}function c(m){return uki.reduce([],m,e)}function e(m,p){return m.concat(p)}var g,d=uki.attr,f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,h=[{name:"ID",regexp:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/},
+{name:"ATTR",regexp:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/},{name:"TYPE",regexp:/^((?:[\w\u00c0-\uFFFF\*_\.-]|\\.)+)/},{name:"POS",regexp:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/}],j={first:function(m){return m===0},last:function(m,p,l){return m===l.length-1},even:function(m){return m%2===0},odd:function(m){return m%2===1},lt:function(m,p){return m<p[2]-0},gt:function(m,p){return m>p[2]-0},nth:function(m,p){return p[2]-0==m},eq:function(m,p){return p[2]-
+0==m}},k={TYPE:function(m,p){p=p[1];if(p=="*")return true;return(m=d(m,"typeName"))&&m.length>=p.length&&("."+m).indexOf("."+p)==m.length-p.length},ATTR:function(m,p){m=d(m,p[1]);var l=m+"",o=p[2];p=p[4];return m==null?o==="!=":o==="="?l===p:o==="*="?l.indexOf(p)>=0:o==="~="?(" "+l+" ").indexOf(p)>=0:!p?l&&m!==false:o==="!="?l!=p:o==="^="?l.indexOf(p)===0:o==="$="?l.substr(l.length-p.length)===p:false},ID:function(m,p){return k.ATTR(m,["","id","=","",p[1]])},POS:function(m,p,l,o){return(m=j[p[1]])?
+m(l,p,o):false}},n={"+":function(m){return uki.unique(uki.map(m,"nextView"))},">":function(m){return uki.unique(c(uki.map(m,"childViews")))},"":function(m){return uki.unique(a(c(uki.map(m,"childViews"))))},"~":function(m){return uki.unique(c(uki.map(m,b)))}};g=uki.Selector={find:function(m,p,l){p=p||uki.top();if(p.length===undefined)p=[p];var o=g.tokenize(m);m=o[0];o=o[1];for(var y=p,v;m.length>0;){v=n[m[0]]?n[m.shift()]:n[""];y=v(y);if(m.length==0)break;y=g.reduce(m.shift(),y)}if(o)y=y.concat(g.find(o,
+p,true));return l?y:new uki.Collection(uki.unique(y))},reduce:function(m,p){if(!p||!p.length)return[];for(var l,o;m!="";){o=false;uki.each(h,function(y,v){if(l=m.match(v.regexp)){o=true;p=uki.grep(p,function(G,E){return k[v.name](G,l,E,p)});m=m.replace(v.regexp,"");return false}});if(!o)break}return p},tokenize:function(m){var p=[],l,o;for(f.lastIndex=0;(l=f.exec(m))!==null;){p.push(l[1]);if(l[2]){o=RegExp.rightContext;break}}return[p,o]}};uki.find=g.find})();uki.image=function(b,a,c){var e=new Image;
+e.src=uki.imageSrc(b,a,c);return e};uki.imageSrc=function(b,a,c){if(uki.image.dataUrlSupported&&a)return a;if(c&&uki.image.needAlphaFix)return c;return b};uki.imageHTML=function(b,a,c,e){if(uki.image.needAlphaFix&&c)b=c;else if(uki.image.dataUrlSupported)b=a;return"<img"+(e||"")+' src="'+b+'" />'};uki.image.dataUrlSupported=r.createElement("canvas").toDataURL||/MSIE (8)/.test(A);uki.image.needAlphaFix=/MSIE 6/.test(A);if(uki.image.needAlphaFix)try{r.execCommand("BackgroundImageCache",false,true)}catch(K){}(function(){var b=
+/^\s*null\s*$/,a=/theme\s*\(\s*(.*\s*)\)/,c=/rows\s*\(\s*(.*\s*)\)/,e=/cssBox\s*\(\s*(.*\s*)\)/,g=uki.background=function(d){if(typeof d==="string"){var f;if(d.match(b))return new g.Null;if(f=d.match(a))return uki.theme.background(f[1]);if(f=d.match(c))return new g.Rows(f[1].split(",")[0],f[1].split(/\s*,\s*/).slice(1));if(f=d.match(e))return new g.CssBox(f[1]);return new g.Css(d)}return d}})();uki.background.Base=uki.background.Null=uki.newClass({init:uki.F,attachTo:uki.F,detach:uki.F});uki.background.Sliced9=
+uki.newClass(new (function(){function b(d,f,h,j,k){j=h[3]?c(h,j):"";h[3]||(f+=a(h,k));return'<div class="'+d+'" style="position:absolute;overflow:hidden;'+f+'">'+j+"</div>"}function a(d,f){return";background: url("+uki.imageSrc(d[0],d[1],d[2])+") "+f}function c(d,f){return uki.imageHTML(d[0],d[1],d[2],' ondragstart="return false;" galleryimg="no" style="-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;position:absolute;'+f+'"')}var e=uki.dom,g={};this.init=function(d,f,h){this._settings=
+uki.extend({},d);this._inset=x.create(f);this._size=null;this._inited=false;h=h||{};this._fixedSize=u.create(h.fixedSize)||new u;this._bgInset=x.create(h.inset)||new x;this._zIndex=h.zIndex||-1;this._container=this._getContainer();this._container.style.zIndex=this._zIndex};this._getContainer=function(){var d=this._getKey();if(!g[d])return g[d]=this._createContainer();return g[d].cloneNode(true)};this._createContainer=function(){var d=this._inset,f=this._bgInset,h=this._settings,j=d.left+d.right,k=
+d.top+d.bottom;f=["left:"+f.left+"px","right:"+f.right+"px","top:"+f.top+"px","bottom:"+f.bottom+"px"].join(";");var n=[];if(d.top&&d.left)n[n.length]=b("tl",["left:0;top:0","width:"+d.left+"px","height:"+d.top+"px"].join(";"),h.c,["left:0;top:0","width:"+j+"px","height:"+k+"px"].join(";"),"top left");if(d.top)n[n.length]=b("t",["left:"+d.left+"px","top:0","height:"+d.top+"px","right:"+d.right+"px"].join(";"),h.h,["left:0;top:0;width:100%","height:"+k+"px"].join(";"),"repeat-x top");if(d.top&&d.right)n[n.length]=
+b("tr",["right:0;top:0","width:"+d.right+"px","height:"+d.top+"px"].join(";"),h.c,["left:-"+d.left+"px","top:0","width:"+j+"px","height:"+k+"px"].join(";"),"top right");if(d.left)n[n.length]=b("l",["left:0","top:"+d.top+"px","width:"+d.left+"px","bottom:"+d.bottom+"px"].join(";"),h.v,["left:0;top:0;height:100%","width:"+j+"px"].join(";"),"repeat-y left");if(h.m)n[n.length]=b("m",["left:"+d.left+"px","top:"+d.top+"px","right:"+d.right+"px","bottom:"+d.bottom+"px"].join(";"),h.m,"left:0;top:0;height:100%;width:100%",
+"");if(d.right)n[n.length]=b("r",["right:0","top:"+d.top+"px","width:"+d.right+"px","bottom:"+d.bottom+"px"].join(";"),h.v,["left:-"+d.left+"px","top:0;height:100%","width:"+j+"px"].join(";"),"repeat-y right");if(d.bottom&&d.left)n[n.length]=b("bl",["left:0;bottom:0","width:"+d.left+"px","height:"+d.bottom+"px"].join(";"),h.c,["left:0","top:-"+d.top+"px","width:"+j+"px","height:"+k+"px"].join(";"),"left -"+d.top+"px");if(d.bottom)n[n.length]=b("b",["left:"+d.left+"px","bottom:0","height:"+d.bottom+
+"px","right:"+d.right+"px"].join(";"),h.h,["left:0","top:-"+d.top+"px","width:100%","height:"+k+"px"].join(";"),"repeat-x 0 -"+d.top+"px");if(d.bottom&&d.right)n[n.length]=b("br",["right:0;bottom:0","width:"+d.right+"px","height:"+d.bottom+"px"].join(";"),h.c,["left:-"+d.left+"px","top:-"+d.top+"px","width:"+j+"px","height:"+k+"px"].join(";"),"right -"+d.top+"px");d=uki.createElement("div","position:absolute;overflow:hidden;"+f,n.join(""));d.className="uki-background-Sliced9";return d};this._getKey=
+function(){return uki.map(["v","h","m","c"],function(d){return this._settings[d]&&this._settings[d][0]||""},this).concat([this._inset,this._bgInset,this._fixedSize]).join(",")};this.attachTo=function(d){this._comp=d;this._container.style.visibility="visible";this._comp.dom().insertBefore(this._container,this._comp.dom().firstChild);if(!uki.supportNativeLayout){this._layoutHandler=this._layoutHandler||uki.proxy(function(f){if(!(this._size&&this._size.eq(f.rect))){this._size=f.rect;this.layout()}},
+this);this._comp.bind("layout",this._layoutHandler);this.layout()}};this.detach=function(){if(this._comp){this._container.style.visibility="hidden";uki.supportNativeLayout||this._comp.unbind("layout",this._layoutHandler);this._size=this._comp=null;this._attached=this._inited=false}};this.layout=function(){var d=this._comp.rect(),f=this._parts,h=this._inset,j=this._bgInset,k=this._fixedSize,n=H(k.width||d.width-j.left-j.right);d=H(k.height||d.height-j.top-j.bottom);j=h.left+h.right;h=h.top+h.bottom;
+if(!f){f={};uki.each(this._container.childNodes,function(){if(this.className)f[this.className]=this});this._parts=f}f.t&&e.layout(f.t.style,{width:n-j});f.b&&e.layout(f.b.style,{width:n-j});f.l&&e.layout(f.l.style,{height:d-h});f.r&&e.layout(f.r.style,{height:d-h});f.m&&e.layout(f.m.style,{height:d-h,width:n-j});e.layout(this._container.style,{width:n,height:d})}}));uki.background.Css=uki.newClass(new (function(){this.init=function(b){this._options=typeof b=="string"?{background:b}:b;this._options=
+uki.browser.css(this._options)};this.attachTo=function(b){this._comp=b;this._originalValues={};uki.each(this._options,function(a,c){this._originalValues[a]=b.style(a);b.style(a,c)},this)};this.detach=function(){this._comp&&uki.each(this._options,function(b){this._comp.style(b,this._originalValues[b])},this)}}));uki.background.CssBox=uki.newClass(new (function(){function b(c){a[c]||uki.dom.probe(uki.createElement("div",c+";position:absolute;overflow:hidden;left:-999em;width:10px;height:10px;"),function(e){a[c]=
+new x(e.offsetHeight-10,e.offsetWidth-10)});return a[c]}var a={};this.init=function(c,e){this._options=c;e=e||{};this._inset=inset=x.create(e.inset)||new x;this._insetWidth=b(c).left+inset.left+inset.right;this._insetHeight=b(c).top+inset.top+inset.bottom;this._container=uki.createElement("div","position:absolute;overflow:hidden;z-index:"+(e.zIndex||"-1")+";left:"+inset.left+"px;top:"+inset.top+"px;right:"+inset.right+"px;bottom:"+inset.bottom+"px;"+uki.browser.css(c),e.innerHTML);this._container.className=
+"uki-background-CssBox";this._attached=false};this.attachTo=function(c){this._comp=c;this._comp.dom().insertBefore(this._container,this._comp.dom().firstChild);if(!uki.supportNativeLayout){this._layoutHandler=this._layoutHandler||uki.proxy(function(e){this.layout(e.rect)},this);this._comp.bind("layout",this._layoutHandler);this.layout(this._comp.rect())}};this.layout=function(c){this._prevLayout=uki.dom.layout(this._container.style,{width:c.width-this._insetWidth,height:c.height-this._insetHeight},
+this._prevLayout)};this.detach=function(){if(this._comp){this._comp.dom().removeChild(this._container);uki.supportNativeLayout||this._comp.unbind("layout",this._layoutHandler);this._attached=false}}}));uki.background.LinearGradient=uki.newClass(uki.background.CssBox,new (function(){function b(g,d,f,h){var j=g+d+(f?1:0)+uki.map(h,function(m){return m.pos+"-"+m.color});if(!e[j]){var k=document.createElement("canvas");k.width=k.height=c;var n=k.getContext("2d");f=n.createLinearGradient(0,0,f?c:0,f?0:
+c);f.addColorStop(0,g);f.addColorStop(1,d);for(g=0;g<h.length;g++)f.addColorStop(h[g].pos,h[g].color);n.fillStyle=f;n.fillRect(0,0,c,c);e[j]=k.toDataURL&&k.toDataURL()}return e[j]}function a(g,d,f){return"filter:progid:DXImageTransform.Microsoft.gradient(gradientType="+(g?"1":"0")+", startColorstr=#FF"+d.substr(1)+", endColorstr=#FF"+f.substr(1)+");"}var c=200;this.init=function(g){this._options=g;var d=this._inset=uki.geometry.Inset.create(this._options.inset)||new uki.geometry.Inset;this._container=
+this._createContainer();g=this._container.cloneNode(true);g.style.cssText+=";width:100px;height:100px;";uki.dom.probe(g,uki.proxy(function(f){this._insetWidth=f.offsetWidth-100+d.width();this._insetHeight=f.offsetHeight-100+d.height()},this));this._container.style.cssText+=";left:"+d.left+"px;top:"+d.top+"px;right:"+d.right+"px;bottom:"+d.bottom+"px;";this._attached=false};var e={};this._createContainer=function(){var g=this._options.startColor||"#FFFFFF",d=this._options.endColor||"#CCCCCC",f=this._options.horizontal,
+h=this._options.stops||[],j="",k=0,n=uki.browser.cssLinearGradient(),m;if(n=="-moz-linear-gradient"||n=="linear-gradient"){for(j+="background-image:"+n+"("+(f?"left":"top")+", "+g;k<h.length;k++)j+=","+h[k].color+" "+h[k].pos*100+"%";j+=", "+d+");"}else if(n=="-webkit-gradient"){for(j+="background-image:"+n+"(linear, 0% 0%, "+(f?"100% 0%":"0% 100%")+", from("+g+"), to("+d+")";k<h.length;k++)j+=",color-stop("+h[k].pos*100+"%,"+h[k].color+")";j+=");"}else if(!uki.browser.canvas()&&uki.browser.cssFilter()&&
+h.length==0)j+=a(f,g,d);n=uki.createElement("div",uki.browser.css(this._options.css)+";position:absolute;overflow:hidden;z-index:"+(this._options.zIndex||"-1")+";"+j,this._options.innerHTML);n.className="uki-background-CssBox";if(j)return n;if(uki.browser.canvas()&&(m=b(g,d,f,h))){f=uki.createElement("img","position:absolute;left:0;top:0;width:100%;height:100%;z-index:0;");f.src=m;n.appendChild(f)}else if(uki.browser.cssFilter()&&h.length>0){h.unshift({pos:0,color:g});for(h.push({pos:1,color:d});k<
+h.length-1;k++){m=uki.createElement("div","position:absolute;z-index:-1;left:"+(f?h[k].pos*100-(k&&1)+"%":"0")+";top:"+(f?"0":h[k].pos*100-(k&&1)+"%")+";width:"+(f?(h[k+1].pos-h[k].pos)*100+1+"%":"100%")+";height:"+(f?"100%":(h[k+1].pos-h[k].pos)*100+1+"%")+";"+a(f,h[k].color,h[k+1].color));n.appendChild(m)}}return n}}));uki.background.Rows=uki.newClass(new (function(){function b(e,g){var d=e+" "+g.join(" "),f=[],h=[],j,k=g.length;if(!a[d]){for(j=0;j<k;j++)f[j]=['<div style="height:',e,"px;width:100%;overflow:hidden;",
+g[j]?"background:"+g[j]:"",'"></div>'].join("");for(j=0;j<c;j++)h[j]=f[j%k];a[d]=h.join("")}return a[d]}var a=[],c=100;this.init=function(e,g){this._height=e||20;this._colors=uki.isArray(g)?g:g.split(" ");this._packSize=F(c/this._colors.length)*this._colors.length;this._renderedHeight=0;this._visibleExt=200;if(this._colors.length==1)this._colors=this._colors.concat(["#FFF"])};this.attachTo=function(e){this._comp&&this.detach();this._comp=e;if(!this._container){this._container=uki.createElement("div",
+"position:absolute;left:0;top:0;width:100%;z-index:-1");this._container.className="uki-background-Rows"}this._layoutHandler=this._layoutHandler||uki.proxy(function(g){this.layout(g.rect,g.visibleRect)},this);this._comp.dom().appendChild(this._container);this._comp.bind("layout",this._layoutHandler)};this.layout=function(e,g){for(e=g?g.height+this._visibleExt*2:e.maxY();this._renderedHeight<e;){var d=c*this._height,f=uki.createElement("div","height:"+d+"px;overflow:hidden;width:100%;",b(this._height,
+this._colors));this._renderedHeight+=d;this._container.appendChild(f)}if(g)this._container.style.top=F((g.y-this._visibleExt)/this._height/this._colors.length)*this._height*this._colors.length+"px"};this.detach=function(){if(this._comp){this._comp.dom().removeChild(this._container);this._comp.unbind("layout",this._layoutHandler);this._comp=null}}}));uki.background.Multi=uki.newClass({init:function(){this._bgs=Array.prototype.slice.call(arguments,0)},attachTo:function(b){for(var a=0,c=this._bgs.length;a<
+c;a++)this._bgs[a].attachTo(b)},detach:function(){for(var b=0,a=this._bgs.length;b<a;b++)this._bgs[b].detach()}});(function(){var b=uki.theme={themes:[],register:function(a,c){(c=b.themes)[c.length]=a},background:function(a,c){return b._namedResource(a,"background",c)||new uki.background.Null},image:function(a,c){return b._namedResource(a,"image",c)||new Image},imageSrc:function(a,c){return b._namedResource(a,"imageSrc",c)||""},style:function(a,c){return b._namedResource(a,"style",c)||""},dom:function(a,
+c){return b._namedResource(a,"dom",c)||uki.createElement("div")},template:function(a,c){return b._namedResource(a,"template",c)||""},_namedResource:function(a,c,e,g,d){for(g=b.themes.length-1;g>=0;g--)if(d=b.themes[g][c](a,e))return d;return null}}})();uki.theme.Base={images:[],imageSrcs:[],backgrounds:[],doms:[],styles:[],templates:[],background:function(b,a){return this.backgrounds[b]&&this.backgrounds[b](a)},image:function(b,a){if(this.images[b])return this.images[b](a);return this.imageSrcs[b]&&
+uki.image.apply(uki,this.imageSrcs[b](a))},imageSrc:function(b,a){if(this.imageSrcs[b])return uki.imageSrc.apply(uki,this.imageSrcs[b](a));return this.images[b]&&this.images[b](a).src},dom:function(b,a){return this.doms[b]&&this.doms[b](a)},style:function(b,a){return this.styles[b]&&this.styles[b](a)},template:function(b,a){return this.templates[b]&&this.templates[b](a)}};uki.theme.Template=function(b){b=b.split("${");var a,c,e;this.parts=[b[0]];this.names=[];a=1;for(c=b.length;a<c;a++){e=b[a].split("}");
+this.names.push(e.shift());this.parts.push("");this.parts.push(e.join("}"))}};uki.theme.Template.prototype.render=function(b){for(var a=0,c=this.names,e=c.length;a<e;a++)this.parts[a*2+1]=b[c[a]]||"";return this.parts.join("")};uki.view.utils=new (function(){function b(a){return a.visibleRect?a.visibleRect():a.rect().clone()}this.visibleRect=function(a,c){var e=[],g,d=a;do{e[e.length]=d;d=d.parent()}while(d&&d!=c);if(c&&c!=a)e[e.length]=c;for(a=e.length-1;a>=0;a--){d=e[a];c=b(d);g=g?g.intersection(c):
+c;g.x-=d.rect().x;g.y-=d.rect().y}return g};this.top=function(a){for(;a.parent();)a=a.parent();return a};this.offset=function(a,c){for(var e=new z,g;a&&a!=c;){g=a.rect();e.x+=g.x;e.y+=g.y;if(a.scrollTop){e.x-=a.scrollLeft();e.y-=a.scrollTop()}a=a.parent()}return e};this.scrollableParent=function(a){do{if(uki.isFunction(a.scrollTop))return a;a=a.parent()}while(a);return null}});uki.extend(uki.view,uki.view.utils);uki.view.Styleable=new (function(){this.style=function(b,a){if(typeof b=="string")return this._style(b,
+a);uki.each(b,function(c,e){this._style(c,e)},this);return this};this._style=function(b,a){if(a===undefined)return this._dom.style[b];this._dom.style[b]=a;return this};this.textSelectable=uki.newProp("_textSelectable",function(b){this._textSelectable=b;if(uki.browser.cssUserSelect()!="unsupported")this._dom.style[uki.camalize(uki.browser.cssUserSelect())]=b?"":uki.browser.cssUserSelect()=="-moz-user-select"?"-moz-none":"none";else uki.dom[b?"unbind":"bind"](this.dom(),"selectstart",uki.dom.preventDefaultHandler);
+this._dom.style.cursor=b?"":"default"});this.draggable=function(b){if(b===undefined)return this._dom.getAttribute("draggable");this._dom.setAttribute("draggable",true);this._dom.style.WebkitUserDrag="element";return this}});uki.view.Focusable=new (function(){this._focusOnClick=this._focusable=true;this.focusOnClick=uki.newProp("_focusOnClick");this.focusable=uki.newProp("_focusable",function(b){(this._focusable=b)&&this._initFocusable();this._updateFocusable()});this.disabled=uki.newProp("_disabled",
+function(b){var a=b!==!!this._disabled;(this._disabled=b)&&this.blur();this._updateFocusable();a&&this._updateBg&&this._updateBg()});this._updateFocusable=function(){if(!(this._preCreatedFocusTarget||!this._focusTarget))this._focusTarget.style.display=this._focusable&&!this._disabled?"block":"none"};this._initFocusable=function(b){if(!(!b&&!this._focusable||this._focusTarget)){this._preCreatedFocusTarget=this._focusTarget=b;if(!b){this._focusTarget=uki.createElement("input","position:absolute;left:-9999px;top:0;width:1px;height:1px;");
+this._focusTarget.className="uki-view-Focusable";this.dom().appendChild(this._focusTarget)}this._hasFocus=false;this._firstFocus=true;uki.dom.bind(this._focusTarget,"focus",uki.proxy(function(a){this._stopWatingForBlur();this._hasFocus||this._focus(a)},this));uki.dom.bind(this._focusTarget,"blur",uki.proxy(function(){if(this._hasFocus){this._hasFocus=false;this._waitingForBlur=setTimeout(uki.proxy(function(){this._waitingForBlur=false;this._hasFocus||this._blur()},this),1)}},this));b||this.bind("mousedown",
+function(){this._focusOnClick&&this.focus()});this._updateFocusable()}};this._focus=function(){this._hasFocus=true;this._firstFocus=false};this._blur=function(){this._hasFocus=false};this._stopWatingForBlur=function(){if(this._waitingForBlur){clearTimeout(this._waitingForBlur);this._waitingForBlur=false;this._hasFocus=true}};this.focus=function(){if(this._focusable&&!this._disabled){this._stopWatingForBlur();this._hasFocus||this._focus();var b=this._focusTarget;setTimeout(function(){try{b.focus()}catch(a){}b=
+null},1)}return this};this.blur=function(){try{this._focusTarget.blur()}catch(b){}return this};this.hasFocus=function(){return this._hasFocus};this._bindToDom=function(b){if(!this._focusTarget||"keyup keydown keypress focus blur".indexOf(b)==-1)return false;return uki.view.Observable._bindToDom.call(this,b,this._focusTarget)}});var C=16,D=32;uki.view.declare("uki.view.Base",uki.view.Observable,uki.view.Styleable,function(){function b(c){if(!c)return 0;var e=0;if(c.indexOf("width")>-1)e|=C;if(c.indexOf("height")>
+-1)e|=D;return e}var a=1;this.defaultCss="position:absolute;z-index:100;-moz-user-focus:none;";this.init=function(c){this._parentRect=this._rect=s.create(c);this._setup();uki.initNativeLayout();this._createDom()};this._setup=function(){uki.extend(this,{_anchors:0,_parent:null,_visible:true,_needsLayout:true,_textSelectable:false,_styleH:"left",_styleV:"top",_firstLayout:true});this.defaultCss+=uki.theme.style("base")};this.dom=function(){return this._dom};this.id=function(c){if(c===undefined)return this._dom.id;
+this._dom.id&&uki.unregisterId(this);this._dom.id=c;uki.registerId(this);return this};uki.delegateProp(this,"className","_dom");this.visible=function(c){if(c===undefined)return this._dom.style.display!="none";this._dom.style.display=c?"block":"none";return this};this.background=function(c){if(c===undefined&&!this._background&&this.defaultBackground)this._background=this.defaultBackground();if(c===undefined)return this._background;c=uki.background(c);if(c==this._background)return this;this._background&&
+this._background.detach(this);c.attachTo(this);this._background=c;return this};this.defaultBackground=function(){return this._defaultBackground&&uki.background(this._defaultBackground)};this.parent=function(c){if(c===undefined)return this._parent;this._parent=c;return this};this.childViews=function(){return[]};this.prevView=function(){if(!this.parent())return null;return this.parent().childViews()[this._viewIndex-1]||null};this.nextView=function(){if(!this.parent())return null;return this.parent().childViews()[this._viewIndex+
+1]||null};this.rect=function(c){if(c===undefined)return this._rect;this._parentRect=c=s.create(c);this._rect=this._normalizeRect(c);this._needsLayout=this._needsLayout||a++;return this};this.anchors=uki.newProp("_anchors",function(c){if(c.indexOf){var e=0;if(c.indexOf("right")>-1)e|=2;if(c.indexOf("bottom")>-1)e|=4;if(c.indexOf("top")>-1)e|=1;if(c.indexOf("left")>-1)e|=8;if(c.indexOf("width")>-1||e&8&&e&2)e|=C;if(c.indexOf("height")>-1||e&4&&e&1)e|=D;c=e}this._anchors=c;this._styleH=c&8?"left":"right";
+this._styleV=c&1?"top":"bottom"});this.rectForChild=function(){return this.rect()};this.layout=function(){this._layoutDom(this._rect);this._needsLayout=false;this.trigger("layout",{rect:this._rect,source:this});this._firstLayout=false};this.layoutIfNeeded=function(){this._needsLayout&&this.visible()&&this.layout()};uki.each(["min","max"],function(c,e){c=e+"Size";var g="_"+c;this[c]=function(d){if(d===undefined)return this[g]||new u;this[g]=u.create(d);this.rect(this._parentRect);this._dom.style[e+
+"Width"]=this[g].width?this[g].width+"px":"";this._dom.style[e+"Height"]=this[g].height?this[g].height+"px":"";return this}},this);this.parentResized=function(c,e){var g=this._parentRect.clone(),d=(e.width-c.width)/((this._anchors&8^8?1:0)+(this._anchors&C?1:0)+(this._anchors&2^2?1:0));c=(e.height-c.height)/((this._anchors&1^1?1:0)+(this._anchors&D?1:0)+(this._anchors&4^4?1:0));if(this._anchors&8^8)g.x+=d;if(this._anchors&C)g.width+=d;if(this._anchors&1^1)g.y+=c;if(this._anchors&D)g.height+=c;this.rect(g)};
+this.childResized=function(){};this.resizeToContents=function(c){c=b(c);if(0==c)return this;this.rect();this._rect=this._parentRect=this._calcRectOnContentResize(c);this._needsLayout=true;return this};this.contentsSize=function(){return this.rect()};this._normalizeRect=function(c){if(this._minSize)c=new s(c.x,c.y,q(this._minSize.width,c.width),q(this._minSize.height,c.height));if(this._maxSize)c=new s(c.x,c.y,w(this._maxSize.width,c.width),w(this._maxSize.height,c.height));return c};this._initBackgrounds=
+function(){this.background()&&this.background().attachTo(this)};this._calcRectOnContentResize=function(c){var e=this.contentsSize(c),g=this.rect();if(e.eq(g))return g;var d=this.rect().clone(),f=e.width-g.width;e=e.height-g.height;if(c&C){if(this._anchors&8^8&&this._anchors&2^2)d.x-=f/2;else if(this._anchors&8^8)d.x-=f;d.width+=f}if(c&D){if(this._anchors&1^1&&this._anchors&4^4)d.y-=e/2;else if(this._anchors&1^1)d.y-=e;d.height+=e}return d};uki.each(["width","height","minX","maxX","minY","maxY","x",
+"y","left","top"],function(c,e){this[e]=function(g){if(g===undefined)return uki.attr(this.rect(),e);uki.attr(this.rect(),e,g);return this}},this);this._createDom=function(){this._dom=uki.createElement("div",this.defaultCss);this._initClassName()};this._initClassName=function(){this._dom.className=this.typeName().replace(/\./g,"-")};this._layoutDom=function(c){var e={},g=uki.supportNativeLayout,d=this.parent().rectForChild(this);if(g&&this._anchors&8&&this._anchors&2){e.left=c.x;e.right=d.width-c.x-
+c.width}else{e.width=c.width;e[this._styleH]=this._styleH=="left"?c.x:d.width-c.x-c.width}if(g&&this._anchors&1&&this._anchors&4){e.top=c.y;e.bottom=d.height-c.y-c.height}else{e.height=c.height;e[this._styleV]=this._styleV=="top"?c.y:d.height-c.y-c.height}this._lastLayout=uki.dom.layout(this._dom.style,e,this._lastLayout);this._firstLayout&&this._initBackgrounds();return true};this._bindToDom=function(c){if("resize layout".indexOf(c)>-1)return true;return uki.view.Observable._bindToDom.call(this,
+c)}});uki.view.declare("uki.view.Container",uki.view.Base,function(b){function a(c,e){var g=0,d;for(d=c._childViews.length-1;d>=0;d--)if(c._childViews[d].visible())g=q(g,c._childViews[d].rect()[e]());return g}this._inset=new x;this._setup=function(){this._childViews=[];b._setup.call(this)};this.contentsWidth=function(){return a(this,"maxX")+this.inset().right};this.contentsHeight=function(){return a(this,"maxY")+this.inset().bottom};this.contentsSize=function(){return new u(this.contentsWidth(),this.contentsHeight())};
+this.childViews=function(c){if(c===undefined)return this._childViews;uki.each(this._childViews,function(e,g){this.removeChild(g)},this);uki.each(uki.build(c),function(e,g){this.appendChild(g)},this);return this};this.removeChild=function(c){c.parent(null);this.domForChild(c).removeChild(c.dom());var e,g;e=c._viewIndex+1;for(g=this._childViews.length;e<g;e++)this._childViews[e]._viewIndex--;this._childViews=uki.grep(this._childViews,function(d){return d!=c});this._contentChanged()};this.appendChild=
+function(c){c._viewIndex=this._childViews.length;this._childViews.push(c);c.parent(this);this.domForChild(c).appendChild(c.dom());this._contentChanged()};this.insertBefore=function(c,e){var g,d;g=c._viewIndex=e._viewIndex;for(d=this._childViews.length;g<d;g++)this._childViews[g]._viewIndex++;this._childViews.splice(e._viewIndex-1,0,c);c.parent(this);this.domForChild(c).insertBefore(c.dom(),e.dom());this._contentChanged()};this.domForChild=function(){return this._dom};this.inset=uki.newProp("_inset",
+function(c){this._inset=x.create(c)});this._contentChanged=function(){};this._layoutDom=function(c){b._layoutDom.call(this,c);this._layoutChildViews(c)};this._layoutChildViews=function(){for(var c=0,e=this.childViews();c<e.length;c++)e[c].layoutIfNeeded()};this.rect=function(c){if(c===undefined)return this._rect;this._parentRect=c=s.create(c);var e=this._rect;if(!this._resizeSelf(c))return this;this._needsLayout=true;if(e.width!=c.width||e.height!=c.height)this._resizeChildViews(e);this.trigger("resize",
+{oldRect:e,newRect:this._rect,source:this});return this};this._resizeSelf=function(c){this._rect=this._normalizeRect(c);return true};this._resizeChildViews=function(c){for(var e=0,g=this.childViews();e<g.length;e++)g[e].parentResized(c,this._rect)}});uki.view.declare("uki.view.Box",uki.view.Container,{});uki.view.declare("uki.view.Image",uki.view.Base,function(){this.typeName=function(){return"uki.view.Image"};uki.delegateProp(this,"src","_dom");this._createDom=function(){this._dom=uki.createElement("img",
+this.defaultCss);this._initClassName()}});uki.view.declare("uki.view.Label",uki.view.Base,function(b){this._setup=function(){b._setup.call(this);uki.extend(this,{_scrollable:false,_textSelectable:false,_inset:new x});this.defaultCss+=uki.theme.style("label")};this._style=function(a,c){if(c!==undefined&&uki.inArray(a,uki.browser.textStyles)!=-1)this._label.style[a]=c;return b._style.call(this,a,c)};this.adaptToContents=uki.newProp("_adaptToContents");this.textSelectable=function(a){if(a!==undefined&&
+!this._textSelectProp)this._label.unselectable=a?"":"on";return b.textSelectable.call(this,a)};this.contentsSize=function(a){var c=this._createLabelClone(a),e=this.inset(),g;uki.dom.probe(c,function(){g=new u(c.offsetWidth+e.width(),c.offsetHeight+e.height())});return g};this.text=function(a){return a===undefined?this.html():this.html(uki.escapeHTML(a))};this.html=function(a){if(a===undefined)return this._label.innerHTML;this._label.innerHTML=a;return this};this.inset=uki.newProp("_inset",function(a){this._inset=
+x.create(a)});this.scrollable=uki.newProp("_scrollable",function(a){this._scrollable=a;this._label.style.overflow=a?"auto":"hidden"});this.multiline=uki.newProp("_multiline",function(a){this._multiline=a;this._label.style.whiteSpace=a?"":"nowrap"});this._createLabelClone=function(a){var c=this._label.cloneNode(true),e=this.inset(),g=this.rect();if(a&C)c.style.width=c.style.right="";else if(uki.supportNativeLayout){c.style.right="";c.style.width=g.width-e.width()+"px"}if(a&D)c.style.height=c.style.bottom=
+"";else if(uki.supportNativeLayout){c.style.bottom="";c.style.height=g.height-e.height()+"px"}c.style.paddingTop=0;c.style.visibility="hidden";return c};this._createDom=function(){b._createDom.call(this);this._label=uki.createElement("div",this.defaultCss+"white-space:nowrap;");this._dom.appendChild(this._label);this.textSelectable(this.textSelectable())};this._layoutDom=function(){var a=this._inset,c,e=this._anchors,g="";c=uki.supportNativeLayout?{left:a.left,top:a.top,right:a.right,bottom:a.bottom}:
+{left:a.left,top:a.top,width:this._rect.width-a.width(),height:this._rect.height-a.height()};if(e&4)if(e&1)if(e&2){if(!(e&8)){c.left=c.width=undefined;g="offsetWidth"}}else{c.right=c.width=undefined;g="offsetWidth"}else{c.height=c.bottom=undefined;g="offsetHeight"}else{c.height=c.bottom=undefined;g="offsetHeight"}b._layoutDom.apply(this,arguments);if(!this.multiline()){parseInt(this.style("fontSize"),10);this._label.style.lineHeight=this._rect.height-a.top-a.bottom+"px"}this._lastLabelLayout=uki.dom.layout(this._label.style,
+c,this._lastLabelLayout);if(this.adaptToContents()&&g){a=this._label[g];if(a!=this._lastWatchValue&&this.parent()){this.resizeToContents(g=="offsetWidth"?"width":"height");this.parent().childResized(this)}this._lastWatchValue=a}}});uki.view.declare("uki.view.Button",uki.view.Label,uki.view.Focusable,function(b,a){this._backgroundPrefix="button-";this._setup=function(){b._setup.call(this);uki.extend(this,{_inset:new x(0,4)});this.defaultCss+="cursor:default;-moz-user-select:none;-webkit-user-select:none;"+
+uki.theme.style("button")};uki.addProps(this,["backgroundPrefix"]);uki.each(["normal","hover","down","focus","disabled"],function(c,e){var g=e+"-background";this[g]=function(d){if(d)this["_"+g]=d;return this["_"+g]=this["_"+g]||uki.theme.background(this._backgroundPrefix+e,{height:this.rect().height,view:this})}},this);this._createLabelClone=function(c){return b._createLabelClone.call(this,c)};this._layoutDom=function(c){b._layoutDom.call(this,c);if(this._firstLayout){this["hover-background"]();this["down-background"]();
+this._backgroundByName(this._backgroundName||"normal")}};this._updateBg=function(){this._backgroundByName(this._disabled?"disabled":this._down?"down":this._over?"hover":"normal")};this._createDom=function(){this._dom=uki.createElement("div",this.defaultCss);this._initClassName();this._label=uki.createElement("div",this.defaultCss);this._dom.appendChild(this._label);this._dom.appendChild(uki.createElement("div","left:0;top:0;width:100%;height:100%;position:absolute;background:url("+uki.theme.imageSrc("x")+
+");"));this.textSelectable(this.textSelectable());this._initFocusable();uki.dom.bind(document,"mouseup",uki.proxy(this._mouseup,this));this.bind("mousedown",this._mousedown);this.bind("mouseenter",this._mouseenter);this.bind("mouseleave",this._mouseleave);this.bind("keyup",this._keyup);this.bind("keydown",this._keydown)};this._mouseup=function(){if(this._down){this._down=false;this._updateBg()}};this._mousedown=function(){this._down=true;this._updateBg()};this._mouseenter=function(){this._over=true;
+this._updateBg()};this._mouseleave=function(){this._over=false;this._updateBg()};this._focus=function(c){this["focus-background"]().attachTo(this);a._focus.call(this,c)};this._keydown=function(c){if((c.which==32||c.which==13)&&!this._down)this._mousedown()};this._keyup=function(c){if((c.which==32||c.which==13)&&this._down){this._mouseup();this.trigger("click",{domEvent:c,source:this})}c.which==27&&this._down&&this._mouseup()};this._blur=function(c){this["focus-background"]().detach();a._blur.call(this,
+c)};this._backgroundByName=function(c){var e=this[c+"-background"]();if(this._background!=e){this._background&&this._background.detach();e.attachTo(this);this._background=e;this._backgroundName=c}};this._bindToDom=function(c){return uki.view.Focusable._bindToDom.call(this,c)||uki.view.Label.prototype._bindToDom.call(this,c)}});uki.view.declare("uki.view.Checkbox",uki.view.Button,function(b){this._backgroundPrefix="checkbox-";uki.each(["checked-normal","checked-hover","checked-disabled"],function(a,
+c){var e=c+"-background";this[e]=function(g){if(g)this["_"+e]=g;return this["_"+e]=this["_"+e]||uki.theme.background(this._backgroundPrefix+c,{height:this.rect().height,view:this})}},this);this._setup=function(){b._setup.call(this);this._focusable=false};this._updateBg=function(){var a=this._disabled?"disabled":this._over?"hover":"normal";if(this._checked)a="checked-"+a;this._backgroundByName(a)};this.value=this.checked=uki.newProp("_checked",function(a){this._checked=!!a;this._updateBg()});this._mouseup=
+function(){if(this._down){this._down=false;if(!this._disabled){this.checked(!this.checked());this.trigger("change",{checked:this._checked,source:this})}}}});(function(){var b=uki.view.declare("uki.view.Radio",uki.view.Checkbox,function(){this._backgroundPrefix="radio-";this.group=uki.newProp("_group",function(a){b.unregisterGroup(this);this._group=a;b.registerGroup(this);this.checked()&&b.clearGroup(this)});this.value=this.checked=uki.newProp("_checked",function(a){this._checked=!!a;a&&b.clearGroup(this);
+this._updateBg()});this._mouseup=function(){if(this._down){this._down=false;if(!this._checked&&!this._disabled){this.checked(!this._checked);this.trigger("change",{checked:this._checked,source:this})}}}});b.groups={};b.registerGroup=function(a){var c=a.group();if(b.groups[c])b.groups[c].push(a);else b.groups[c]=[a]};b.unregisterGroup=function(a){var c=a.group();if(b.groups[c])b.groups[c]=uki.grep(b.groups[c],function(e){return e!=a})};b.clearGroup=function(a){uki.each(b.groups[a.group()]||[],function(c,
+e){if(e!=a)if(e.checked()){e.checked(false);this.trigger("change",{checked:false,source:e})}})}})();uki.view.declare("uki.view.TextField",uki.view.Base,uki.view.Focusable,function(b,a){function c(d){if(!g[d]){var f=uki.createElement("input",b.defaultCss+"border:none;padding:0;border:0;margin:0;overflow:hidden;left:-999em;top:0;line-height:1;"+d);uki.dom.probe(f,function(h){g[d]=h.offsetHeight})}return g[d]}function e(d){return typeof d.placeholder=="string"}var g={};this._backgroundPrefix="";this._tagName=
+"input";this._type="text";this._setup=function(){b._setup.apply(this,arguments);uki.extend(this,{_value:"",_multiline:false,_placeholder:""});this.defaultCss+="margin:0;border:none;outline:none;padding:0;left:2px;top:0;z-index:100;-moz-resize:none;resize:none;background: url("+uki.theme.imageSrc("x")+");"+uki.theme.style("input")};this._updateBg=function(){this._input.style.color=this._disabled?"#999":"#000"};uki.delegateProp(this,"name","_input");this.value=function(d){if(d===undefined)return this._input.value;
+this._input.value=d;this._updatePlaceholderVis();return this};this.placeholder=uki.newProp("_placeholder",function(d){this._placeholder=d;if(!this._multiline&&e(this._input))this._input.placeholder=d;else if(this._placeholderDom)this._placeholderDom.innerHTML=d;else{this._placeholderDom=uki.createElement("div",this.defaultCss+"z-input:103;color:#999;cursor:text;-moz-user-select:none;",d);if(!this._multiline)this._placeholderDom.style.whiteSpace="nowrap";this._dom.appendChild(this._placeholderDom);
+this._updatePlaceholderVis();uki.each(["fontSize","fontFamily","fontWeight"],function(f,h){this._placeholderDom.style[h]=this._input.style[h]},this);uki.dom.bind(this._placeholderDom,"mousedown",uki.proxy(function(f){this.focus();f.preventDefault()},this))}});this._style=function(d,f){if(uki.inArray(d,uki.browser.textStyles)!=-1){if(f===undefined)return this._input.style[d];this._input.style[d]=f;if(this._placeholderDom)this._placeholderDom.style[d]=f}return b._style.call(this,d,f)};uki.addProps(this,
+["backgroundPrefix"]);this.defaultBackground=function(){return uki.theme.background(this._backgroundPrefix+"input")};this._createDom=function(){this._dom=uki.createElement("div",b.defaultCss+";cursor:text;overflow:visible;");this._initClassName();this._input=uki.createElement(this._tagName,this.defaultCss+(this._multiline?"":";overflow:hidden;"));this._input.value=this._value;if(this._type)this._input.type=this._type;this._dom.appendChild(this._input);this._input.value=this.value();this._initFocusable(this._input);
+this.bind("mousedown",function(d){d.target!=this._input&&this.focus()})};this._layoutDom=function(){b._layoutDom.apply(this,arguments);uki.dom.layout(this._input.style,{width:this._rect.width-4});var d;if(this._multiline){this._input.style.height=this._rect.height-4+"px";this._input.style.top="2px";d="2px 0"}else{d=(this._rect.height-c("font-size:"+this.style("fontSize")+";font-family:"+this.style("fontFamily")))/2;d=F(d)+"px 0 "+H(d)+"px 0";this._input.style.padding=d}if(this._placeholderDom)this._placeholderDom.style.padding=
+d};this._updatePlaceholderVis=function(){if(this._placeholderDom)this._placeholderDom.style.display=this.value()?"none":"block"};this._focus=function(d){this._focusBackground=this._focusBackground||uki.theme.background(this._backgroundPrefix+"input-focus");this._focusBackground.attachTo(this);if(this._placeholderDom)this._placeholderDom.style.display="none";a._focus.call(this,d)};this._blur=function(d){this._focusBackground.detach();this._updatePlaceholderVis();a._blur.call(this,d)};this._bindToDom=
+function(d){return a._bindToDom.call(this,d)||b._bindToDom.call(this,d)}});uki.view.declare("uki.view.MultilineTextField",uki.view.TextField,function(b){this._tagName="textarea";this._type="";this._setup=function(){b._setup.call(this);this._multiline=true}});uki.view.declare("uki.view.PasswordTextField",uki.view.TextField,function(b){this._setup=function(){b._setup.call(this);this._type="password"}});uki.Collection.addAttrs(["placeholder"]);(function(){function b(){a||uki.dom.probe(uki.createElement("div",
+"position:absolute;left:-99em;width:100px;height:100px;overflow:scroll;",'<div style="position:absolute;left:0;width:100%;"></div>'),function(e){a=e.offsetWidth-e.clientWidth;c=e.firstChild.offsetWidth==100});return a}var a,c;uki.view.declare("uki.view.ScrollPane",uki.view.Container,function(e){uki.extend(this,{_scrollableY:true,_scrollableX:false,_scrollY:false,_scrollX:false,_sbY:false,_sbX:false});this._setup=function(){e._setup.call(this);this._clientRect=this.rect().clone();this._rectForChild=
+this.rect().clone()};uki.addProps(this,["scrollableY","scrollableX","scrollX","scrollY"]);this.scrollV=this.scrollY;this.scrollH=this.scrollX;this.scrollableV=this.scrollableY;this.scrollableH=this.scrollableX;this.rectForChild=function(){return this._rectForChild};this.clientRect=function(){return this._clientRect};this.scroll=function(g,d){g&&this.scrollLeft(this.scrollLeft()+g);d&&this.scrollTop(this.scrollTop()+d)};uki.each(["scrollTop","scrollLeft"],function(g,d){this[d]=function(f){if(f==undefined)return this._dom[d];
+this._dom[d]=f;this.trigger("scroll",{source:this});return this}},this);this.visibleRect=function(){var g=this._clientRect.clone();g.x=this.rect().x+this.scrollLeft();g.y=this.rect().y+this.scrollTop();return g};this.rect=function(g){if(g===undefined)return this._rect;g=s.create(g);var d=this._rect;this._parentRect=g;if(!this._resizeSelf(g))return this;this._updateClientRects();this._needsLayout=true;this.trigger("resize",{oldRect:d,newRect:this._rect,source:this});return this};this._createDom=function(){e._createDom.call(this);
+if(A.indexOf("Gecko/")>-1)this._dom.tabIndex="-1"};this._recalcClientRects=function(){b();var g=this.contentsWidth(),d=this.contentsHeight();g=this._scrollableX?g>this._rect.width:false;d=this._scrollableY?d>this._rect.height:false;this._sbX=g||this._scrollX;this._sbY=d||this._scrollY;this._clientRect=new s(this._rect.width+(d?-1:0)*a,this._rect.height+(g?-1:0)*a);this._rectForChild=new s(this._rect.width+(d&&!c?-1:0)*a,this._rect.height+(g&&!c?-1:0)*a)};this._updateClientRects=function(){var g=this._clientRect;
+this._recalcClientRects();if(g.width!=this._clientRect.width||g.height!=this._clientRect.height)this._resizeChildViews(g)};this._resizeChildViews=function(g){for(var d=0,f=this.childViews();d<f.length;d++)f[d].parentResized(g,this._clientRect)};this._layoutChildViews=function(){for(var g=0,d=this.childViews();g<d.length;g++)d[g]._needsLayout&&d[g].visible()&&d[g].layout()};this._layoutDom=function(g){this._updateClientRects();if(this._layoutScrollX!==this._sbX){this._dom.style.overflowX=this._sbX?
+"scroll":"hidden";this._layoutScrollX=this._sbX}if(this._layoutScrollY!==this._sbY){this._dom.style.overflowY=this._sbY?"scroll":"hidden";this._layoutScrollY=this._sbY}e._layoutDom.call(this,g)};this._contentChanged=this.childResized=function(){this._needsLayout=true;uki.after(uki.proxy(this.layoutIfNeeded,this))}});uki.view.ScrollPane.initScrollWidth=b})();uki.Collection.addAttrs(["scrollTop","scrollLeft"]);uki.fn.scroll=function(b,a){this.each(function(){this.scroll(b,a)})};uki.view.list={};uki.view.declare("uki.view.List",
+uki.view.Base,uki.view.Focusable,function(b,a){function c(d,f){for(var h=new Array(f-d),j=0;d<=f;d++,j++)h[j]=d;return h}function e(d,f,h){for(var j=f=uki.binarySearch(f,d);d[f]<=h;)f++;f>j&&d.splice(j,f-j)}this._throttle=42;this._visibleRectExt=300;this._defaultBackground="theme(list)";this._setup=function(){b._setup.call(this);uki.extend(this,{_rowHeight:30,_render:new uki.view.list.Render,_data:[],_lastClickIndex:-1,_selectedIndexes:[]})};this.defaultBackground=function(){return uki.theme.background("list",
+this._rowHeight)};uki.addProps(this,["render","packSize","visibleRectExt","throttle","lastClickIndex","multiselect"]);this.rowHeight=uki.newProp("_rowHeight",function(d){this._rowHeight=d;this.minSize(new u(this.minSize().width,this._rowHeight*this._data.length));this._background&&this._background.detach();this._background=null;this.background()&&this.background().attachTo(this);this._contentChanged()});this.data=function(d){if(d===undefined)return this._data;this.clearSelection();this._data=d;this._packs[0].itemFrom=
+this._packs[0].itemTo=this._packs[1].itemFrom=this._packs[1].itemTo=0;this.minSize(new u(this.minSize().width,this._rowHeight*this._data.length));this.trigger("selection",{source:this});this._contentChanged();return this};this.relayout=function(){this._packs[0].itemFrom=this._packs[0].itemTo=this._packs[1].itemFrom=this._packs[1].itemTo=0;this.layout()};this.contentsSize=function(){return new u(this.rect().width,this._rowHeight*this._data.length)};this.addRow=function(d,f){this._data.splice(d,0,f);
+f=this._itemAt(d);var h=r.createElement("div");h.innerHTML=this._rowTemplate.render({height:this._rowHeight,text:this._render.render(this._data[d],this._rowRect(d),d)});f?f.parentNode.insertBefore(h.firstChild,f):this._dom.childNodes[0].appendChild(h.firstChild);if(d<=this._packs[0].itemTo){this._packs[0].itemTo++;this._packs[1].itemFrom++;this._packs[1].itemTo++;this._packs[1].dom.style.top=this._packs[1].itemFrom*this._rowHeight+"px"}else this._packs[1].itemTo++;for(d=uki.binarySearch(d,this.selectedIndexes());d<
+this._selectedIndexes.length;d++)this._selectedIndexes[d]++;this.minSize(new u(this.minSize().width,this._rowHeight*this._data.length));this._contentChanged();return this};this.removeRow=function(d){this._data.splice(d,1);this.data(this._data);return this};this.redrawRow=function(d){var f=this._itemAt(d);if(f)f.innerHTML=this._render.render(this._data[d],this._rowRect(d),d);return this};this.selectedIndex=function(d){if(d===undefined)return this._selectedIndexes.length?this._selectedIndexes[0]:-1;
+this.selectedIndexes([d]);this._scrollToPosition(d);return this};this.selectedIndexes=function(d){if(d===undefined)return this._selectedIndexes;this.clearSelection(true);this._selectedIndexes=d;for(d=0;d<this._selectedIndexes.length;d++)this._setSelected(this._selectedIndexes[d],true);this.trigger("selection",{source:this});return this};this.selectedRow=function(){return this._data[this.selectedIndex()]};this.selectedRows=function(){return uki.map(this.selectedIndexes(),function(d){return this._data[d]},
+this)};this.clearSelection=function(d){for(var f=0;f<this._selectedIndexes.length;f++)this._setSelected(this._selectedIndexes[f],false);this._selectedIndexes=[];if(!d)this._lastClickIndex=-1};this.isSelected=function(d){return this._selectedIndexes[uki.binarySearch(d,this._selectedIndexes)]==d};this.layout=function(){this._layoutDom(this._rect);this._needsLayout=false;this.trigger("layout",{rect:this._rect,source:this,visibleRect:this._visibleRect});this._firstLayout=false};this._rowRect=function(d){return new s(0,
+d*this._rowHeight,this.rect().width,this._rowHeight)};this._toggleSelection=function(d){var f=[].concat(this._selectedIndexes),h=uki.binarySearch(d,f);f[h]==d?f.splice(h,1):f.splice(h,0,d);this.selectedIndexes(f)};var g=false;this._scrollableParentScroll=function(){if(!g)if(this._throttle){if(!this._throttleStarted){this._throttleStarted=true;setTimeout(uki.proxy(function(){this._throttleStarted=false;this.layout()},this),this._throttle)}}else this.layout()};this._contentChanged=function(){this._needsLayout=
+true;uki.after(uki.proxy(this._relayoutParent,this))};this._relayoutParent=function(){this.parent().childResized(this);if(this._scrollableParent){for(var d=this;d&&d!=this._scrollableParent;){d._needsLayout=true;d=d.parent()}d.layout()}};this.keyPressEvent=function(){return t.opera||/mozilla/i.test(A)&&!/(compatible|webkit)/i.test(A)?"keypress":"keydown"};this._bindSelectionEvents=function(){this.bind("mousedown",this._mousedown);this.bind("mouseup",this._mouseup);this.bind(this.keyPressEvent(),this._keypress)};
+this._mouseup=function(d){if(this._multiselect){var f=uki.dom.offset(this._dom);d=(d.pageY-f.y)/this._rowHeight<<0;this._selectionInProcess&&this._lastClickIndex==d&&this.isSelected(d)&&this.selectedIndexes([d]);this._selectionInProcess=false}};this._mousedown=function(d){var f=uki.dom.offset(this._dom);f=(d.pageY-f.y)/this._rowHeight<<0;var h=this._selectedIndexes;if(this._multiselect){this._selectionInProcess=false;if(d.shiftKey&&h.length>0)if(this.isSelected(f)){h=[].concat(h);e(h,Math.min(f+1,
+this._lastClickIndex),Math.max(f-1,this._lastClickIndex));this.selectedIndexes(h)}else this.selectedIndexes(c(Math.min(f,h[0]),Math.max(f,h[h.length-1])));else if(d.metaKey)this._toggleSelection(f);else if(this.isSelected(f))this._selectionInProcess=true;else this.selectedIndexes([f])}else this.selectedIndexes([f]);this._lastClickIndex=f};this._keypress=function(d){var f=-1;if(d.which==38||d.keyCode==38){f=Math.max(0,this._lastClickIndex-1);d.preventDefault()}else if(d.which==40||d.keyCode==40){f=
+Math.min(this._data.length-1,this._lastClickIndex+1);d.preventDefault()}else if(this._multiselect&&(d.which==97||d.which==65)&&d.metaKey){d.preventDefault();this.selectedIndexes(c(0,this._data.length-1))}if(f>-1&&f!=this._lastClickIndex){if(d.shiftKey&&this._multiselect){this.isSelected(f)?this._toggleSelection(this._lastClickIndex):this._toggleSelection(f);this._scrollToPosition(f)}else this.selectedIndex(f);this._lastClickIndex=f}};this._createDom=function(){this._dom=uki.createElement("div",this.defaultCss+
+"overflow:hidden");this._initClassName();var d=uki.createElement("div","position:absolute;left:0;top:0px;width:100%;overflow:hidden");this._packs=[{dom:d,itemTo:0,itemFrom:0},{dom:d.cloneNode(false),itemTo:0,itemFrom:0}];this._dom.appendChild(this._packs[0].dom);this._dom.appendChild(this._packs[1].dom);this._initFocusable();this._bindSelectionEvents()};this._setSelected=function(d,f){var h=this._itemAt(d);h&&this._render.setSelected(h,this._data[d],f,this.hasFocus())};this._scrollToPosition=function(d){if(this._visibleRect){var f;
+f=(d+1)*this._rowHeight;d=d*this._rowHeight;g=true;if(f>=this._visibleRect.maxY())this._scrollableParent.scroll(0,f-this._visibleRect.maxY());else d<this._visibleRect.y&&this._scrollableParent.scroll(0,d-this._visibleRect.y);g=false;this.layout()}};this._itemAt=function(d){if(d<this._packs[1].itemTo&&d>=this._packs[1].itemFrom)return this._packs[1].dom.childNodes[d-this._packs[1].itemFrom];else if(d<this._packs[0].itemTo&&d>=this._packs[0].itemFrom)return this._packs[0].dom.childNodes[d-this._packs[0].itemFrom];
+return null};this._rowTemplate=new uki.theme.Template('<div style="width:100%;height:${height}px;overflow:hidden;">${text}</div>');this._renderPack=function(d,f,h){var j=[];for(i=f;i<h;i++)j[j.length]=this._rowTemplate.render({height:this._rowHeight,text:this._render.render(this._data[i],this._rowRect(i),i)});d.dom.innerHTML=j.join("");d.itemFrom=f;d.itemTo=h;d.dom.style.top=f*this._rowHeight+"px";this._restorePackSelection(d,f,h)};this._restorePackSelection=function(d){var f=this._selectedIndexes;
+if(f[0]<=d.itemFrom&&f[f.length-1]>=d.itemFrom||f[0]<=d.itemTo&&f[f.length-1]>=d.itemTo||f[0]>=d.itemFrom&&f[f.length-1]<=d.itemTo){var h=uki.binarySearch(d.itemFrom,f);for(h=Math.max(h,0);f[h]!==null&&f[h]<d.itemTo;){var j=f[h]-d.itemFrom;this._render.setSelected(d.dom.childNodes[j],this._data[j],true,this.hasFocus());h++}}};this._swapPacks=function(){var d=this._packs[0];this._packs[0]=this._packs[1];this._packs[1]=d};this._layoutDom=function(d){if(!this._scrollableParent){this._scrollableParent=
+uki.view.scrollableParent(this);this._scrollableParent.bind("scroll",uki.proxy(this._scrollableParentScroll,this))}var f=this._rowHeight*this._data.length;this._visibleRect=uki.view.visibleRect(this,this._scrollableParent);if(this._focusTarget)this._focusTarget.style.top=this._visibleRect.y+"px";var h=F((this._visibleRect.height+this._visibleRectExt*2)/this._rowHeight),j=q(0,this._visibleRect.y-this._visibleRectExt),k=w(f,this._visibleRect.maxY()+this._visibleRectExt),n=this._packs[0].itemFrom*this._rowHeight,
+m=this._packs[1].itemTo*this._rowHeight;f=true;b._layoutDom.call(this,d);if(k<=n||j>=m||k>m&&this._packs[1].itemFrom*this._rowHeight>this._visibleRect.y&&this._packs[1].itemTo>this._packs[1].itemFrom||j<n&&this._packs[0].itemTo*this._rowHeight<this._visibleRect.maxY()){d=j-this._visibleRectExt/2;d=q(0,Math.round(d/this._rowHeight));h=w(this._data.length,d+h);this._renderPack(this._packs[0],d,h);this._renderPack(this._packs[1],h,h)}else if(k>m&&this._packs[1].itemTo>this._packs[1].itemFrom){d=this._packs[1].itemTo;
+h=w(this._data.length,this._packs[1].itemTo+h);this._renderPack(this._packs[0],d,h);this._swapPacks()}else if(k>m){d=this._packs[0].itemTo;h=w(this._data.length,this._packs[1].itemTo+h);this._renderPack(this._packs[1],d,h)}else if(j<n){d=q(this._packs[0].itemFrom-h,0);h=this._packs[0].itemFrom;this._renderPack(this._packs[1],d,h);this._swapPacks()}else f=false;if(f&&/MSIE 6|7/.test(A))this.dom().className+=""};this._bindToDom=function(d){return a._bindToDom.call(this,d)||b._bindToDom.call(this,d)};
+this._focus=function(d){a._focus.call(this,d);this._selectedIndexes.length==0&&this._data.length>0?this.selectedIndexes([0]):this.selectedIndexes(this.selectedIndexes())};this._blur=function(d){a._blur.call(this,d);this.selectedIndexes(this.selectedIndexes())}});uki.Collection.addAttrs(["data","selectedIndex","selectedIndexes","selectedRow","selectedRows","lastClickIndex"]);uki.view.declare("uki.view.ScrollableList",uki.view.ScrollPane,function(b){this._createDom=function(){b._createDom.call(this);
+this._list=uki({view:"List",rect:this.rect().clone().normalize(),anchors:"left top right bottom"})[0];this.appendChild(this._list)};uki.each("data rowHeight render packSize visibleRectExt throttle focusable selectedIndex selectedIndexes selectedRow selectedRows multiselect draggable textSelectable".split(" "),function(a,c){uki.delegateProp(this,c,"_list")},this)});uki.view.list.Render=uki.newClass({init:function(){},render:function(b,a){return'<div style="line-height: '+a.height+'px; font-size: 12px; padding: 0 4px;">'+
+b+"</div>"},setSelected:function(b,a,c,e){b.style.backgroundColor=c&&e?"#3875D7":c?"#CCC":"";b.style.color=c&&e?"#FFF":"#000"}});uki.view.table={};uki.view.declare("uki.view.Table",uki.view.Container,function(b){var a="rowHeight data packSize visibleRectExt render selectedIndex selectedIndexes selectedRows selectedRow focus blur hasFocus lastClickIndex focusable textSelectable multiselect".split(" ");this._headerHeight=this._rowHeight=17;this._listImpl="uki.view.List";uki.each(a,function(c,e){uki.delegateProp(this,
+e,"_list")},this);this._setup=function(){b._setup.call(this);this._columns=[];this.defaultCss+="overflow:hidden;"};this._style=function(c,e){this._header.style(c,e);return b._style.call(this,c,e)};this.list=function(){return this._list};this.header=function(){return this._header};this.columns=uki.newProp("_columns",function(c){for(var e=0;e<this._columns.length;e++)this._columns[e].unbind();this._columns=uki.build(c);for(e=this._totalWidth=0;e<this._columns.length;e++){this._columns[e].position(e);
+this._columns[e].bind("beforeResize",uki.proxy(function(){this._updateTotalWidth();this._scrollPane.layout()},this))}this._updateTotalWidth();this._header.columns(this._columns)});this.redrawCell=function(c,e){var g=this._list._itemAt(c);if(g){var d=r.createElement("div");d.innerHTML=this.columns()[e].render(this.data()[c],new s(0,c*this.rowHeight(),this.list().width(),this.rowHeight()),c);g.replaceChild(d.firstChild,g.childNodes[e])}return this};uki.each(["redrawRow","addRow","removeRow"],function(c,
+e){this[e]=function(){this.list()[e].apply(this.list(),arguments);return this}},this);this.redrawColumn=function(c){for(var e=this._list._packs[1].itemTo,g=this._list._packs[0].itemFrom;g<e;g++)this.redrawCell(g,c);return this};this._updateTotalWidth=function(){for(var c=this._totalWidth=0;c<this._columns.length;c++){this._columns[c].position(c);this._totalWidth+=this._columns[c].width()}this._list.minSize(new u(this._totalWidth,this._list.minSize().height));this._header.minSize(new u(this._totalWidth,
+0))};this._createDom=function(){b._createDom.call(this);this._initClassName();var c=new s(0,this._headerHeight,this.rect().width,this.rect().height-this._headerHeight),e=c.clone().normalize(),g=new s(0,0,this.rect().width,this._headerHeight),d={view:this._listImpl,rect:e,anchors:"left top bottom right",render:new uki.view.table.Render(this),className:"table-list"};c={view:"ScrollPane",rect:c,anchors:"left top right bottom",scrollableH:true,childViews:[d],className:"table-scroll-pane"};g={view:"table.Header",
+rect:g,anchors:"top left right",className:"table-header"};uki.each(a,function(f,h){if(this["_"+h]!==undefined)d[h]=this["_"+h]},this);this._scrollPane=uki.build(c)[0];this._list=this._scrollPane.childViews()[0];this._header=uki.build(g)[0];this._scrollPane.resizeToContents();this.appendChild(this._header);this.appendChild(this._scrollPane);this._scrollPane.bind("scroll",uki.proxy(function(){this._header.dom().style.left=-this._scrollPane.scrollLeft()+"px"},this))}});uki.Collection.addAttrs(["columns"]);
+uki.view.table.Render=uki.newClass(uki.view.list.Render,new (function(){this.init=function(b){this._table=b};this.render=function(b,a,c){var e=this._table.columns();return uki.map(e,function(g,d){return e[d].render(b,a,c)}).join("")}}));uki.view.table.Column=uki.newClass(uki.view.Observable,new (function(){this._width=100;this._maxWidth=this._minWidth=this._position=this._offset=0;this._css="float:left;white-space:nowrap;text-overflow:ellipsis;";this._inset=new x(3,5);this._templatePrefix="table-";
+this.init=function(){};uki.addProps(this,["position","css","formatter","label","resizable","maxWidth","minWidth","maxWidth","key","sort"]);this.template=function(){return this._template||(this._template=uki.theme.template(this._templatePrefix+"cell"))};this.headerTemplate=function(){var b="";if(this.sort()=="ASC")b="-asc";if(this.sort()=="DESC")b="-desc";return uki.theme.template(this._templatePrefix+"header-cell"+b)};this.sortData=function(b){var a=this;return b.sort(function(c,e){return a._key?
+a.compare(uki.attr(c,a._key),uki.attr(e,a._key)):a.compare(c[a._position],e[a._position])})};this.compare=function(b,a){return(b>=a?1:b==a?0:-1)*(this._sort=="DESC"?-1:1)};this.width=uki.newProp("_width",function(b){var a={oldWidth:this._width,source:this};this._width=this._normailizeWidth(b);a.newWidth=this._width;this.trigger("beforeResize",a);if(this._stylesheet&&a.newWidth!=a.oldWidth)(this._stylesheet.styleSheet?this._stylesheet.styleSheet.rules:this._stylesheet.sheet.cssRules)[0].style.width=
+this._clientWidth()+"px";this.trigger("resize",a)});this._bindToDom=uki.F;this._normailizeWidth=function(b){if(this._maxWidth)b=w(this._maxWidth,b);if(this._minWidth)b=q(this._minWidth,b);return b};this.inset=uki.newProp("_inset",function(b){this._inset=x.create(b)});this.render=function(b,a,c){this._prerenderedTemplate||this._prerenderTemplate(a);a=this._key?uki.attr(b,this._key):b[this._position];this._prerenderedTemplate[1]=this._formatter?this._formatter(a,b,c):a;return this._prerenderedTemplate.join("")};
+this.appendResizer=function(b,a){a=uki.theme.dom("resizer",a);b.appendChild(a);return a};this.renderHeader=function(b){this._className||this._initStylesheet();return this.headerTemplate().render({data:'<div style="overflow:hidden;text-overflow:ellipsis;*width:100%;height:100%;padding-top:'+this._inset.top+'px">'+this.label()+"</div>",style:"*overflow-y:hidden;"+this._cellStyle(true,b),className:this._className})};this._prerenderTemplate=function(b){this._className||this._initStylesheet();this._prerenderedTemplate=
+this.template().render({data:"\u0001\u0001",style:"overflow:hidden;"+this._cellStyle(false,b.height),className:this._className}).split("\u0001")};this._cellPadding=function(b){var a=this._inset;return["padding:",b?"0":a.top,"px ",a.right,"px ",b?"0":a.bottom,"px ",a.left,"px;"].join("")};this._cellHeight=function(b,a){return"height:"+(a-(uki.dom.offset.boxModel&&!b?this._inset.height():0))+"px;"};this._cellStyle=function(b,a){return this._css+this._cellPadding(b)+";"+this._cellHeight(b,a)};this._clientWidth=
+function(){return this._width-(uki.dom.offset.boxModel?this._inset.width()+1:0)};this._initStylesheet=function(){if(!this._className){uki.dom.offset.initializeBoxModel();this._className="uki-table-column-"+uki.guid++;var b="."+this._className+" {width:"+this._clientWidth()+"px;}";this._stylesheet=uki.dom.createStylesheet(b)}}}));uki.view.table.NumberColumn=uki.newClass(uki.view.table.Column,new function(){this._css=uki.view.table.Column.prototype._css+"text-align:right;";this.compare=function(b,a){b*=
+1;a*=1;return(b>=a?1:b==a?0:-1)*(this._sort=="DESC"?-1:1)}});uki.view.table.CustomColumn=uki.view.table.Column;uki.view.declare("uki.view.table.Header",uki.view.Label,function(b){this._defaultBackground="theme(table-header)";this._setup=function(){b._setup.call(this);this._multiline=true;this._resizers=[]};this.columns=uki.newProp("_columns",function(a){this._columns=a;this.html(this._createColumns());this._createResizers()});this._createDom=function(){b._createDom.call(this);this.bind("click",this._click)};
+this._click=function(a){if(!this._dragging){a=a.target;if(!(a==this.dom()||a==this._label)){for(;a.parentNode!=this._label;)a=a.parentNode;a=uki.inArray(a,this._label.childNodes);a>-1&&this.trigger("columnClick",{source:this,columnIndex:a,column:this._columns[a]})}}};this.redrawColumn=function(a){this._resizers[a]&&uki.dom.unbind(this._resizers[a]);var c=r.createElement("div");c.innerHTML=this._columns[a].renderHeader(this.rect().height);this._label.replaceChild(c.firstChild,this._label.childNodes[a]);
+this._columns[a].resizable()&&this._createResizers(a)};this._createColumns=function(){for(var a=[],c=0,e=this._columns,g=e.length;c<g;c++)a[a.length]=e[c].renderHeader(this.rect().height);return a.join("")};this._createResizer=function(a){var c=this._columns[a];if(c.resizable()){c=c.appendResizer(this._label.childNodes[a],this.rect().height);this._bindResizerDrag(c,a);this._resizers[a]=c}};this._createResizers=function(){uki.each(this._columns,this._createResizer,this)};this._bindResizerDrag=function(a,
+c){var e=this;uki.dom.bind(a,"draggesture",function(g){e._dragging=true;var d=uki.dom.offset(e.dom());g=g.pageX-d.x;d=0;var f,h=e._columns[c];for(f=0;f<c;f++)d+=e._columns[f].width();h.width(g-d)});uki.dom.bind(a,"draggestureend",function(){setTimeout(function(){e._dragging=false},1)})}});uki.view.declare("uki.view.Slider",uki.view.Container,uki.view.Focusable,function(b,a){this._handleSize=new u(10,18);this._setup=function(){b._setup.call(this);uki.extend(this,{_min:0,_max:1,_value:0,_values:null,
+_keyStep:0.01})};uki.addProps(this,["min","max","values","keyStep"]);this.values=uki.newProp("_values",function(c){this._values=c;this._min=c[0];this._max=c[c.length-1]});this.value=uki.newProp("_value",function(c){this._value=q(this._min,w(this._max,c));this._position=this._val2pos(this._value);this._moveHandle()});this._pos2val=function(c,e){if(this._values){c=Math.round(1*c/(this._rect.width-this._handleSize.width)*(this._values.length-1));if(e)this._cachedIndex=c;return this._values[c]}return c/
+(this._rect.width-this._handleSize.width)*(this._max-this._min)+this._min};this._val2pos=function(c){if(this._values)return(this._cachedIndex!==undefined?this._cachedIndex:uki.binarySearch(c,this._values))/(this._values.length-1)*(this._rect.width-this._handleSize.width);return(c-this._min)/(this._max-this._min)*(this._rect.width-this._handleSize.width)};this._createDom=function(){this._dom=uki.createElement("div",this.defaultCss+"height:18px;-moz-user-select:none;-webkit-user-select:none;overflow:visible;");
+this._initClassName();this._handle=uki({view:"SliderHandle",rect:new s(0,(this._rect.height-this._handleSize.height)/2,this._handleSize.width,this._handleSize.height),anchors:"left top"})[0];this.appendChild(this._handle);uki.theme.background("slider-bar").attachTo(this);uki.each(["draggesturestart","draggesture","draggestureend"],function(c,e){this._handle.bind(e,uki.proxy(this["_"+e],this))},this);this.bind(uki.view.List.prototype.keyPressEvent(),this._keypress);this.bind("click",this._click);this._initFocusable()};
+this._focus=function(c){this._handle._focus();a._focus.call(this,c)};this._blur=function(c){this._handle._blur();a._blur.call(this,c)};this._click=function(c){this.value(this._pos2val(c.pageX-uki.dom.offset(this._dom).x-this._handleSize.width/2,true));this._cachedIndex=undefined;this.trigger("change",{source:this,value:this._value})};this._keypress=function(c){if(c.which==39||c.keyCode==39)this.value(this.value()+this._keyStep*(this._max-this._min));else if(c.which==37||c.keyCode==37)this.value(this.value()-
+this._keyStep*(this._max-this._min))};this._moveHandle=function(){var c=this._handle.rect().clone();c.x=this._position;c.y=(this._rect.height-this._handleSize.height)/2;this._handle.rect(c).layout()};this._draggesturestart=function(){this._dragging=true;this._initialPosition=this._handle.rect().clone();return true};this._draggesture=function(c){this.value(this._pos2val(q(0,w(this._rect.width-this._handleSize.width,this._initialPosition.x+c.dragOffset.x)),true));this._cachedIndex=undefined};this._draggestureend=
+function(){this._dragging=false;this._initialPosition=null;this.value(this._pos2val(this._position,true));this._cachedIndex=undefined;this.trigger("change",{source:this,value:this._value})};this._layoutDom=function(c){b._layoutDom.call(this,c);this._position=this._val2pos(this._value);this._moveHandle();return true};this._bindToDom=function(c){if(c=="change")return true;return uki.view.Focusable._bindToDom.call(this,c)||b._bindToDom.call(this,c)}});uki.view.declare("uki.view.SliderHandle",uki.view.Button,
+{_backgroundPrefix:"slider-handle-",_focusable:false});uki.view.declare("uki.view.HSplitPane",uki.view.Container,function(b){this._throttle=0;this._setup=function(){b._setup.call(this);this._originalRect=this._rect;uki.extend(this,{_vertical:false,_handlePosition:200,_autogrowLeft:false,_autogrowRight:true,_handleWidth:7,_leftMin:100,_rightMin:100,_panes:[]})};uki.addProps(this,["leftMin","rightMin","autogrowLeft","autogrowRight","throttle"]);this.topMin=this.leftMin;this.bottomMin=this.rightMin;
+this.handlePosition=uki.newProp("_handlePosition",function(a){this._handlePosition=this._normalizePosition(a);this.trigger("handleMove",{source:this,handlePosition:this._handlePosition,dragValue:a});this._resizeChildViews()});this.handleWidth=uki.newProp("_handleWidth",function(a){if(this._handleWidth!=a){this._handleWidth=a;a=this._createHandle();this._dom.insertBefore(a,this._handle);this._removeHandle();this._handle=a;this._resizeChildViews()}});this._normalizePosition=function(a){var c=this._vertical?
+"height":"width";return q(this._leftMin,w(this._rect[c]-this._rightMin-this._handleWidth,q(0,w(this._rect?this._rect[c]:1E3,a*1))))};this._removeHandle=function(){this._dom.removeChild(this._handle)};this._createHandle=function(){var a;if(this._vertical){a=uki.theme.dom("splitPane-vertical",{handleWidth:this._handleWidth});a.style.top=this._handlePosition+"px"}else{a=uki.theme.dom("splitPane-horizontal",{handleWidth:this._handleWidth});a.style.left=this._handlePosition+"px"}uki.each(["draggesturestart",
+"draggesture","draggestureend"],function(c,e){uki.dom.bind(a,e,uki.proxy(this["_"+e],this))},this);return a};this._createDom=function(){this._dom=uki.createElement("div",this.defaultCss);this._initClassName();for(var a=0,c;a<2;a++){c={view:"Container"};c.anchors=a==1?"left top bottom right":this._vertical?"left top right":"left top bottom";c.rect=a==0?this._leftRect():this._rightRect();this._panes[a]=uki.build(c)[0];this.appendChild(this._panes[a])}this._dom.appendChild(this._handle=this._createHandle())};
+this._normalizeRect=function(a){a=b._normalizeRect.call(this,a);a=a.clone();if(this._vertical)a.height=q(a.height,this._leftMin+this._rightMin);else a.width=q(a.width,this._leftMin+this._rightMin);return a};this._resizeSelf=function(a){var c=this._rect,e=this._vertical?"height":"width";if(!b._resizeSelf.call(this,a))return false;if(this._autogrowLeft){c=a[e]-c[e];this._handlePosition=this._normalizePosition(this._handlePosition+(this._autogrowRight?c/2:c))}if(this._vertical){if(a.height-this._handlePosition<
+this._rightMin)this._handlePosition=q(this._leftMin,a.height-this._rightMin)}else if(a.width-this._handlePosition<this._rightMin)this._handlePosition=q(this._leftMin,a.width-this._rightMin);return true};this._draggesturestart=function(a){var c=uki.dom.offset(this.dom());this._posWithinHandle=a[this._vertical?"pageY":"pageX"]-c[this._vertical?"y":"x"]-this._handlePosition;return true};this._draggesture=function(a){this._updatePositionOnDrag(a)};this._draggestureend=function(a){this._updatePositionOnDrag(a)};
+this._updatePositionOnDrag=function(a){var c=uki.dom.offset(this.dom());this.handlePosition(a[this._vertical?"pageY":"pageX"]-c[this._vertical?"y":"x"]-this._posWithinHandle);if(this._throttle){this._throttleHandler=this._throttleHandler||uki.proxy(function(){this.layout();this._trottling=false},this);if(!this._trottling){this._trottling=true;setTimeout(this._throttleHandler,this._throttle)}}else this.layout()};this.topPane=this.leftPane=function(a){return this._paneAt(0,a)};this.bottomPane=this.rightPane=
+function(a){return this._paneAt(1,a)};this.topChildViews=this.leftChildViews=function(a){return this._childViewsAt(0,a)};this.bottomChildViews=this.rightChildViews=function(a){return this._childViewsAt(1,a)};this._childViewsAt=function(a,c){if(c===undefined)return this._panes[a].childViews();this._panes[a].childViews(c);return this};this._paneAt=function(a,c){if(c===undefined)return this._panes[a];uki.build.copyAttrs(this._panes[a],c);return this};this._leftRect=function(){return this._vertical?new s(this._rect.width,
+this._handlePosition):new s(this._handlePosition,this._rect.height)};this._rightRect=function(){return this._vertical?new s(0,this._handlePosition+this._handleWidth,this._rect.width,this._rect.height-this._handleWidth-this._handlePosition):new s(this._handlePosition+this._handleWidth,0,this._rect.width-this._handleWidth-this._handlePosition,this._rect.height)};this._resizeChildViews=function(){this._panes[0].rect(this._leftRect());this._panes[1].rect(this._rightRect())};this._layoutDom=function(a){b._layoutDom.call(this,
+a);this._handle.style[this._vertical?"top":"left"]=this._handlePosition+"px"};this._bindToDom=function(a){if(a=="handleMove")return true;return b._bindToDom.call(this,a)}});uki.view.declare("uki.view.VSplitPane",uki.view.HSplitPane,function(b){this._setup=function(){b._setup.call(this);this._vertical=true}});uki.Collection.addAttrs(["handlePosition"]);uki.view.declare("uki.view.Popup",uki.view.Container,function(b){this._setup=function(){b._setup.call(this);uki.extend(this,{_offset:2,_relativeTo:null,
+_horizontal:false,_flipOnResize:true,_defaultBackground:"theme(popup-normal)"})};this._createDom=function(){b._createDom.call(this);this.hideOnClick(true)};uki.addProps(this,["offset","relativeTo","horizontal","flipOnResize"]);this.hideOnClick=function(a){if(a===undefined)return this._clickHandler;if(a!=!!this._clickHandler)if(a){this._clickHandler=this._clickHandler||uki.proxy(function(c){uki.dom.contains(this._relativeTo.dom(),c.target)||uki.dom.contains(this.dom(),c.target)||this.hide()},this);
+uki.dom.bind(r.body,"mousedown",this._clickHandler);uki.dom.bind(t,"resize",this._clickHandler)}else{uki.dom.unbind(r.body,"mousedown",this._clickHandler);uki.dom.unbind(t,"resize",this._clickHandler);this._clickHandler=false}return this};this.toggle=function(){this.parent()&&this.visible()?this.hide():this.show()};this.show=function(){this.visible(true);if(this.parent()){this.rect(this._recalculateRect());this.layout(this._rect)}else new uki.Attachment(t,this);this.trigger("toggle",{source:this})};
+this.hide=function(){this.visible(false);this.trigger("toggle",{source:this})};this.parentResized=function(){this.rect(this._recalculateRect())};this._resizeSelf=function(a){this._rect=this._normalizeRect(a);return true};this._layoutDom=function(a){return b._layoutDom.call(this,a)};this._recalculateRect=function(){if(!this.visible())return this._rect;var a=uki.dom.offset(this._relativeTo.dom()),c=this._relativeTo.rect(),e=this.rect().clone(),g=uki.view.top(this);g.rect();g=uki.dom.offset(g.dom());
+var d=new z,f=this._horizontal?this._offset:0,h=this._horizontal?0:this._offset;a.offset(-g.x,-g.y);d.x=this._anchors&2?a.x+c.width-(this._horizontal?0:e.width)+f:this._anchors&8?a.x-(this._horizontal?e.width:0)-f:a.x+(c.width-e.width>>1)-f;d.y=this._anchors&4?a.y+(this._horizontal?c.height:0)-e.height-h:this._anchors&1?a.y+(this._horizontal?0:c.height)+h:a.y+(c.height-e.height>>1)+h;return new s(d.x,d.y,e.width,e.height)}});uki.each(["show","hide","toggle"],function(b,a){uki.fn[a]=function(){this.each(function(){this[a]()})}});
+uki.view.declare("uki.view.VFlow",uki.view.Container,function(b){this.contentsSize=function(){var a=uki.reduce(0,this._childViews,function(c,e){return c+(e.visible()?e.rect().height:0)});return new u(this.contentsWidth(),a)};this.hidePartlyVisible=uki.newProp("_hidePartlyVisible");this.resizeToContents=function(a){this._resizeChildViews(this._rect);return b.resizeToContents.call(this,a)};this.layout=function(){return b.layout.call(this)};this._resizeChildViews=function(a){for(var c=0,e,g=0,d=this.childViews();g<
+d.length;g++){e=d[g];e.parentResized(a,this._rect);e.rect().y=c;this._hidePartlyVisible&&e.visible(e._rect.height+c<=this._rect.height);if(e.visible())c+=e._rect.height}};this._contentChanged=this.childResized=function(){this._needsLayout=true;uki.after(uki.proxy(this._afterChildResized,this))};this._afterChildResized=function(){this.resizeToContents("height");this.parent().childResized(this);this.layoutIfNeeded()}});uki.view.declare("uki.view.HFlow",uki.view.VFlow,function(){this.contentsSize=function(){var b=
+uki.reduce(0,this._childViews,function(a,c){return a+(c.visible()?c.rect().width:0)});return new u(b,this.contentsHeight())};this._resizeChildViews=function(b){for(var a=0,c,e=0,g=this.childViews();e<g.length;e++){c=g[e];c.parentResized(b,this._rect);c.rect().x=a;this._hidePartlyVisible&&c.visible(c._rect.width+a<=this._rect.width);if(c.visible())a+=c._rect.width}};this._afterChildResized=function(){this.resizeToContents("width");this.parent().childResized(this);this.layoutIfNeeded()}});uki.view.toolbar=
+{};uki.view.declare("uki.view.Toolbar",uki.view.Container,function(b){this.typeName=function(){return"uki.view.Toolbar"};this._moreWidth=30;this._setup=function(){b._setup.call(this);this._buttons=[];this._widths=[]};this.buttons=uki.newProp("_buttons",function(a){this._buttons=a;this._flow.childViews(uki.build(uki.map(this._buttons,this._createButton,this)).resizeToContents("width"));this._totalWidth=uki.reduce(0,this._flow.childViews(),function(c,e){return c+e.rect().width})});uki.moreWidth=uki.newProp("_moreWidth",
+function(a){this._moreWidth=a;this._updateMoreVisible()});this._createDom=function(){b._createDom.call(this);var a=this.rect(),c=a.clone().normalize();a={view:"Button",rect:new s(a.width-this._moreWidth,0,this._moreWidth,a.height),anchors:"right top",className:"toolbar-button",visible:false,backgroundPrefix:"toolbar-more-",text:">>",focusable:false};var e={view:"Popup",rect:"0 0",anchors:"right top",className:"toolbar-popup",background:"theme(toolbar-popup)",childViews:{view:"VFlow",rect:"0 5 0 0",
+anchors:"right top left bottom"}};this._flow=uki.build({view:"HFlow",rect:c,anchors:"left top right",className:"toolbar-flow",hidePartlyVisible:true})[0];this._more=uki.build(a)[0];this.appendChild(this._flow);this.appendChild(this._more);e.relativeTo=this._more;this._popup=uki.build(e)[0];this._more.bind("click",uki.proxy(this._showMissingButtons,this))};this._showMissingButtons=function(){for(var a=this._flow.rect().width,c=0,e=[],g=0,d=this._flow.childViews(),f=d.length;g<f;g++){c+=d[g].rect().width;
+c>a&&e.push(g)}a=uki.map(e,function(h){var j={html:d[h].html(),backgroundPrefix:"toolbar-popup-button-"};uki.each(["fontSize","fontWeight","color","textAlign","inset"],function(k,n){j[n]=uki.attr(d[h],n)});return this._createButton(j)},this);uki("VFlow",this._popup).childViews(a).resizeToContents("width height");this._popup.resizeToContents("width height").height(this._popup.height()+5).toggle()};this._updateMoreVisible=function(){var a=this._rect;if(this._more.visible()!=a.width<this._totalWidth){this._more.visible(a.width<
+this._totalWidth);var c=this._flow.rect();c.width+=(a.width<this._totalWidth?-1:1)*this._moreWidth;this._flow.rect(c)}};this.rect=function(a){var c=b.rect.call(this,a);a&&this._updateMoreVisible();return c};this._createButton=function(a){var c=this.rect().clone().normalize();c.width=100;return uki.extend({view:"Button",rect:c,focusable:false,align:"left",anchors:"left top",backgroundPrefix:"toolbar-button-",autosizeToContents:"width",focusable:false},a)}})})();
+(function(){function t(q){return uki.theme.airport.imagePath+q}function r(q){var w=uki.theme.imageSrc("checkbox");return new uki.background.CssBox("",{innerHTML:'<div style="position:absolute;left:50%;top:50%;width:18px;height:18px;overflow:hidden;margin:-9px 0 0 -9px; background: url('+w+") 0 -"+q+'px"></div>'})}function A(q){var w=uki.theme.imageSrc("radio");return new uki.background.CssBox("",{innerHTML:'<div style="position:absolute;left:50%;top:50%;width:18px;height:18px;overflow:hidden;margin:-9px 0 0 -9px; background: url('+
+w+") 0 -"+q+'px"></div>'})}function B(){return'<div style="'+uki.browser.css("position:absolute;overflow:hidden;z-index:10;left:50%;top:50%;width:2px;height:11px;margin:-6px 0 0 -1px;background:#8599AE;border-top:1px solid #6A7A8C;")+'"></div>'}uki.theme.airport=uki.extend({},uki.theme.Base,{imagePath:"http://static.ukijs.org/pkg/0.3.8/uki-theme/airport/i/",backgrounds:{"button-normal":function(){return new uki.background.LinearGradient({startColor:"#FDFEFF",stops:[{pos:0.15,color:"#F5F7FD"},{pos:0.8,color:"#C9CACF"}],endColor:"#C7CBD2",
+innerHTML:'<div style="position:absolute;z-index:1;left:1px;bottom:0px;right:1px;height:1px;overflow:hidden;background:rgba(255,255,255,0.4)"></div>',css:"border:1px solid #666;border-radius:3px;box-shadow:0 1px 0 rgba(255,255,255,0.5);"})},"button-hover":function(){return new uki.background.LinearGradient({startColor:"#FFFFFF",stops:[{pos:0.7,color:"#D7DAE4"}],endColor:"#D9DEE6",innerHTML:'<div style="position:absolute;z-index:1;left:1px;bottom:0px;right:1px;height:1px;overflow:hidden;background:rgba(255,255,255,0.4)"></div>',
+css:"border:1px solid #666;border-radius:3px;box-shadow:0 1px 0 rgba(255,255,255,0.5);"})},"button-down":function(){return new uki.background.LinearGradient({startColor:"#9C9DA1",stops:[{pos:0.6,color:"#C5C7CD"}],endColor:"#CCCFD6",css:"border:1px solid #666;border-radius:3px;box-shadow:inset 0 1px 3px rgba(0,0,0,0.5);"})},"button-focus":function(){if(uki.browser.cssBoxShadow()=="unsupported")return new uki.background.CssBox("background:#7594D2;"+(uki.browser.cssFilter()&&uki.image.needAlphaFix?"filter:Alpha(opacity=70);":
+"opacity:0.7;"),{inset:"-2 -2",zIndex:-2});return new uki.background.Css({boxShadow:"0 0 6px #0244D4",borderRadius:"3px"})},"button-disabled":function(){return new uki.background.Multi(uki.theme.background("button-normal"),new uki.background.Css({color:"#999"}))},"checkbox-normal":function(){return r(18)},"checkbox-hover":function(){return r(54)},"checkbox-disabled":function(){return r(90)},"checkbox-checked-normal":function(){return r(0)},"checkbox-checked-hover":function(){return r(36)},"checkbox-checked-disabled":function(){return r(72)},
+"checkbox-focus":function(){if(uki.image.needAlphaFix)return new uki.background.CssBox("",{innerHTML:'<div style="position:absolute;left:50%;top:50%;width:19px;height:19px;overflow:hidden;margin:-10px 0 0 -10px;background:#7594D2;filter:Alpha(opacity=70);"></div>',zIndex:-2});return new uki.background.CssBox("",{innerHTML:'<div style="position:absolute;left:50%;top:50%;width:24px;height:24px;overflow:hidden;margin:-12px 0 0 -12px; background: url('+uki.theme.imageSrc("checkbox-focus")+') 0 0"></div>',
+zIndex:-2})},"radio-normal":function(){return A(18)},"radio-hover":function(){return A(54)},"radio-disabled":function(){return A(90)},"radio-checked-normal":function(){return A(0)},"radio-checked-hover":function(){return A(36)},"radio-checked-disabled":function(){return A(72)},"radio-focus":function(){if(uki.image.needAlphaFix)return uki.theme.airport.background("checkbox-focus");var q=uki.theme.imageSrc("radio-focus");return new uki.background.CssBox("",{innerHTML:'<div style="position:absolute;left:50%;top:50%;width:24px;height:24px;overflow:hidden;margin:-12px 0 0 -12px; background: url('+
+q+') 0 0"></div>',zIndex:-2})},"toolbar-button-normal":function(){return new uki.background.Css("#CCC")},"toolbar-button-hover":function(){return new uki.background.Css("#E0E0E0")},"toolbar-button-down":function(){return new uki.background.Css("#AAA")},"toolbar-button-focus":function(){return new uki.background.Css("#CCC")},"toolbar-popup-button-normal":function(){return new uki.background.Css({textAlign:"left"})},"toolbar-popup-button-down":function(){return new uki.background.Css({background:"#AAA",
+textAlign:"left"})},"toolbar-popup-button-hover":function(){return new uki.background.Css({background:"#4086FF",color:"#FFF",textAlign:"left",textShadow:"none"})},"popup-normal":function(){return new uki.background.CssBox("background:#ECEDEE;border-radius:5px;border:1px solid #CCC;box-shadow:0 3px 8px rgba(0,0,0,0.6)")},panel:function(){return new uki.background.LinearGradient({startColor:"#DEDEDF",stops:[{pos:0.2,color:"#D4D4D5"},{pos:0.9,color:"#989899"}],endColor:"#989899",css:"box-shadow:0 1px 0 rgba(0,0,0,0.5);"})},
+input:function(){return new uki.background.CssBox("background:white;border: 1px solid #999;border-top-color:#777;box-shadow:0 1px 0 rgba(255, 255, 255, 0.4), inset 0 1px 2px rgba(0,0,0,0.2);")},"input-focus":function(){if(uki.browser.cssBoxShadow()=="unsupported")return new uki.background.CssBox("background:#7594D2;"+(uki.browser.cssFilter()&&uki.image.needAlphaFix?"filter:Alpha(opacity=70);":"opacity:0.7;"),{inset:"-2 -2",zIndex:-2});return new uki.background.Css({boxShadow:"0 0 6px #0244D4"})},
+"slider-handle-normal":function(){return new uki.background.LinearGradient({startColor:"#FFFFFF",endColor:"#B1CEEA",innerHTML:B(),css:"border:1px solid #8393A6;border-bottom-color:#687482;box-shadow:0 0 2px rgba(0,0,0,0.5);"})},"slider-handle-hover":function(){return new uki.background.LinearGradient({startColor:"#DFF6FF",endColor:"#8AC5F3",innerHTML:B(),css:"border:1px solid #8393A6;border-bottom-color:#687482;box-shadow:0 0 2px rgba(0,0,0,0.5);"})},"slider-handle-focus":function(){if(uki.browser.cssBoxShadow()==
+"unsupported")return new uki.background.CssBox("background:#7594D2;"+(uki.browser.cssFilter()&&uki.image.needAlphaFix?"filter:Alpha(opacity=70);":"opacity:0.7;"),{inset:"-2 -2",zIndex:-2});return new uki.background.CssBox("box-shadow: 0 0 6px #0244D4;",{zIndex:2,inset:"1"})},"slider-bar":function(){uki.dom.offset.initializeBoxModel();return new uki.background.CssBox("overflow:visible;",{inset:uki.dom.offset.boxModel?"0 2 0 0":"0 0",innerHTML:'<div style="'+uki.browser.css("position:absolute;left:0;overflow:hidden;width:100%;top:50%;height:3px;margin-top:-2px;background:#C6C7CD;border:1px solid #777;border-radius:3px;box-shadow:0 1px 0 rgba(255,255,255,0.5),inset 0 1px 1px rgba(0,0,0,0.2)")+
+'"></div>'})},list:function(q){return new uki.background.Rows(q,"#EDF3FE")},"table-header":function(){return new uki.background.LinearGradient({startColor:"#FFFFFF",stops:[{pos:0.8,color:"#e0e0e0"}],endColor:"#EEEEEE",css:"border-bottom:1px solid #CCC;"})}},images:{checkbox:function(){return uki.image(t("checkbox/normal.png"),"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAABsCAYAAABn5uLmAAAF1UlEQVRYw+2Y3VMTVxjGve9N/4T+CU4vetErpxfauzp1puNFZ3Ckah1jibWOGqhFhYpY8AvBIoygiErwIxAI+UCQhhgkEQwgIQKiIXwJBEKABIJP913dlJNddE8602mrO/ObIee8z8Oe3cnL87JuXZLXJ+vXf/zp519kyjYM7nHwsOtgDrakaLDhy6+3xU30jucgXr2CKsorq7H3yGno8sqwZZtmc9zoWpMP0eUVVdxrcWDHT1k4VVaP/KsWMMe63PAY4UhM5Okzf/znRDp7fEjV/oLjRdUorm5GidHNGhUb2hCcX0azw41NX22Fy+MVP6/GPx7Ed2kZyMi/gtMVFjz2+UE6xqigqgUvQ1GcLa7A9n2Z2K45hGcjU+KahFaXhf1ZhcgrN6Hufoe4RjrGiM46EoxgYCSIvNI70B49jx1pOvj8k+J6ztlL2H04FydLa1F4wyquEbJnlFNSg+eTiyLtvX5kF+mxJ/0UUn84jIo7ZqTsTcexwirhburFfamWdIzRcaGof3whjtXZi8xzlUg7cga7DmQjI68cuaU1uNPoZupIxxj9LDzA3pEwQ2W9A1nCnWULbyjn0l0U3TDLakjHGB08UQyPf07GhesNwnMxIPuiHu39k7J90jFG+46eg3soJMPRN4ETRTdxu6lTcZ90jNEeXS6cAzPckI4x2rn/GIieQFg1kkb27d+m0YGXde/Z1eruBi8yk8ZWF4iVlVeqkTSMkanJgdjKCjekYxt/wz0sx2LckI4xqjKYsLS8zA3pGKMKvSEpI9IxRiVXbyK6tMQN6RijotIriESj3JCOMTpTeAkLkSg3pGOMTuYXYH4xwg3p2J594jeEFxa5IR1jlHE0G3PzC9yQjjE6oDuCUHieG9IxRmn7D4HgefWSRvbF3a3Rgpf/aSP8ZzKk2uutGTImhEw1NNudb8+QS0IfJob8gfjPifT09SN135sMeeu+coYMR1dgb3uETZu3iqGTPq9mbGoWO7R/ZciegWHlDDknpNaC0mtihkzdewijkzPimsSP6dnxDGm2d76uV8qQs4sxjE7P4fTl1xlylzYdgZcz4nrehdJ4hrxYZRPXCMUMORVeEukZCIjBijLkTq0OeqM1niHzhQxJ+1KtYoacEOKuhL2jTzFDmloeMXWKGXJkJsJw2+pkMmSJ3iqrUcyQL6YWZRRXWcQM+atwVF8gKNtXzJDPJxdk9A1P41RxFawPuhT3FTPk4MswN2tmyEBwUTUfMuTfuHg7JCEz4Z2yCUnDGPFM2ash3ZpTNg+kU5yyeVlzyuZlzSmbl7dO2Ty8c8pWi6opWw2qp+x3oXrKfhcfpuz/2rVx48aPUlK+/Ua2MRwYAA93DXpkHsvQazTfb4ibDA4+AREVZjA19PV5UVNbDZutDpmZ6Z/Fjbx9j1WbjI2NCgZmPHnigdOZ0EY8Xe2riiNrmszNhdDQUIfOThf6+73o6HSyRi5Xq1g4MTGKiopyzMxOKxqZTEY8fNiK3t4uLCyEQTrGyOFoFgs7OlyoN9XCaKyRmdhsFvxhb0J3dydGR4fFNdIxRk1N5rjA43GjudmGurpa4fOiuNbW5oDVZhL3vN7ueC3pGCOzxRjfnBWO5XI5hTswCXd2Fz5fr/CGbgtrD9DV9Ujcl2pJxxjVGm+9ecivGRsfFu7CjsZGM0wNRuEILcKx2+H3DzJ1pGOMqm9dFzbCDPRW2oWH6XI5xDukZ5NYQzrGqLLyivAWQjLoKC73AzxstyMUmpLtk44xKisrQTg8LSM4MyEecWioX3GfdOxf2uILwm+c5IZ0jNH5gjMgIpGwaiSN7Nufl58LXt63DDk8/AK8yEwGBp6CiAn/yVOLpGGMvN4eLhMJ0rEd0tORlBHpEjpkW1JGpEvokPakjEiX0CFtSRmRjjGyWExJGZGOMTIaDUkZkY7tkNX6pIxIl9AhryVlRLqEDnk5KSPSJXTI35MyIh07ixScB8FjImlkX9z8/Dzw8u/uZ38Cqx5HdHgrjesAAAAASUVORK5CYII=",
+t("checkbox/normal.gif"))},"checkbox-focus":function(){return uki.image(t("checkbox/focus.png"),"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABdklEQVRIie2Wv0rDUBSHUxRFRHQRFxWRioTeZHEXVxHqYgaRNrmrj+Ai+AAdi6trH0AQh6K05uYpfBQ934m0EnHQcIuDB+5y7/l9J+dPchMEU2sER8P5VjJY2ExeljY6D8v77dHKbvK4+t3iHD/80aFXzhdLBnPN4/tFBE37vN7qjrbDzO2ZTh6GnbExXRdXl+5zLn74o1O9cOB9xjfY3MmGa8Y+bSGO0vGhSYuT2Lqz2BbnUZpfVBf7nOOn/gQWPRwNMslE0iIyh1HqDiLrTuPMXZqsuI5s3hNQX/Zuq6vcz3v44Y8OPRx4H+UKAmpHevrkCi+ubu5e33660KGHo2WmJ5g2SGpImjzJb+DTIJKJcODB1QBMgTZUakm6dQKghwMPrgbQ+ss00DBqWidA2TNpvPC0DxjzzMiVU1H0a5VI9HDgwZ0EKEdTxk+mo14GMmHCgfcf4I8F8DpF3t8D72+y92+R96+p9/vA/402gzt5Bn8VHu0d2HhIetPffvAAAAAASUVORK5CYII=")},
+radio:function(){return uki.image(t("radio/normal.png"),"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAABsCAYAAABn5uLmAAAGiklEQVRYw+2X/08TdxjH/RP6Z+2XJWeyzMTMjGkkEkmsog4VzSGiA0FUQAPIUHEqMC0i3Rgt66Sj8q0wkG9tgV4RAflij7b0C198du8Ozn6uPe66uGSJXvLO5fk8r+cNP/TeeT579vxXj8nmNTxzvuVbnEtCy8ASyZJqnKOvadLg8HKNDp/Y61khf2id3r8nWahxjj44VZN66xjX2OmmRTFMsY0tVaEPDnySSZnJZrhjdoqzyyEKRTc1BQ485hijyiY739btIv/ahm6BxxxjdO2BRXDPvqOlQExWR1c/fbnvO/riq2/jb9SJffCYY4x+uNNCc/4oowNHTtOpK9WUX9EQf6NWMphjjPIrHpNvOcLImF9O5Q1/UFVzd/yNWslgjjE6V1JHk2/DjPJK66j8sY0qJBO8USsZzDFGOQWVQtfIaxqfC8my9E7Q1dpnVFJnjr9RJ/bBY44xyj57lS+//5yGXwd1CzzmGKMMI2/IzLkotvdPkVMIaAoceMwl/Si/yTrNZZ7gydztpu4pUVXogwOv+pl8fSib23fomMhfryPTn+Nkd/tlocY5+uA0P1yOyzBw+zP5vfsPC3v3Z9IHHRZwjv6eT/yx2QYN9r5XfJdzRJBECRJwjr6mibWzn7N29Yujbi+tBtdoa+u9LNQ4Rx+cqskvVgdnsfeQPxCkza0tVaEPDnxy4JtMhua2DnHFv0obm5uaAgcec2zom8x8t3OI1jc2dAs85hijB03NwuLySlpG4DHHGN2pb6DY+nrawhxjdLv2PkVjsbSFOcbo+q0aCkdjaQtzjFFRaYXgm52jtUhUt8Bjjg3/wqv802etFApHdAs85hgjI88bzuUXilOCj4JrYU2BA4+5pB9lzpk87vzFQvJMeykQWlMV+uDAq34m2TmnuWM534t36x+RyzMlfV8hWahxjj44zQ83I8NoOJJt5LOOGoWsbCPJkmqco/+pJyR2RFPfPN/sXMDOSDtCjfM0dkhB7PMskxiKUeKDGufoa+yQLq7RPknLgShtSsunmtAHBz7FDjlo+LHtL/GttGiuS/msJXDgMccYVZle8lZpMQjFtnQLPOYYo4rGTsG3uEpBaWPdkb1nQNodD27vkAfjdWIfPObYZbS+nVYjm4wOZCl2SKlWMphLWkZXpM0+Ual2SCWTtIwW3GqkRWnlTdT50rvMDolayWCOMcorvS+8ml6gOTEqyzHkoZLtHRJv1Il98JhjjE4XVvF1TzpoZiWiW+Axp0jIMsPx/Jti/8SstD+HNQUOPOaSfpRZZ4q4E/k3qGdMoOmlkKrQBwde9TPJzOE5bKs3655Q1/A0uReCslDjHH1wuhIyI/ssn3H0rCC9SdY/9eeE/NgJ2SsloV9KRPaWHaPedBJyUYxo3LIj2gmZzi1bNSHb+ybTumWDT5mQnjd+5hatJfApE1J5g9ajlAmpvEHrUcqEVN6g9ShlQjrG3jC3aC2BT5mQVY3WtG7Z4FUT0jog6Lplg9s1IY/z1+nXvqldb9nog9OVkEXVTdTy0sPcslHj/HNCfsTH6x00eL3jvFeYEASfi3aEOn7u1XHLdrkGOZd7SJyfn6FwBLfsLVmocY4+OFWTweEebnRsgNbWQoyBUuiDA59kYrPZDL19nWIoFNjVZEfgwGOOMXrxwsK7XKO6THYEHnOMkcXSKqyuikl/1e0ZoQnXUPyt/G/BY44xet76NOkvjow6aWi4VxZqJYM5xqjp54dJUE+PPUlKBnOM0U8P70qNTUYOxwuy2ztkoVYymGOMamurhaWleakZlbW8PE+/236jdos5/kad2AePOcaosvIGb7W20fp6WLfAY44x4qXbcum1YnH2jZei0YCmwIHnU92yL18u4K6VFdPMzCSFw6Kq0AcHXvUzycs7w124cE5sanpE09MTFAyuyEKNc/TBaX64RqPRkJt7kpck5Oaeog9CfZJH/3NCfqSEdG8nZESRkJHthHRrJeTwcD83Pj6oKyHBgU+ZkP1Oh5SQQZ0JGSTwSQnZ1WXjPZ6JtBISPOYYo85OixAIrKZlBB5zyqhNy2RHmGOMWs2mf2WEOcboqakhKf30CHOM0aPH9cLKuwUmAbUEHnOM0b17NbzdbksrIcFjjjEqKyszVFffln7RPl0JCQ485pJ+lOXlZVxNzW2anZ3eNSHRBwde9TMpKStC3IpmczP5fB4mIVHjHH1wmh8uAv1KUQFfVHxJKCoupA+6JOA8ZeD/L5+/ASNtA71vTxEVAAAAAElFTkSuQmCC",
+t("radio/normal.gif"))},"radio-focus":function(){return uki.image(t("radio/focus.png"),"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABcElEQVRIx92VP0oEMRTGt9ADeAERq21E8ACLNp7AUrRbWdzdDDa6IBhnM5YqaCPCiiewyPzBA1gK1oIsHsNCNL9owBl1HYWM6MBjSPK+7+W99yWp1X7jm5dyrLUVTwTyYqotdb0dJjOdUM86Y8w86/jhX5q82TwZF1JPrkd6rtNPF40tiTBb7qps1Rlj5lnHD39wpchbMp4WKmmIKF0x/+0gSg8DlZ0KlZ05Y8w8669+DXAjgzjy7m68IPrJmoiS/Y29S907vr7fGdw+hOfDJ2eMmWcdP/zBfRqEGpKm3TnkKj3aPLi6kYO7x7fERWMdP/xfcAZveN71hEbZWpKu2RGgUcRFs0HIxODhgS8XADXQMGpK2l/t/KNMbLkMHh74cgGQHKqgcdT2O+TOwIGHB758AKNrpIc6ig0ta+Cs2gwPfLkAHB6rbyPBn5A7Aw8PfP8sgPceeFeR93Pg/SR7v4u836aVvAeVvGiVvMl/7nsGaBHOn+3vxvEAAAAASUVORK5CYII=")}},
+imageSrcs:{x:function(){return[t("x.gif"),"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII="]},"splitPane-horizontal":function(){return[t("splitPane/horizontal.gif"),"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAICAYAAAA870V8AAAAFUlEQVQIW2MoLy//zwAEYJq6HGQAAJuVIXm0sEPnAAAAAElFTkSuQmCC"]},"splitPane-vertical":function(){return[t("splitPane/vertical.gif"),"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAADCAYAAABfwxXFAAAAE0lEQVQIHWMsLy//z0AOYMSnEwAIngTLoazFLgAAAABJRU5ErkJggg=="]}},
+templates:{"table-header-cell":function(){return new uki.theme.Template('<div style="position:relative;border-right:1px solid #CCC;${style}" class="${className}">${data}</div>')},"table-cell":function(){return new uki.theme.Template('<div style="position:relative;border-right:1px solid #CCC;height:100%;${style}" class="${className}">${data}</div>')},"table-header-cell-asc":function(){return new uki.theme.Template('<div style="position:relative;border-right:1px solid #CCC;background: rgba(0,0,128,0.1);${style}" class="${className}"><div style="padding-right:7px">${data}</div><span style="position:absolute;right:0;top:50%;margin-top:-7px;">&darr;</span></div>')},
+"table-header-cell-desc":function(){return new uki.theme.Template('<div style="position:relative;border-right:1px solid #CCC;background: rgba(0,0,128,0.1);${style}" class="${className}"><div style="padding-right:7px">${data}</div><span style="position:absolute;right:0;top:50%;margin-top:-7px;">&uarr;</span></div>')}},doms:{resizer:function(q){var w=new uki.theme.Template("position:absolute;width:5px;top:0;right:-3px;height:${height}px;cursor:col-resize;cursor:ew-resize;z-index:101;background:url("+
+uki.theme.imageSrc("x")+")");q=uki.createElement("div",w.render({height:q}));if(!q.style.cursor||window.opera)q.style.cursor="e-resize";return q},"splitPane-vertical":function(q){q=q.handleWidth==1?uki.createElement("div","position:absolute;z-index:100;-moz-user-focus:none;font-family:Arial,Helvetica,sans-serif;width:100%;height:5px;margin-top:-2px;cursor:row-resize;cursor:ns-resize;z-index:200;overflow:hidden;background: url("+uki.theme.imageSrc("x")+")",'<div style="position:absolute;z-index:100;-moz-user-focus:none;font-family:Arial,Helvetica,sans-serif;background:#999;width:100%;height:1px;left:0px;top:2px;overflow:hidden;"></div>'):
+uki.createElement("div","position:absolute;z-index:100;-moz-user-focus:none;font-family:Arial,Helvetica,sans-serif;width:100%;height:"+(q.handleWidth-2)+"px;border: 1px solid #CCC;border-width: 1px 0;cursor:row-resize;cursor:ns-resize;z-index:200;overflow:hidden;background: url("+uki.theme.imageSrc("splitPane-vertical")+") 50% 50% no-repeat;");if(!q.style.cursor||window.opera)q.style.cursor="n-resize";return q},"splitPane-horizontal":function(q){q=q.handleWidth==1?uki.createElement("div","position:absolute;z-index:100;-moz-user-focus:none;font-family:Arial,Helvetica,sans-serif;height:100%;width:5px;margin-left:-2px;cursor:col-resize;cursor:ew-resize;z-index:200;overflow:hidden;background: url("+
+uki.theme.imageSrc("x")+")",'<div style="position:absolute;z-index:100;-moz-user-focus:none;font-family:Arial,Helvetica,sans-serif;background:#999;height:100%;width:1px;top:0px;left:2px;overflow:hidden;"></div>'):uki.createElement("div","position:absolute;z-index:100;-moz-user-focus:none;font-family:Arial,Helvetica,sans-serif;height:100%;width:"+(q.handleWidth-2)+"px;border: 1px solid #CCC;border-width: 0 1px;cursor:col-resize;cursor:ew-resize;z-index:200;overflow:hidden;background: url("+uki.theme.imageSrc("splitPane-horizontal")+
+") 50% 50% no-repeat;");if(!q.style.cursor||window.opera)q.style.cursor="e-resize";return q}},styles:{base:function(){return"font-family:Arial,Helvetica,sans-serif;"},label:function(){return"font-size:12px;"},button:function(){return"color:#333;text-align:center;font-weight:bold;text-shadow:0 1px 0 rgba(255,255,255,0.6);"},input:function(){return"font-size:12px;"}}});uki.theme.airport.backgrounds["slider-handle-down"]=uki.theme.airport.backgrounds["slider-handle-hover"];uki.theme.airport.backgrounds["toolbar-popup"]=
+uki.theme.airport.backgrounds["popup-normal"];uki.theme.airport.backgrounds["toolbar-popup-button-disabled"]=uki.theme.airport.backgrounds["toolbar-popup-button-normal"];uki.theme.register(uki.theme.airport)})();
diff --git a/lib/uki-more-0.3.8.min.js b/lib/uki-more-0.3.8.min.js
new file mode 100644 (file)
index 0000000..b7fe1af
--- /dev/null
@@ -0,0 +1,39 @@
+uki.more={};uki.more.utils={range:function(d,a){for(var h=new Array(a-d),b=0;d<=a;d++,b++)h[b]=d;return h}};uki.extend(uki,uki.more.utils);uki.more.view={};uki.viewNamespaces.push("uki.more.view.");uki.more.view.treeList={};
+uki.view.declare("uki.more.view.TreeList",uki.view.List,function(d){function a(b,e,g){for(e=e;e<b.length;e++)b[e]+=g}function h(b){b=uki.attr(b,"children");for(var e=b.length,g=0;g<b.length;g++)if(b[g].__opened)e+=h(b[g]);return e}this._setup=function(){d._setup.call(this);this._render=new uki.more.view.treeList.Render};this.listData=d.data;this.data=uki.newProp("_treeData",function(b){this._treeData=b;this._data=this._treeNodeToListData(b);b=false;for(var e=this.listData().length-1;e>=0;e--)if(this._data[e].__opened){b=
+true;this._openSubElement(e)}this.listData(this._data);b&&this.trigger("open")});this._treeNodeToListData=function(b,e){e=e||0;return uki.map(b,function(g){g.__indent=e;return g})};this.toggle=function(b){this._data[b].__opened?this.close(b):this.open(b)};this._openSubElement=function(b){var e=this._data[b],g=uki.attr(e,"children");if(!g||!g.length)return 0;var i=g.length;e.__opened=true;this._data.splice.apply(this._data,[b+1,0].concat(this._treeNodeToListData(g,e.__indent+1)));for(e=g.length-1;e>=
+0;e--)if(this._data[b+1+e].__opened)i+=this._openSubElement(b+1+e);return i};this.open=function(b){if(this._data[b].__opened)return this;var e=this._openSubElement(b),g=uki.binarySearch(b,this._selectedIndexes),i=this._lastClickIndex,c=this._selectedIndexes;this.clearSelection(true);a(c,g+(c[g]==b?1:0),e);this.listData(this._data);this.selectedIndexes(c);this._lastClickIndex=i>b?i+e:i;this.trigger("open");return this};this.close=function(b){var e=this._data[b],g=this._selectedIndexes,i=uki.attr(e,
+"children");if(!(!i||!i.length||!e.__opened)){i=h(e);e.__opened=false;this._data.splice(b+1,i);e=uki.binarySearch(b,g);for(var c=e+(g[e]==b?1:0),f=0,j=this._lastClickIndex;g[c+f]&&g[c+f]<=b+i;)f++;this.clearSelection(true);a(g,c,-i);f>0&&g.splice(e,f);this.listData(this._data);this.selectedIndexes(g);this._lastClickIndex=j>b?j-i:j;this.trigger("close")}};this._mousedown=function(b){if(b.target.className.indexOf("toggle-tree")>-1){var e=uki.dom.offset(this._dom);this.toggle((b.pageY-e.y)/this._rowHeight<<
+0)}else d._mousedown.call(this,b)};this._keypress=function(b){d._keypress.call(this,b);b=b.domEvent;if(b.which==39||b.keyCode==39)this.open(this._lastClickIndex);else if(b.which==37||b.keyCode==37)this.close(this._lastClickIndex)}});
+uki.more.view.treeList.Render=uki.newClass(uki.view.list.Render,new (function(){this._parentTemplate=new uki.theme.Template('<div class="${classPrefix}-row ${classPrefix}-${opened}" style="margin-left:${indent}px"><div class="${classPrefix}-toggle"><i class="toggle-tree"></i></div>${text}</div>');this._leafTemplate=new uki.theme.Template('<div class="${classPrefix}-row" style="margin-left:${indent}px">${text}</div>');this.initStyles=function(){this.classPrefix="treeList-"+uki.guid++;var d=(new uki.theme.Template(".${classPrefix}-row { color: #333; position:relative; padding-top:3px; } .${classPrefix}-toggle { overflow: hidden; position:absolute; left:-15px; top:5px; width: 10px; height:9px; } .${classPrefix}-toggle i { display: block; position:absolute; left: 0; top: 0; width:20px; height:18px; background: url(${imageSrc});} .${classPrefix}-selected { background: #3875D7; } .${classPrefix}-selected .${classPrefix}-row { color: #FFF; } .${classPrefix}-selected i { left: -10px; } .${classPrefix}-selected-blured { background: #CCCCCC; } .${classPrefix}-opened i { top: -9px; }")).render({classPrefix:this.classPrefix,
+imageSrc:"i/arrows.png"});uki.dom.createStylesheet(d)};this.render=function(d){this.classPrefix||this.initStyles();var a=d.data,h=uki.attr(d,"children");return h&&h.length?this._parentTemplate.render({text:a,indent:d.__indent*18+22,classPrefix:this.classPrefix,opened:d.__opened?"opened":""}):this._leafTemplate.render({text:a,indent:d.__indent*18+22,classPrefix:this.classPrefix})};this.setSelected=function(d,a,h,b){d.className=!h?"":b?this.classPrefix+"-selected":this.classPrefix+"-selected-blured"}}));
+uki.view.declare("uki.more.view.ToggleButton",uki.view.Button,function(d){this._setup=function(){d._setup.call(this);this._focusable=false};this.value=this.checked=uki.newProp("_checked",function(a){this._checked=!!a;this._updateBg()});this._updateBg=function(){this._backgroundByName(this._disabled?"disabled":this._down||this._checked?"down":this._over?"hover":"normal")};this._mouseup=function(){if(this._down){this._down=false;this._disabled||this.checked(!this.checked())}}});
+uki.view.declare("uki.more.view.RadioButton",uki.more.view.ToggleButton,function(){var d=uki.view.Radio;this.group=uki.newProp("_group",function(a){d.unregisterGroup(this);this._group=a;d.registerGroup(this);this.checked()&&d.clearGroup(this)});this.value=this.checked=uki.newProp("_checked",function(a){this._checked=!!a;a&&d.clearGroup(this);this._updateBg()});this._mouseup=function(){if(this._down){this._down=false;if(!this._checked&&!this._disabled){this.checked(!this._checked);this.trigger("change",
+{checked:this._checked,source:this})}}}});uki.more.view.splitTable={};
+uki.view.declare("uki.more.view.SplitTable",uki.view.Container,function(d){var a=uki.geometry.Rect,h=uki.geometry.Size,b="rowHeight data packSize visibleRectExt render selectedIndex focusable textSelectable multiselect".split(" ");this._defaultHandlePosition=200;this._headerHeight=17;this._style=function(c,f){this._leftHeader.style(c,f);this._rightHeader.style(c,f);return d._style.call(this,c,f)};this.columns=uki.newProp("_columns",function(c){this._columns=uki.build(c);this._totalWidth=0;this._leftHeader.columns([this._columns[0]]);
+this._columns[0].bind("beforeResize",uki.proxy(this._syncHandlePosition,this,this._columns[0]));for(c=1;c<this._columns.length;c++){this._columns[c].position(c-1);this._columns[c].bind("beforeResize",uki.proxy(this._rightColumnResized,this,this._columns[c]))}this._updateTotalWidth();this._rightHeader.columns(Array.prototype.slice.call(this._columns,1));this._splitPane.leftMin(this._columns[0].minWidth()-1);this._syncHandlePosition(this._splitPane)});uki.each(b,function(c,f){this[f]=function(j){if(j===
+undefined)return this._leftList[f]();this._leftList[f](j);this._rightList[f](j);return this}},this);this.hasFocus=function(){return this._leftList.hasFocus()||this._rightList.hasFocus()};this.rightColumns=function(){return this._rightHeader.columns()};this._rightColumnResized=function(){this._updateTotalWidth();this._horizontalScroll.layout()};this.rowHeight=function(c){if(c===undefined)return this._leftList.rowHeight();this._leftList.rowHeight(c);this._rightList.rowHeight(c);return this};this.data=
+function(c){if(c===undefined)return uki.map(this._leftList.data(),function(f,j){return[f].concat(this._rightList.data()[j])},this);this._leftList.data(uki.map(c,function(f){return[f[0]]}));this._rightList.data(uki.map(c,function(f){return f.slice(1)}));this._splitPane.minSize(new h(0,this._leftList.minSize().height));this._verticalScroll.layout()};this._createDom=function(){d._createDom.call(this);var c=uki.view.ScrollPane.initScrollWidth(),f=this.rect().height-this._headerHeight-c;uki([{view:"table.Header",
+rect:new a(this._defaultHandlePosition,this._headerHeight),anchors:"left top"},{view:"Box",className:"table-header-container",style:{overflow:"hidden"},rect:new a(this._defaultHandlePosition,0,this.rect().width-this._defaultHandlePosition-1,this._headerHeight),anchors:"left top right",childViews:{view:"table.Header",rect:new a(this.rect().width-this._defaultHandlePosition-1,this._headerHeight),anchors:"top left right",className:"table-header"}},{view:"ScrollPane",rect:new a(0,this._headerHeight,this.rect().width,
+f),anchors:"left top right bottom",className:"table-v-scroll",scrollV:true,childViews:[{view:"HSplitPane",rect:new a(this.rect().width,f),anchors:"left top right bottom",className:"table-horizontal-split-pane",handlePosition:this._defaultHandlePosition,handleWidth:1,leftChildViews:[{view:"List",rect:new a(this._defaultHandlePosition,f),anchors:"left top right bottom",className:"table-list-left"}],rightChildViews:[{view:"Box",rect:"0 0 100 100",anchors:"left top right bottom",style:{overflow:"hidden"},
+rect:new a(this.rect().width-this._defaultHandlePosition-1,f),childViews:{view:"ScrollPane",rect:new a(this.rect().width-this._defaultHandlePosition-1,f+c),scrollableV:false,scrollableH:true,anchors:"left top right bottom",className:"table-h-scroll",childViews:[{view:"List",rect:new a(this.rect().width-this._defaultHandlePosition-1,f+c),anchors:"left top right bottom"}]}}]}]},{view:"ScrollPane",rect:new a(this._defaultHandlePosition+1,f+this._headerHeight,this.rect().width-this._defaultHandlePosition-
+1,c),anchors:"left bottom right",scrollableH:true,scrollableV:false,scrollH:true,className:"table-h-scroll-bar",childViews:{view:"Box",rect:"1 1",anchors:"left top"}}]).appendTo(this);this._verticalScroll=uki("ScrollPane[className=table-v-scroll]",this)[0];this._horizontalScroll=uki("ScrollPane[className=table-h-scroll]",this)[0];this._horizontalScrollBar=uki("ScrollPane[className=table-h-scroll-bar]",this)[0];this._leftList=uki("List:eq(0)",this)[0];this._rightList=uki("List:eq(1)",this)[0];this._splitPane=
+uki("HSplitPane",this)[0];this._leftHeader=uki("table.Header:eq(0)",this)[0];this._rightHeader=uki("table.Header:eq(1)",this)[0];this._rightHeaderContainer=uki("[className=table-header-container]",this)[0];this._dummyScrollContents=uki("Box",this._horizontalScrollBar);this._leftList._scrollableParent=this._verticalScroll;this._rightList._scrollableParent=this._verticalScroll;this._verticalScroll.bind("scroll",uki.proxy(this._leftList._scrollableParentScroll,this._leftList));this._verticalScroll.bind("scroll",
+uki.proxy(this._rightList._scrollableParentScroll,this._rightList));this._leftList.render(new uki.more.view.splitTable.Render(this._leftHeader));this._rightList.render(new uki.more.view.splitTable.Render(this._rightHeader));this._bindEvents()};this._bindEvents=function(){this._splitPane.bind("handleMove",uki.proxy(this._syncHandlePosition,this,this._splitPane));this._horizontalScroll.bind("scroll",uki.proxy(this._syncHScroll,this,this._horizontalScroll));this._horizontalScrollBar.bind("scroll",uki.proxy(this._syncHScroll,
+this,this._horizontalScrollBar));this._leftList.bind("selection",uki.proxy(this._syncSelection,this,this._leftList));this._rightList.bind("selection",uki.proxy(this._syncSelection,this,this._rightList))};var e=false;this._syncHandlePosition=function(c){if(!e){e=true;var f;if(c==this._splitPane){c=this._splitPane.handlePosition()+1;this.columns()[0].width(c)}else{c=this.columns()[0].width();this._splitPane.handlePosition(c-1).layout()}this._leftHeader.rect(new a(c,this._headerHeight)).layout();f=this._rightHeaderContainer.rect().clone();
+f.x=c;f.width=this._rect.width-c-uki.view.ScrollPane.initScrollWidth();this._rightHeaderContainer.rect(f).layout();f=this._horizontalScrollBar.rect().clone();f.x=c;f.width=this._rect.width-c-uki.view.ScrollPane.initScrollWidth();this._horizontalScrollBar.rect(f).layout();e=false}};var g=false;this._syncHScroll=function(c){if(!g){g=true;var f=c==this._horizontalScroll?this._horizontalScrollBar:this._horizontalScroll;c=c.scrollLeft();f.scrollLeft(c);this._rightHeader.dom().style.marginLeft=-c+"px";
+g=false}};var i=false;this._syncSelection=function(c){if(!i){i=true;(c==this._leftList?this._rightList:this._leftList).selectedIndexes(c.selectedIndexes());i=false}};this._updateTotalWidth=function(){this._totalWidth=0;for(var c=1;c<this._columns.length;c++)this._totalWidth+=this._columns[c].width();this._rightHeader.minSize(new h(this._totalWidth,0));this._rightList.minSize(new h(this._totalWidth,this._rightList.minSize().height));this._dummyScrollContents.rect(new a(this._totalWidth,1)).parent().layout();
+this._rightHeader.minSize(new h(this._totalWidth,0));this._horizontalScroll.layout()}});uki.more.view.splitTable.Render=uki.newClass(uki.view.table.Render,new function(){this.setSelected=function(d,a,h,b){b=true;d.style.backgroundColor=h&&b?"#3875D7":h?"#CCC":"";d.style.color=h&&b?"#FFF":"#000"}});
+uki.view.declare("uki.more.view.Form",uki.view.Container,function(d){this._setup=function(){d._setup.call(this);uki.extend(this,{_method:"GET",_action:""})};this.action=uki.newProp("_action",function(a){this._dom.action=this._action=a});this.method=uki.newProp("_method",function(a){this._dom.method=this._method=a});this.submit=function(){this._dom.submit()};this.reset=function(){this._dom.reset()};this._createDom=function(){this._dom=uki.createElement("form",d.defaultCss);this._initClassName();this._dom.action=
+this._action;this._dom.method=this._method}});
+uki.view.declare("uki.more.view.Select",uki.view.Checkbox,function(d){this._backgroundPrefix="select-";this._popupBackground="theme(select-popup)";this._listBackground="theme(select-list)";this._popupOffset=0;this._setup=function(){d._setup.call(this);this._inset=new uki.geometry.Inset(0,20,0,4);this._focusable=this._selectFirst=true;this._options=[];this._maxPopupHeight=200;this._lastScroll=0};this.Render=uki.newClass(uki.view.list.Render,function(){this.render=function(a){return'<span style="line-height: 22px; text-align: left; white-space: nowrap; margin: 0 4px; cursor: default">'+
+a+"</span>"};this.setSelected=function(a,h,b){a.style.backgroundColor=b?"#3875D7":"";a.style.color=b?"#FFF":"#000"}});this.selectFirst=uki.newProp("_selectFirst");this.opened=function(){return this._popup.visible()&&this._popup.parent()};this.popupAnchors=function(a){if(a===undefined)return this._popup.anchors();this._popup.anchors(a);return this};this._createDom=function(){d._createDom.call(this);this.style({fontWeight:"normal",textAlign:"left"});this._label.style.overflow="hidden";this._popup=uki({view:"Popup",
+anchors:"left top",rect:"100 100",style:{zIndex:1E3},offset:this._popupOffset,background:this._popupBackground,relativeTo:this,visible:false,childViews:[{view:"ScrollPane",rect:"100 100",anchors:"left top right bottom",childViews:[{view:"List",rect:"100 100",anchors:"left top right bottom",rowHeight:22,textSelectable:false,focusable:true,background:this._listBackground,render:new this.Render,style:{fontSize:"12px"}}]}]})[0];this._popup.hide();this._list=uki("List",this._popup)[0];this._scroll=uki("ScrollPane",
+this._popup)[0];this._popup.bind("toggle",uki.proxy(function(){this._down=this._popup.visible();if(this._popup.visible()){this._updateWidth();this._scroll.scrollTop(this._lastScroll)}this._checked=this._popup.visible();this._updateBg()},this));this.bind(this._list.keyPressEvent(),function(a){if(this.preventTransfer)this.preventTransfer=false;else this._popup.visible()&&this._list.trigger(a.type,a)});this.bind("blur",function(){setTimeout(uki.proxy(function(){if(!this._hasFocus&&this.opened()){this._lastScroll=
+this._scroll.scrollTop();this._popup.hide()}},this),50)});this._list.bind("focus",uki.proxy(function(){this._hasFocus=false;this.focus()},this));this._list.bind("click",uki.proxy(this.selectCurrent,this))};this.contentsSize=function(a){var h=this.html();this.html(this._longestText);a=d.contentsSize.call(this,a);this.html(h);return a};this._keydown=function(a){if((a.which==32||a.which==13)&&this._popup.visible())this.selectCurrent(a);else if((a.which==40||a.which==38)&&!this._popup.visible()){this._popup.toggle();
+a.preventDefault();this.preventTransfer=true}else d._keydown.call(this,a)};this.selectCurrent=function(a){this.selectedIndex()==-1?this.text(this._selectFirst&&this._options[0]?this._options[0].text:""):this.text(this._options[this.selectedIndex()].text);this._lastScroll=this._scroll.scrollTop();this._popup.hide();a&&this.trigger("change",{source:this})};this.value=function(a){if(a===undefined)return this._options[this.selectedIndex()]?this._options[this.selectedIndex()].value:undefined;else{var h=
+-1,b,e=this._options.length,g;for(g=0;g<e;g++){b=this._options[g];if(b.value==a){h=g;break}}this.selectedIndex(h);this.selectCurrent()}};this.maxPopupHeight=uki.newProp("_maxPopupHeight");this._updateWidth=function(){if(!(this._widthCached||!this._options.length)){var a=this._list.dom().firstChild.firstChild.firstChild,h=a.innerHTML;a.innerHTML=this._longestText;this._widthCached=a.offsetWidth+8;a.innerHTML=h;this._popup.rect(new uki.geometry.Rect(this._popup.rect().x,this._popup.rect().y,Math.max(this._widthCached,
+this.rect().width),Math.min(this._maxPopupHeight,this._options.length*22))).layout()}};this.options=uki.newProp("_options",function(a){this._options=a;this._list.data(uki.map(a,"text")).selectedIndex(0);this._selectFirst&&a.length>0&&this.text(a[0].text);this._longestText="";uki.each(a,function(h,b){if(b.text.length>this._longestText.length)this._longestText=b.text},this);this._widthCached=false;this._lastScroll=0});uki.delegateProp(this,"selectedIndex","_list");this._updateBg=function(){return d._updateBg.call(this)};
+this._mousedown=function(a){d._mousedown.call(this,a);if(!this.disabled()){this._popup.toggle();this.trigger("toggle",{opened:this.opened()})}};this._mouseup=function(){if(this._down)this._down=false}});uki.Collection.addAttrs(["options"]);
+(function(){function d(h,b){return new uki.background.CssBox((b||"")+"background: url("+uki.theme.imageSrc(h)+"); background-position: 100% 50%; background-repeat: no-repeat;")}var a=uki.extend({},uki.theme.Base,{backgrounds:{"select-normal":function(){return new uki.background.Multi(d("select-handle-normal"),uki.theme.background("button-normal"))},"select-hover":function(){return new uki.background.Multi(d("select-handle-normal"),uki.theme.background("button-hover"))},"select-checked-normal":function(){return new uki.background.Multi(d("select-handle-normal"),
+uki.theme.background("button-down"))},"select-disabled":function(){return new uki.background.Multi(d("select-handle-normal","opacity:0.4;"),uki.theme.background("button-disabled"))},"select-popup":function(){return uki.theme.background("popup-normal")}},imageSrcs:{"select-handle-normal":function(){return["select-down-m.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAABkCAYAAABtnKvPAAAAeUlEQVRo3u3SMQ2AMBAFUCQgAQlIQUKTthq6IhEpOIAOTIWFABPvkr/c8IZ/15VStu6rgcPhcDgcDofD4XA4HA6HnybnvNRsF1ke4yGEoUJrA691379SS4xxavDx1c5TSvMBh08Oegv253A4HA6Hw+FwOBwOh8P/ie9z0RuWFOYPhAAAAABJRU5ErkJggg==",
+"select-down-m.gif"]}}});a.backgrounds["select-checked-hover"]=a.backgrounds["select-checked-normal"];uki.theme.register(a)})();
diff --git a/notes.md b/notes.md
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/Y/_intro.js b/src/Y/_intro.js
new file mode 100644 (file)
index 0000000..1fb369f
--- /dev/null
@@ -0,0 +1,4 @@
+(function(){
+
+this.Y = Y;
+
diff --git a/src/Y/_outro.js b/src/Y/_outro.js
new file mode 100644 (file)
index 0000000..22a7560
--- /dev/null
@@ -0,0 +1,2 @@
+
+})();
diff --git a/src/Y/alias.js b/src/Y/alias.js
new file mode 100644 (file)
index 0000000..cf46872
--- /dev/null
@@ -0,0 +1,13 @@
+var globals   = this
+,   _Object   = globals.Object
+,   _Function = globals.Function
+,   _Array    = globals.Array
+,   _String   = globals.String
+,   _Number   = globals.Number
+
+,   slice    = _Array.prototype.slice
+,   toString = _Object.prototype.toString
+,   hasOwn   = _Object.prototype.hasOwnProperty
+,   isArray  = _Array.isArray
+;
+
diff --git a/src/Y/core.js b/src/Y/core.js
new file mode 100644 (file)
index 0000000..d394536
--- /dev/null
@@ -0,0 +1,40 @@
+// Generic Collection Functions
+
+function notSelfOrWrapped(fn){
+    var self = arguments.callee.caller;
+    return fn && fn !== self && fn.__wraps__ !== self;
+}
+
+function reduce(o, fn, acc, cxt){
+    if ( !o )
+        return acc;
+    
+    if ( notSelfOrWrapped(o.reduce) )
+        return o.reduce.apply(o, slice.call(arguments,1));
+    
+    cxt = cxt || o;
+    for ( var name in o )
+        acc = fn.call(cxt, acc, o[name], name, o);
+    
+    return acc;
+}
+
+function attr(o, key, value, def){
+    if ( o && notSelfOrWrapped(o.attr) )
+        return o.attr.apply(o, slice.call(arguments,1));
+    
+    if ( value !== undefined || def !== undefined ){
+        o[key] = (value !== undefined ? value : def);
+        return o;
+    } else
+        return o[key];
+}
+
+function extend( A, B ){
+    return slice.call(arguments,1).reduce(function(A, donor){
+        return reduce(donor, function(o, v, k){
+            return attr(o, k, v, o[k]);
+        }, A);
+    }, A);
+}
+
diff --git a/src/Y/modules/event.js b/src/Y/modules/event.js
new file mode 100644 (file)
index 0000000..e211432
--- /dev/null
@@ -0,0 +1,68 @@
+function YEvent(type, target, trigger, data){
+    this.data = data || {};
+    // Y.extend(this, this.data);
+    
+    this.type = type;
+    this.target = target || trigger;
+    this.trigger = trigger || target;
+}
+
+function YEmitter(target, parent){
+    this.queues = {};
+    this.target = target; // || this;
+    
+    if (parent) {
+        parent = parent.__emitter__ || parent;
+        if (parent !== target) this.parent = parent;
+    }
+    
+    Y.bindAll(this);
+    
+    // target.addEventListener = addEventListener;
+    // target.removeEventListener = removeEventListener;
+    // target.fire = fire;
+    // target.dispatchEvent = dispatchEvent;
+    
+}
+
+YEmitter.prototype.getQueue =
+function(evt){
+    if ( !this.queues[evt] )
+        this.queues[evt] = [];
+    return this.queues[evt];
+};
+
+YEmitter.prototype.addEventListener =
+function(evt, fn){
+    this.getQueue(evt).push(fn);
+    return this;
+};
+
+YEmitter.prototype.removeEventListener =
+function(evt, fn){
+    var q = this.getQueue(evt)
+    ,   idx = q.indexOf(fn);
+    if (idx !== -1) q.splice(idx, 1);
+    return this;
+};
+
+YEmitter.prototype.fire =
+function(type, instance, data){
+    instance = instance || this;
+    var evt = new YEvent(type, data, instance, target);
+    this.dispatchEvent(evt);
+    return evt;
+};
+
+// XXX: does not handle degenerate or recursive event dispatch
+YEmitter.prototype.dispatchEvent =
+function(evt){
+    for (var i=0, q=this.getQueue(evt.type), L=q.length; i<L; i++)
+        q[i].call(evt.target, evt);
+        
+    if (this.parent) this.parent.dispatchEvent(evt);
+    
+    return evt;
+};
+
+
diff --git a/src/Y/modules/metaclass.js b/src/Y/modules/metaclass.js
new file mode 100644 (file)
index 0000000..3aebbbd
--- /dev/null
@@ -0,0 +1,322 @@
+// Inspired by John Resig's "Simple Class Inheritence" -- http://ejohn.org/blog/simple-javascript-inheritance/
+/*
+Metaprogramming API
+
+Events
+- "create" -- Fired when a new class instance is created. Data: self
+- "destroy" -- Fired when an instance is destoryed. Data: self
+- "init" -- Fired when the initialiser is invoked. Note that subclasses of the listened class will fire this event. Data: self
+- "subclass" -- Fired when a new subclass is created. Data: parent, self, body
+
+Customization Protocols
+- init() -- Initialiser; fires "init" event.
+- destroy() -- Should clean up resources and release weak references; fires "destroy" event.
+- extended(name, child, body) -- Called when a new subclass is created. Returns new child class.
+
+Class Methods
+- subclass(name, body) -- Fires "subclass" event.
+- fabricate() -- Generates a new class instance but does not inistialise it.
+- instantiate([args...])
+- methods()
+- properties()
+
+Note: Metaprogramming events cannot depend on Class.
+*/
+
+function ClassEvent(type, data, instance, cls){
+    this.data = data || {};
+    extend(this, this.data);
+    
+    this.type = type;
+    this.cls = cls;
+    this.instance = instance;
+}
+
+function ClassEmitter(target, parent){
+    var self    = target.__emitter__ = this
+    ,   queues  = self.queues = {};
+    self.target = target;
+    
+    // parent = (parent && parent.__emitter__) || parent;
+    if (parent && parent !== target) self.parent = parent;
+    
+    function getQueue(evt){
+        if ( !queues[evt] )
+            queues[evt] = [];
+        return queues[evt];
+    }
+    
+    this.addEventListener = target.addEventListener = addEventListener;
+    function addEventListener(evt, fn){
+        getQueue(evt).push(fn);
+        return target;
+    }
+    
+    this.removeEventListener = target.removeEventListener = removeEventListener;
+    function removeEventListener(evt, fn){
+        var q = getQueue(evt)
+        ,   idx = q.indexOf(fn);
+        if (idx !== -1) q.splice(idx, 1);
+        return target;
+    }
+    
+    this.fire = target.fire = fire;
+    function fire(type, instance, data){
+        var inst = instance || this
+        ,   evt = new ClassEvent(type, data, inst, target);
+        
+        for (var i=0, q=getQueue(evt.type), L=q.length; i<L; i++)
+            q[i].call(evt.target, evt);
+        
+        if (self.parent) self.parent.fire(type, instance, data);
+        
+        return evt;
+    }
+    
+}
+
+
+
+var KNOWN_CLASSES = {};
+
+// Private delegating constructor -- must be defined for every
+// new class to prevent shared state. All construction is
+// actually done in the init method.
+function _Class() {
+    var cls = arguments.callee
+    ,   instance = this;
+    
+    // Not subclassing
+    if ( cls.caller !== Y.Class.fabricate ) {
+        new ClassEmitter(this, cls);
+        cls.fire('create', instance);
+        if ( instance.init )
+            return instance.init.apply(instance, arguments);
+    }
+    
+    return instance;
+}
+
+
+/**
+ * Creates a new class. All classes inherit from Y.Class, and therefore support
+ * metaprogramming hooks and mixins.
+ * 
+ * @param {String} className Name of the Class.
+ * 
+ * @param {Type} [Parent] Parent class for subclassing. If Parent is a subclass of Y.Class
+ * itself, the new class will inherit directly from Parent. Otherwise, members will be
+ * copied off the prototype of Parent before applying any new instance members, but the
+ * new class will not respond to instanceof from Parent.
+ * 
+ * @param {Object} [members] Instance members to add to the new class's prototype; a class
+ * constructor can be supplied as `init`.
+ * 
+ * @returns {Class} A new Class.
+ */
+function Class(className, Parent, members){
+    var ClassFactory  = arguments.callee
+    ,   SuperClass = ClassFactory
+    ,   prototype  = this
+    ;
+    
+    if ( !members && !isFunction(Parent) ) {
+        members = Parent;
+        Parent  = null;
+    }
+    members = members || {};
+    Parent  = Parent  || Object.getPrototypeOf(members).constructor;
+    if (Parent == ClassFactory) Parent = Object;
+    var parentMembers = Parent.prototype;
+    
+    if (Parent.prototype instanceof ClassFactory) {
+        SuperClass = Parent;
+        prototype  = Parent.fabricate();
+        parentMembers = {};
+    }
+    
+    if ( !(prototype instanceof ClassFactory) )
+        return new ClassFactory(Parent, members);
+    
+    // Creates a new function with the appropriate name
+    // based on the className.
+    var NewClass, 
+    constructor = [
+        'var '+className,
+        (''+_Class).replace('_Class', className),
+        'NewClass = '+className, ''
+    ].join(';\n');
+    eval(constructor);
+    NewClass.prototype.toString = function(){ return this.className+"()"; };
+    
+    // Copy Class statics
+    for (var k in ClassFactory)
+        NewClass[k] = ClassFactory[k];
+    
+    // Copy parent methods, then add new instance methods
+    for (var k in parentMembers)
+        prototype[k] = parentMembers[k];
+    for (var k in members)
+        prototype[k] = members[k];
+    
+    prototype.constructor = NewClass;
+    NewClass.prototype = prototype;
+    
+    // Fix Constructors
+    NewClass.constructor = SuperClass;
+    var init = prototype.init
+    ,   initWrapper = function(){
+            var instance = this;
+            
+            if (init) {
+                var result = init.apply(instance, arguments);
+                if (result) instance = result;
+            }
+            
+            NewClass.fire('init', instance);
+            return instance;
+        };
+    initWrapper.__wraps__ = init;
+    NewClass.init = prototype.init = initWrapper;
+    
+    // Finalize
+    NewClass.className = prototype.className = className;
+    KNOWN_CLASSES[className] = NewClass;
+    
+    var ParentEmitter = (Parent.__emitter__ ? Parent : ClassFactory);
+    new ClassEmitter(NewClass, ParentEmitter);
+    ParentEmitter.fire('subclass', 
+        NewClass, {
+            className : className,
+            parent    : Parent,
+            child     : NewClass,
+            members   : members
+        });
+    
+    return NewClass;
+}
+
+// Decorate with emitter methods
+new ClassEmitter(Class);
+
+Y.Class = 
+Y.subclass = Class;
+
+/* Class Methods */
+
+/**
+ * Create a new instance and run delegate constructor if it exists.
+ * Unlike the keyword `new`, instantiate can be applied.
+ */
+Class.instantiate = 
+    function(){
+        var instance = this.fabricate();
+        if ( instance.init )
+            instance.init.apply(instance, arguments);
+        return instance;
+    };
+    
+/**
+ * Create new instance, but do not run the delegate constructor.
+ */
+Class.fabricate = 
+    function(){
+        var Cls = this;
+        return new Cls();
+    };
+
+/**
+ * Class method of Classes, not to be confused with Y.subclass, which is a static method.
+ */
+Class.subclass = 
+    function(className, members){
+        return new Class(className, this, members);
+    };
+
+/**
+`mixin` handles the common work involved in, well, "mixing it in" to a class: 
+copying the right methods and wrapping the constructor, as well as handling a
+couple of common options.
+
+Methods to be mixed into another class should attach to your Mixin's prototype; 
+if a prototype.init method is present, it will be wrapped along with the class's 
+default init method, ensuring they both run--the class first--with the correct 
+arguments. 
+
+Any Mixin you write should be a subclass of `Class`. If it possesses an init 
+method (that is, `Mixin.init` NOT `Mixin.prototype.init`), it should take a 
+class instance (such as the class prototype) and an options object. It may 
+modify the class instance if it wishes, but its return will be ignored. This
+method will run prior to the contents of your Mixin's prototype being copied
+onto (or over the contents of) the object. Methods (other than init) attached 
+to your Mixin itself will be ignored.
+
+`mixin` Options:
+- Bool proto: Whether to extend the prototype of the class, or the body. This 
+option will not be passed to your Mixin, but you will get the correct object 
+(an instance or the prototype) when invoked.
+- Bool duplicate: Whether to create a new subclass to mix into. This option will 
+not be passed to your Mixin.
+- Array init_args: Array to use as the arguments to init if `options.proto` is 
+false.
+
+Examples:
+
+// Given:
+var Dict = new Class({ ... });
+var Trackable = { ... };
+
+// Make class Trackable
+mixin( Trackable, Dict, { proto: true } ); 
+mixin( Trackable, Dict ); // Will be autodetected if `cls` is a Function and has a prototype
+
+// Make Trackable a copy of the class
+var TrackableDict = mixin( Trackable, Dict, { duplicate: true } );
+var TrackableDict = new mixin( Trackable, Dict ); // Assumes a request for a new instance should duplicate the class
+
+// Make instance Trackable
+mixin( Trackable, new Dict(), { proto: false } );
+mixin( Trackable, new Dict() ); // Will be autodetected if `cls` is not a Function
+
+// Make static class methods trackable
+mixin( Trackable, Dict, { proto: false } ); // You must force `mixin` to ignore the prototype
+
+
+*/
+function mixin( Mixin, Cls, options ){
+    options = Y({
+        proto     : (isFunction(Cls) && Cls.prototype),
+        override  : true,
+        methodize : false
+    }, options );
+    
+    var mproto = Y(Mixin.prototype).clone()
+    ,   target = options.proto ? Cls.prototype : Cls;
+    
+    // TODO: Metaprogramming protocols
+    
+    // Wrap the constructor so we run on initialization
+    // if ( mproto.init instanceof Function )
+    //     target.init = (target.init || ops.nop).fork(mp.init, true);
+    
+    // Extend class with mixin contents!
+    delete mproto['init'];
+    Y(target).extend( mproto );
+    
+    return Cls;
+}
+Y.mixin = mixin;
+
+
+
+Y.bindAll = bindAll;
+function bindAll(o){
+    for (var k in o)
+        if (isFunction(o[k])) o[k] = o[k].bind(o);
+    return o;
+}
+
+
+
+function YBase(){}
+YBase = Y.YBase = new Class("YBase");
diff --git a/src/Y/modules/y.event.js b/src/Y/modules/y.event.js
new file mode 100644 (file)
index 0000000..4d32a13
--- /dev/null
@@ -0,0 +1,101 @@
+(function(Y, undefined){
+
+
+var 
+
+/**
+ * A simple event.
+ * TODO: Detect jQuery event objects.
+ * TODO: If DOM event, wrap with consistent API.
+ * TODO: use jQuery if present to wrap events.
+ */
+YEvent = Y.YObject.subclass('YEvent', {
+    init : function( type, target, triggerTarget, data ){
+        data = data || {};
+        this.type = type;
+        this.target = target || triggerTarget;
+        this.triggerTarget = triggerTarget || target;
+        this._o = this.data = data;
+    },
+    
+    toString: function(){ return "YEvent("+this.type+")"; }
+}),
+
+/**
+ * A simple multicaster.
+ */
+
+methods = {
+    'init' : function(target){
+        this.target = target || this;
+        this.queues = {};
+        this.decorate(this); // i be so tricky tricky
+        this.decorate(target);
+    },
+    
+    getQueue : function(evt){
+        var Qs = this.queues;
+        if ( !Qs[evt] )
+            Qs[evt] = Y([]);
+        return Qs[evt];
+    },
+    
+    addEventListener : function(evt, fn){
+        this.getQueue(evt).push(fn);
+        return this.target;
+    },
+    
+    removeEventListener : function(evt, fn){
+        this.getQueue(evt).remove(fn);
+        // var q = this.getQueue(evt),
+        //     idx = q.indexOf(fn);
+        // if (idx !== -1)
+        //     return q.splice(idx, 1);
+    },
+    
+    fire : function(evtname, triggerTarget, data, async){
+        var self = this,
+            evt = new YEvent(evtname, self.target, triggerTarget, data);
+        if (async)
+            setTimeout(function(){
+                self.dispatchEvent(evt);
+            }, 10);
+        else
+            self.dispatchEvent(evt);
+        return evt;
+    },
+    
+    // XXX: does not handle degenerate or recursive event dispatch
+    dispatchEvent : function(evt){
+        // log.debug('dispatchEvent('+evt.type+')', this);
+        this.getQueue(evt.type).invoke('call', evt.target, evt);
+        // for (var i=0, q = this.getQueue(evt.type), L = q.length; i<L; i++)
+        //     q[i].call(evt.target, evt);
+        return evt;
+    },
+    
+    /**
+     * Decorates object with bound methods to act as a delegate of this hub.
+     */
+    decorate : function(delegate){
+        if (!delegate) return;
+        for (var k in methods)
+            if (k !== 'init') delegate[k] = methods[k].bind(this);
+        return delegate;
+    }
+},
+
+Emitter = Y.YObject.subclass('Emitter', methods),
+
+// Global Event Hub
+emitter = new Emitter();
+
+Y.event = {
+    Emitter : Emitter,
+    YEvent  : YEvent,
+    globalEmitter : emitter
+};
+
+emitter.decorate(Y.event);
+
+})(this.Y);
diff --git a/src/Y/modules/y.json.js b/src/Y/modules/y.json.js
new file mode 100644 (file)
index 0000000..ec4cde6
--- /dev/null
@@ -0,0 +1,52 @@
+(function(Y){
+
+function toJSON(o, k){
+    var prefix = typeof(k) === 'string' ? '"'+k+'":' : '',
+        json = new Y(o).toJSON();
+    return json ? prefix+json : '';
+}
+
+function install(json_fn){
+    var args = new Y(arguments),
+        tojson = args.shift();
+    args.each(function(YThing){
+        YThing.prototype.toJSON = json_fn;
+    });
+}
+
+function yNoJSON(){
+    return '';
+}
+
+function yToJSON(){
+    return '' + (this._o === undefined ? '' : this._o);
+}
+
+function quoteJSON(){
+    return '"' + this._o + '"';
+}
+
+// Collections
+
+function reducer(acc, v, k, o){
+    var json = toJSON(v, Y.is.array(o) ? 0 : k);
+    return json ? (acc ? acc+','+json : json) : acc;
+}
+
+var collectJSON = Y(function(a, z){
+    return a + this.reduce(reducer, '') + z;
+});
+
+
+// Public API
+
+Y.toJSON = toJSON;
+
+install( yNoJSON, Y.YGeneric, Y.YFunction, Y.YRegExp );
+install( yToJSON, Y, Y.YBoolean, Y.YNumber );
+install( quoteJSON, Y.YString );
+install( collectJSON.partial('{', '}'), Y.YCollection );
+install( collectJSON.partial('[', ']'), Y.YArray );
+
+
+})(this.Y);
diff --git a/src/Y/modules/y.kv.js b/src/Y/modules/y.kv.js
new file mode 100644 (file)
index 0000000..4bf85f8
--- /dev/null
@@ -0,0 +1,26 @@
+(function(Y, undefined){ if (!Y) return;
+
+var enc = encodeURIComponent, 
+    dec = decodeURIComponent;
+
+Y.YObject.prototype.toKV = toKV;
+function toKV(del){
+    return this.reduce(function(acc, v, k){
+        return acc.push( enc(k) + '=' + enc(v) );
+    }, Y([]))
+    .join(del !== undefined ? del : "&");
+}
+
+Y.YString.prototype.toObject =
+Y.YString.prototype.fromKV = fromKV;
+function fromKV(del){
+    return Y(this.split(del || '&'))
+        .reduce(function(acc, pair){
+            var kv = pair.split('='), k = kv[0], v = kv[1];
+            if (k) acc[k] = dec(v);
+            return acc;
+        }, {});
+}
+
+
+})(this.Y);
diff --git a/src/Y/modules/y.op.js b/src/Y/modules/y.op.js
new file mode 100644 (file)
index 0000000..4eed2b2
--- /dev/null
@@ -0,0 +1,100 @@
+(function(Y, undefined){ if (!Y) return;
+
+/* Functional Operators (Taste Great with Curry!) */
+Y.op = { 
+    
+    // comparison
+    cmp:    function(x,y){  return x == y ? 0 : (x > y ? 1 : -1); },
+    eq:     function(x,y){  return x == y; },
+    ne:     function(x,y){  return x != y; },
+    gt:     function(x,y){  return x  > y; },
+    ge:     function(x,y){  return x >= y; },
+    lt:     function(x,y){  return x  < y; },
+    le:     function(x,y){  return x <= y; },
+    
+    // math
+    add:    function(x,y){  return x  + y; },
+    sub:    function(x,y){  return x  - y; },
+    mul:    function(x,y){  return x  * y; },
+    div:    function(x,y){  return x  / y; },
+    flrdiv: function(x,y){  return Math.floor(x / y); },
+    mod:    function(x,y){  return x  % y; },
+    
+    // logic
+    and:    function(x,y){  return x && y; },
+    or:     function(x,y){  return x || y; },
+    xor:    function(x,y){  return x != y; },
+    not:    function(x){    return !x; },
+    neg:    function(x){    return -x; },
+    
+    // bitwise
+    bitnot: function(x){    return ~x; },
+    bitand: function(x,y){  return x  & y; },
+    bitor:  function(x,y){  return x  | y; },
+    bitxor: function(x,y){  return x  ^ y; },
+    lshift: function(x,y){  return x << y; },
+    rshift: function(x,y){  return x >> y; },
+    zrshift: function(x,y){ return x >>> y; },
+    
+    // typecast
+    bool:   function(x){    return !!x; },
+    number: function(x){    return Number(x); },
+    int:    function(x){    return parseInt(x); },
+    float:  function(x){    return parseFloat(x); },
+    str:    function(x){    return String(x); },
+    
+    // functional
+    I:      function(x){    return x; },
+    K:      function(k){    return function(){ return k; }; },
+    nop:    function(){},
+    
+    // accessors
+    isset:  function(o,def){ return o !== undefined ? o : def; },
+    has:    function(x,y){  return y in x; },
+    getkey: function(k,o){  return o[k] },
+    getdef: function(o,k,def){ return (k in o ? o[k] : def); },
+    set:    function(o,k,v){ if (o) o[k] = v; return o; },
+    
+    
+    // flow
+    either: function( test, t, f ){ return function(v){ return test(v) ? t : f; }; },
+    dowhile: function( test, action ){
+        return function(v){
+            var acc = v;
+            while( test(acc) ){ acc = action(acc); }
+            return acc;
+        };
+    },
+    dountil: function( test, action ){ 
+        return Y.ops.dowhile( function(v){ return !test(v); }, action ); 
+    },
+    forloop: function( test, action, pretest, posttest ){
+        pretest = pretest || Y.ops.I;
+        posttest = posttest || Y.ops.I;
+        return function(v){
+            for(var acc = pretest(v); test(acc); acc = posttest(acc) ){
+                acc = action(acc);
+            }
+            return acc;
+        };
+    },
+    repeat: function( action, n ){
+        return function(v){
+            for( var acc = v, i = 0; i < n; ++i ){ acc = action(acc); }
+            return acc;
+        };
+    },
+    maybe: function( pitcher, catcher ){
+        return function(){
+            var args = Y(arguments);
+            try { 
+                return pitcher.apply( this, args );
+            } catch(e) {
+                args.unshift(e);
+                return catcher.apply( this, args );
+            }
+        };
+    }
+};
+
+})(this.Y);
diff --git a/src/Y/modules/y.plugin-arch.js b/src/Y/modules/y.plugin-arch.js
new file mode 100644 (file)
index 0000000..e05b110
--- /dev/null
@@ -0,0 +1,170 @@
+
+Y.reduce = reduce;
+Y.attr   = attr;
+Y.extend = extend;
+
+Y.isFunction = isFunction;
+Y.isString = isString;
+Y.isNumber = isNumber;
+Y.isPlainObject = isPlainObject;
+
+
+
+
+function doPhase(phase, o, newY, args){
+    var ps      = Y.plugins.slice(0) // prevent mutation under iteration
+    ,   pslen   = ps.length
+    ,   capture = (phase === 'capture')
+    ;
+    
+    for ( var i=0, plugin=ps[i]; i<pslen; plugin=ps[++i] ) {
+        if ( !(phase in plugin) )
+            continue;
+        
+        // XXX: Should we clone args on each call to prevent mutation?
+        var r = plugin[phase](o, newY, args);
+        
+        if (r) {
+            // If this is the capture phase, we just matched a plugin,
+            // so we should return without running the bubbling phase
+            if (capture)
+                return r;
+            else
+                o = r;
+        }
+    }
+    
+    // If this is the capture phase, we should bubble to call as
+    // no plugins matched or we would have returned above.
+    if (capture)
+        return doPhase('call', o, newY, args);
+    else
+        return o;
+}
+
+/**
+ * Creates a Y wrapper around its input.
+ */
+function Y(o){
+    var A = arguments
+    ,   newY = (this instanceof Y);
+    
+    // Passthrough nulls
+    if (o === undefined || o === null)
+        return newY ? this : o;
+    
+    // Don't re-wrap Y-objects
+    if ( o.__y__ || o instanceof YBase )
+        return newY ? Y(o.end()) : o;
+    
+    // Invoke plugins
+    var _o = o
+    ,   args = slice.call(A, 0);
+    
+    // Capture phase, which will bubble to `call` if necessary
+    o = doPhase('capture', o, newY, args);
+    
+    // Post-processing phase
+    o = doPhase('finally', o, newY, args);
+    
+    // Wrap as generic object if nothing matched
+    if (o !== _o || o._o)
+        return o;
+    else
+        return new Y.YObject(o);
+}
+
+Y.plugins = [];
+Y.plugins.byName = {};
+
+/**
+ * @param {Object|Function} plugin should either be a function to be used as `call`, or an object
+ * with some set of methods from:
+ *  - call: Invoked during bubbling to process input. Any non-false-y return value will be passed
+ *      along as the Y() return value. All plugins with a `call` method will be invoked during
+ *      bubbling, so non-matching plugins should not return a value.
+ *  - capture: Invoked before bubbling to process input. A non-false-y return value will be
+ *      returned as the Y() return value. All plugins with a `capture` method will be invoked during
+ *      capture, and any truth-y response will prevent bubbling. It is therefore critical non-
+ *      matching plugins do not return a value.
+ *  - finally: Invoked after capture and/or bubbling to post-process the result. Any non-false-y
+ *      return value will be passed along as the Y() return value.
+ * Methods are invoked with:
+ *  - {Object} o: The current return value (or input)
+ *  - {Boolean} newY: Whether `Y()` was invoked with the `new` keyword.
+ *  - {Array} args: The original arguments, converted to an array.
+ */
+Y.plugin = function(plugin){
+    if ( isFunction(plugin) )
+        plugin = { 'call':plugin, 'name':plugin.name };
+    
+    plugin.name = plugin.name || plugin.className;
+    
+    // Register plugin
+    Y.plugins.push(plugin);
+    if (plugin.name) Y.plugins.byName[plugin.name] = plugin;
+    
+    return plugin;
+};
+
+// Y( arguments, start=0, stop=arguments.length )
+// Cast `arguments` object to a real Array, optionally slicing at specified delimiters
+Y.plugin({
+    'capture': function(o, newY, args){
+        if ( o.prototype === undefined
+                 && isNumber(o.length)
+                 && !isArray(o)
+                 && o.constructor === _Object )
+            return slice.call(o, args[1] || 0, args[2] || o.length);
+    }
+});
+
+Y.plugin({
+    'finally': function(o, newY, args){
+        return (newY ? Y(o) : o);
+    }
+});
+
+// Merge Arrays or Objects
+// Y([0,1], [2,3], [4,5])   -> [0,1,2,3,4,5]
+// Y({foo:1}, {bar:2})      -> { foo:1, bar:2 }
+Y.plugin({
+    'capture': function(o, newY, args){
+        if ((args.length > 1) && ( args.every(isArray) || args.every(isPlainObject) ))
+            return extend.apply(this, args);
+    }
+});
+
+// Convenience of Y(Y.range())
+Y.plugin({
+    'capture': function(o, newY, args){
+        if ( (args.length > 1) && args.every(isNumber) )
+            return Y.range.apply(this, A);
+    }
+});
+
+
+// We got random stuff: wrap an Array of it
+Y.plugin({
+    'capture': function(o, newY, args){
+        if (args.length > 1)
+            return new Y.YArray(args);
+    }
+});
+
+function getYType(o){
+    var name   = type_of(o)
+    ,   yname  = 'Y' + name.charAt(0).toUpperCase() + name.slice(1);
+    return Y[yname];
+}
+
+// Builtin type-specific wrappers
+Y.plugin({
+    'call' : function(o, newY, args){
+        var YType = getYType(o);
+        if ( YType && YType !== Y.YObject || YType !== Y.YBase )
+            return new YType(o);
+    }
+});
+
+
diff --git a/src/Y/modules/y.polyevent.js b/src/Y/modules/y.polyevent.js
new file mode 100644 (file)
index 0000000..81cca2b
--- /dev/null
@@ -0,0 +1,203 @@
+(function(){
+
+function PolyEvent(){
+    
+}
+
+/**
+ * @param defaultEvent Subclass of PolyEvent to act as the default event for calls to fire().
+ * @param target Target object, as with @see{EventDispatcher}.
+ */
+function PolyEventDispatcher(target) {
+    this._listeners = {};
+    this._match_cache = {};
+    this._target = target || this;
+}
+
+function getEventCls(subEvent, triggerType) {
+    return PolyEvent;
+}
+
+function addEventListener(type, listener) {
+    // trace(this+'.addEventListener( '+type+' )');
+    if ( !(type in this._listeners) )
+        this._listeners[type] = [];
+    this._listeners[type].push(listener);
+}
+
+function removeEventListener(type, listener) {
+    // trace(this+'.removeEventListener( '+type+' )');
+    
+    if ( !this._listeners[type] ) return;
+    
+    var idx = this._listeners[type].indexOf(listener);
+    if ( idx == -1 ) return;
+    
+    this._listeners[type].splice(idx, 1);
+}
+
+function hasEventListener(type) {
+    return !!(this._listeners[type] && this._listeners[type].length > 0);
+}
+
+function fire( triggerType, data, async, Cls ) {
+    data = data || {};
+    /*trace(this+'.fire( triggerType='+triggerType+', data='+data+', Cls='+Cls+' ) matches:');*/
+    var self = this,
+        dispatch = async ? this.dispatchEventAsync : this.dispatchEvent;
+    this.getMatchingEvents( triggerType+'' )
+        .forEach(function(subEvent) {
+            /*trace("  -", subEvent);*/
+            var EvtCls = Cls || self.getEventCls(subEvent, triggerType);
+            dispatch(new EvtCls(subEvent, triggerType, data));
+        });
+}
+
+function fireAsync( triggerType, data, Cls ) {
+    this.fire( triggerType, data, true, Cls );
+}
+
+function dispatchEvent( event ) {
+    if ( (event == undefined) || !(event.type in this._listeners) )
+        return false;
+    
+    var self = this;
+    this._listeners[event.type]
+        .slice(0)
+        .forEach(function(listener) {
+            listener.call( self._target, event );
+        });
+    return true;
+}
+
+function dispatchEventAsync( event ) {
+    setTimeout( dispatchEvent, 0, event );
+    return true;
+}
+
+
+// Private methods
+poly_dispatcher function targetListener(evt) {
+    dispatchEvent(evt);
+}
+
+function comb( L, n ) {
+    if (n <= 0)
+        return null;
+        
+    else if (n == 1)
+        return L.map(function (v) { return [v]; });
+        
+    else
+        return Basis.reduce( L, function ( acc, v, i ) {
+            return acc.concat(comb(L.slice(i+1), n-1)
+                .map(function ( suffix ) {
+                    return [v].concat(suffix);
+                }));
+        }, [] );
+}
+
+function powerset( L, n=-1 ) {
+    n = n !== -1 ? n : L.length;
+    var p = [];
+    for (var i=1; i < n+1; ++i)
+        p = p.concat( comb(L,i) );
+    return p;
+}
+
+function propertyset( T ) {
+    if ( !T || !T.length )
+        return [];
+    else
+        return unique(Basis.reduce(T, function( acc, t ) { 
+             return acc.concat( shove(t.split('-'), t) );
+        }, []).concat( T, powerset(T).map(joiner) ));
+}
+
+// 1. compose a string S into tokens
+// 2. separate the tokens with properties (T) from the plain tokens (TP)
+// 3. find the property set P = p(T)
+// 4. merge the powersets of t+P for t in T as PS
+// 5. return PS+event
+
+/**
+ * Returns an array of all events which match the given event.  
+ * @param event
+ */     
+function getMatchingEvents( event, base_event ) {
+    base_event = base_event || '';
+    if ( this._match_cache[event] === undefined ) {
+        
+        // Segregate event groups in a manner worthy of strict scrutiny
+        var seg = Basis.reduce( event.split('.'),  
+            function( seg, token ) {
+                if (base_event === '') base_event = token;
+                if (token.indexOf('-') != -1)
+                    seg.special.push(token);
+                else
+                    seg.plain.push(token);
+                return seg;
+            }, { plain: [], special: [] });
+        
+        // Calculate the correct powerset for each event-group
+        var evtset;
+        if ( !seg.special.length )
+            evtset = powerset( seg.plain ).map(sorter).map(joiner);
+        else {
+            evtset = Basis.reduce( propertyset( seg.special ),
+                function( acc, t ) {
+                    return acc.concat( 
+                            powerset( seg.plain.concat([ t ]) )
+                                .map(sorter)
+                                .map(joiner) );
+                }, [] );
+        }
+        // Remove invalid permutations and duplicates
+        this._match_cache[event] =
+            unique( 
+                evtset
+                    .filter(function( v ) { 
+                        return v.indexOf(base_event) === 0; 
+                    }) )
+            .reverse();
+        
+        
+        // Always notify 'all'
+        this._match_cache[event].push('all');
+    }
+    
+//            trace("getMatchingEvents( event="+event+", base_event="+base_event+" ) --> [ "+_match_cache[event].join(",\n  ")+" ]");
+    return _match_cache[event];
+}
+
+var _prefixes = [ 'state', 'core', 'chrome', 'video', 'share' ];
+var _suffixes = [ 'user', 'auto' ];
+
+function cmp( a, b ) {
+    var a_suffix = _suffixes.indexOf(a) !== -1;
+    var b_suffix = _suffixes.indexOf(b) !== -1;
+    var a_prefix = _prefixes.indexOf(a) !== -1;
+    var b_prefix = _prefixes.indexOf(b) !== -1;
+    if (a_prefix === b_prefix && a_suffix === b_suffix)
+        return 0;
+    else if (a_prefix || b_suffix)
+        return -1;
+    else
+        return 1;
+}
+
+function shove( A, v ) { A.push(v); return A; }
+function sorter( A ) { A.sort(cmp); return A; }
+function joiner( A ) { return A.join('.'); }
+
+function unique( A ) {
+    return Basis.reduce(A, function( acc, v ) {
+        return (acc.indexOf(v) == -1) ? shove(acc, v) : acc;
+    }, [] );
+}
+
+function toString() {
+    return '[PolyEventDispatcher target='+_target+']';
+}
+
+})();
\ No newline at end of file
diff --git a/src/Y/type.js b/src/Y/type.js
new file mode 100644 (file)
index 0000000..c23970c
--- /dev/null
@@ -0,0 +1,52 @@
+// Type Utilities //
+// Much borrowed from jQuery
+
+var class2type = "Boolean Number String Function Array Date RegExp Object"
+    .split(" ")
+    .reduce(function(class2type, name) {
+        class2type[ "[object "+name+"]" ] = name.toLowerCase();
+        return class2type;
+    }, {});
+
+function type_of(obj){
+    return obj == null ?
+        String( obj ) :
+        class2type[ toString.call(obj) ] || "object";
+}
+
+function isFunction(obj) { return type_of(obj) === "function"; }
+
+function isString(obj)   { return type_of(obj) === "string"; }
+
+function isNumber(obj)   { return type_of(obj) === "number"; }
+
+
+// A crude way of determining if an object is a window
+function isWindow( obj ) {
+       return obj && typeof obj === "object" && "setInterval" in obj;
+}
+
+function isPlainObject( obj ){
+    // Must be an Object.
+    // Because of IE, we also have to check the presence of the constructor property.
+    // Make sure that DOM nodes and window objects don't pass through, as well
+    if ( !obj || type_of(obj) !== "object" || obj.nodeType || 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.
+    
+    var key;
+    for ( key in obj ) {}
+    
+    return key === undefined || hasOwn.call( obj, key );
+}
+
+
diff --git a/src/Y/y-array.js b/src/Y/y-array.js
new file mode 100644 (file)
index 0000000..de7a26c
--- /dev/null
@@ -0,0 +1,74 @@
+var
+YArray =
+Y.YArray =
+YCollection.subclass('YArray', {
+    'init': function(o){
+        YCollection.init.call(this, o || []);
+    }
+});
+
+YArray.prototype.merge  =
+YArray.prototype.concat = concat;
+function concat( donor ){
+    var A = this._o;
+    new Y(arguments).forEach(function( donor ){
+        A = A.concat(donor);
+    });
+    return Y(A);
+}
+
+YArray.prototype.remove = array_remove;
+function array_remove(v){
+    var idx = this.indexOf(v);
+    if ( idx != -1 )
+        this.splice(idx, 1);
+    return this;
+}
+
+YArray.prototype.unique = array_unique;
+function array_unique(){
+    // Executes in the context of the new array
+    return this.filter(function(v, i){
+        return (this.indexOf(v) === -1);
+    });
+}
+
+// Like map, but produces a dict
+// Y(["Foo?", "R&D"]).generate(encodeURIComponent)
+//  -> Y({"Foo?":"Foo%3F", "R&D":"R%26D"})
+YArray.prototype.generate = generate;
+function generate(fn, acc){
+    var args = Y(arguments),
+        fn = args.shift(),
+        acc = args.shift();
+    return this.reduce(function(acc, v, i){
+        return acc.attr(v, fn.apply( this, [v].concat(args) ));
+    }, Y(acc || {}));
+}
+
+// Y([ ["foo", 1], ["bar", 2] ]).toDict()
+//  -> Y({foo:1, bar:2})
+YArray.prototype.toDict = array_toDict;
+function array_toDict(){
+}
+
+
+
+YArray.prototype.clone = array_clone;
+function array_clone(){
+    return Y(this._o.slice(0));
+}
+
+YArray.prototype.size = function(){
+    return this._o.length;
+};
+
+YArray.prototype.toString = function(){
+    return this.className + "(" + (this._o || "") + ")";
+};
+
+
+YCollection.chainDelegates(YArray, 'push', 'unshift', 'sort', 'splice', 'reverse');
+Y.mixinNames(YArray, Array, ['reduce', 'map', 'forEach', 'filter', 'slice'],    true, true);
+Y.mixinNames(YArray, Array, ['indexOf', 'lastIndexOf', 'shift', 'pop', 'join'], true, false);
+
diff --git a/src/Y/y-class.js b/src/Y/y-class.js
new file mode 100644 (file)
index 0000000..d297e05
--- /dev/null
@@ -0,0 +1,157 @@
+// Inspired by John Resig's "Simple Class Inheritence" -- http://ejohn.org/blog/simple-javascript-inheritance/
+
+var KNOWN_CLASSES = {};
+
+// Private delegating constructor -- must be defined for every
+// new class to prevent shared state. All construction is
+// actually done in the init method.
+function _Class() {
+    var cls = arguments.callee
+    ,   instance = this;
+    
+    // Not subclassing
+    if ( cls.caller !== Y.Class.fabricate ) {
+        if ( instance.init ){
+            var result = instance.init.apply(instance, arguments);
+            if (result) instance = result;
+        }
+    }
+    
+    return instance;
+}
+
+
+/**
+ * Creates a new class. All classes inherit from Y.Class, and therefore support
+ * metaprogramming hooks and mixins.
+ * 
+ * @param {String} className Name of the Class.
+ * 
+ * @param {Type} [Parent] Parent class for subclassing. If Parent is a subclass of Y.Class
+ * itself, the new class will inherit directly from Parent. Otherwise, members will be
+ * copied off the prototype of Parent before applying any new instance members, but the
+ * new class will not respond to instanceof from Parent.
+ * 
+ * @param {Object} [members] Instance members to add to the new class's prototype; a class
+ * constructor can be supplied as `init`.
+ * 
+ * @returns {Class} A new Class.
+ */
+function Class(className, Parent, members) {
+    var ClassFactory  = arguments.callee
+    ,   SuperClass = ClassFactory
+    ,   prototype  = this
+    ,   parentMembers = {}
+    ;
+    
+    if ( !members && !isFunction(Parent) ) {
+        members = Parent;
+        Parent  = null;
+    }
+    members = members || {};
+    Parent  = Parent  || Object.getPrototypeOf(members).constructor || ClassFactory;
+    
+    if (Parent == ClassFactory)
+        Parent = Object;
+    
+    // Parent is the prototype
+    if ( !isFunction(Parent) ) {
+        SuperClass = Object.getPrototypeOf(Parent).constructor || Object;
+        prototype = Parent;
+        
+    // Parent is a constructor: check ClassFactory
+    } else if (Parent.prototype instanceof ClassFactory) {
+        SuperClass = Parent;
+        prototype  = Parent.fabricate();
+        
+    // Recurse so `this` is an instance of ClassFactory
+    } else if ( !(prototype instanceof ClassFactory) ) {
+        return new ClassFactory(Parent, members); 
+    
+    } else {
+        parentMembers = Parent.prototype || {};
+    }
+    
+    // Creates a new function with the appropriate name
+    // based on the className.
+    var NewClass, 
+    constructor = [
+        'var '+className,
+        (''+_Class).replace('_Class', className),
+        'NewClass = '+className, ''
+    ].join(';\n');
+    eval(constructor);
+    NewClass.prototype.toString = function(){ return this.className+"()"; };
+    
+    // Copy Class statics
+    for (var k in ClassFactory)
+        NewClass[k] = ClassFactory[k];
+    
+    // Copy parent methods, then add new instance methods
+    for (var k in parentMembers)
+        prototype[k] = parentMembers[k];
+    for (var k in members)
+        prototype[k] = members[k];
+    
+    // Fix Constructors, prototypes
+    NewClass.prototype = prototype;
+    prototype.constructor = NewClass;
+    NewClass.constructor = SuperClass;
+    if (prototype.init) NewClass.init = prototype.init;
+    
+    // Finalize
+    NewClass.className = prototype.className = className;
+    KNOWN_CLASSES[className] = NewClass;
+    
+    return NewClass;
+}
+
+Y.Class = 
+Y.subclass = Class;
+
+/* Class Methods */
+
+/**
+ * Create a new instance and run delegate constructor if it exists.
+ * Unlike the keyword `new`, instantiate can be applied.
+ */
+Class.instantiate = 
+    function(){
+        var instance = this.fabricate();
+        if ( instance.init )
+            return instance.init.apply(instance, arguments);
+        else
+            return instance;
+    };
+    
+/**
+ * Create new instance, but do not run the delegate constructor.
+ */
+Class.fabricate = 
+    function(){
+        var Cls = this;
+        return new Cls();
+    };
+
+/**
+ * Class method of Classes, not to be confused with Y.subclass, which is a static method.
+ */
+Class.subclass = 
+    function(className, members){
+        return new Class(className, this, members);
+    };
+
+
+Y.bindAll = bindAll;
+function bindAll(o){
+    for (var k in o)
+        if (isFunction(o[k])) o[k] = o[k].bind(o);
+    return o;
+}
+
+
+
+function YBase(){}
+YBase = Y.YBase = new Class("YBase", {
+    __y__ : true
+});
diff --git a/src/Y/y-collection.js b/src/Y/y-collection.js
new file mode 100644 (file)
index 0000000..70d0db6
--- /dev/null
@@ -0,0 +1,141 @@
+
+/** YCollection is the core of Y. */
+
+function bool(v){ return !!(v); }
+
+function extendY(){
+    extend.apply(this, [this._o].concat(Y(arguments)) );
+    return this;
+}
+
+var 
+YCollection =
+Y.YCollection =
+YBase.subclass('YCollection', {
+    'init' : function(o){
+        if (!o) return;
+        this._o = o;
+        // var proto = (o.constructor || {}).prototype || {};
+        // for (var k in proto) {
+        //     if (isFunction(proto[k]))
+        //         this[k] = bind.call(proto[k], o);
+        // }
+    },
+    
+    'attr'   : function(k, v, def){
+        var r = Y.attr(this._o, k, v, def);
+        if (r === this._o)
+            return this;
+        else
+            return r;
+    },
+    'extend' : extendY,
+    'merge'  : extendY,
+    'concat' : extendY,
+    
+    'end' : function(){ return this._o; },
+    
+    'reduce' : function( fn, acc, context ){
+        var o = this._o || this,
+            acc = acc || new o.constructor();
+        for ( var name in o )
+            acc = fn.call( context || this, acc, o[name], name, o );
+        return acc;
+    },
+    
+    'map' : function( fn, context ){
+        var o = this._o, acc = new o.constructor();
+        for ( var name in o )
+            acc[name] = fn.call( context || this, o[name], name, o );
+        return acc;
+    },
+    
+    'forEach' : function( fn, context ){
+        var o = this._o;
+        for ( var name in o )
+            fn.call( context || this, o[name], name, o );
+    },
+    
+    'filter' : function( fn, context ){
+        var o = this._o, acc = new o.constructor();
+        for ( var name in o )
+            if ( fn.call( context || this, o[name], name, o ) )
+            acc[name] = o[name];
+        return acc;
+    },
+    
+    'indexOf' : function( value ){
+        var o = this._o;
+        for ( var name in o )
+            if ( o[name] === value )
+                return name;
+        return -1;
+    },
+    
+    'clone' : function(){
+        return Y({}).extend(this);
+    },
+    
+    'remove' : function(v){
+        var o = this._o;
+        for (var k in o) {
+            if (o[k] !== v) continue;
+            delete o[k];
+            break;
+        }
+        return this;
+    },
+    
+    'every' : function( fn ){
+        var self = this, fn = fn || bool;
+        return this.reduce(function(acc, v, k, o){
+            return acc && fn.call(self, v, k, o);
+        }, true);
+    },
+    
+    'any' : function( fn ){
+        var self = this, fn = fn || bool;
+        return this.reduce(function(acc, v, k, o){
+            return acc || fn.call(self, v, k, o);
+        }, false);
+    },
+    
+    'zip' : function(){
+        var sequences = new Y(arguments).map(Y).unshift(this);
+        return this.map(function(_, k){
+            return sequences.invoke('attr', k);
+        });
+    },
+    
+    'pluck' : function(key){
+        return this.map(function(v){
+            return v && (isFunction(v.attr) ? v.attr(key) : v[key]);
+        });
+    },
+    
+    'invoke' : function(name){
+        var args = Y(arguments),
+            name = args.shift();
+        return this.map(function(o){
+            return o[name].apply(o, args);
+        });
+    }
+    
+});
+
+
+YCollection.chainDelegates = chainDelegates;
+function chainDelegates(type, name){
+    var names = Y(arguments, 1),
+        proto = type.prototype;
+    names.forEach(function(name){
+        proto[name] = function(){
+            var o = this._o || this;
+            o[name].apply(o, Y(arguments));
+            return this;
+        };
+    });
+    return type;
+}
+
+
diff --git a/src/Y/y-core.js b/src/Y/y-core.js
new file mode 100644 (file)
index 0000000..e05b110
--- /dev/null
@@ -0,0 +1,170 @@
+
+Y.reduce = reduce;
+Y.attr   = attr;
+Y.extend = extend;
+
+Y.isFunction = isFunction;
+Y.isString = isString;
+Y.isNumber = isNumber;
+Y.isPlainObject = isPlainObject;
+
+
+
+
+function doPhase(phase, o, newY, args){
+    var ps      = Y.plugins.slice(0) // prevent mutation under iteration
+    ,   pslen   = ps.length
+    ,   capture = (phase === 'capture')
+    ;
+    
+    for ( var i=0, plugin=ps[i]; i<pslen; plugin=ps[++i] ) {
+        if ( !(phase in plugin) )
+            continue;
+        
+        // XXX: Should we clone args on each call to prevent mutation?
+        var r = plugin[phase](o, newY, args);
+        
+        if (r) {
+            // If this is the capture phase, we just matched a plugin,
+            // so we should return without running the bubbling phase
+            if (capture)
+                return r;
+            else
+                o = r;
+        }
+    }
+    
+    // If this is the capture phase, we should bubble to call as
+    // no plugins matched or we would have returned above.
+    if (capture)
+        return doPhase('call', o, newY, args);
+    else
+        return o;
+}
+
+/**
+ * Creates a Y wrapper around its input.
+ */
+function Y(o){
+    var A = arguments
+    ,   newY = (this instanceof Y);
+    
+    // Passthrough nulls
+    if (o === undefined || o === null)
+        return newY ? this : o;
+    
+    // Don't re-wrap Y-objects
+    if ( o.__y__ || o instanceof YBase )
+        return newY ? Y(o.end()) : o;
+    
+    // Invoke plugins
+    var _o = o
+    ,   args = slice.call(A, 0);
+    
+    // Capture phase, which will bubble to `call` if necessary
+    o = doPhase('capture', o, newY, args);
+    
+    // Post-processing phase
+    o = doPhase('finally', o, newY, args);
+    
+    // Wrap as generic object if nothing matched
+    if (o !== _o || o._o)
+        return o;
+    else
+        return new Y.YObject(o);
+}
+
+Y.plugins = [];
+Y.plugins.byName = {};
+
+/**
+ * @param {Object|Function} plugin should either be a function to be used as `call`, or an object
+ * with some set of methods from:
+ *  - call: Invoked during bubbling to process input. Any non-false-y return value will be passed
+ *      along as the Y() return value. All plugins with a `call` method will be invoked during
+ *      bubbling, so non-matching plugins should not return a value.
+ *  - capture: Invoked before bubbling to process input. A non-false-y return value will be
+ *      returned as the Y() return value. All plugins with a `capture` method will be invoked during
+ *      capture, and any truth-y response will prevent bubbling. It is therefore critical non-
+ *      matching plugins do not return a value.
+ *  - finally: Invoked after capture and/or bubbling to post-process the result. Any non-false-y
+ *      return value will be passed along as the Y() return value.
+ * Methods are invoked with:
+ *  - {Object} o: The current return value (or input)
+ *  - {Boolean} newY: Whether `Y()` was invoked with the `new` keyword.
+ *  - {Array} args: The original arguments, converted to an array.
+ */
+Y.plugin = function(plugin){
+    if ( isFunction(plugin) )
+        plugin = { 'call':plugin, 'name':plugin.name };
+    
+    plugin.name = plugin.name || plugin.className;
+    
+    // Register plugin
+    Y.plugins.push(plugin);
+    if (plugin.name) Y.plugins.byName[plugin.name] = plugin;
+    
+    return plugin;
+};
+
+// Y( arguments, start=0, stop=arguments.length )
+// Cast `arguments` object to a real Array, optionally slicing at specified delimiters
+Y.plugin({
+    'capture': function(o, newY, args){
+        if ( o.prototype === undefined
+                 && isNumber(o.length)
+                 && !isArray(o)
+                 && o.constructor === _Object )
+            return slice.call(o, args[1] || 0, args[2] || o.length);
+    }
+});
+
+Y.plugin({
+    'finally': function(o, newY, args){
+        return (newY ? Y(o) : o);
+    }
+});
+
+// Merge Arrays or Objects
+// Y([0,1], [2,3], [4,5])   -> [0,1,2,3,4,5]
+// Y({foo:1}, {bar:2})      -> { foo:1, bar:2 }
+Y.plugin({
+    'capture': function(o, newY, args){
+        if ((args.length > 1) && ( args.every(isArray) || args.every(isPlainObject) ))
+            return extend.apply(this, args);
+    }
+});
+
+// Convenience of Y(Y.range())
+Y.plugin({
+    'capture': function(o, newY, args){
+        if ( (args.length > 1) && args.every(isNumber) )
+            return Y.range.apply(this, A);
+    }
+});
+
+
+// We got random stuff: wrap an Array of it
+Y.plugin({
+    'capture': function(o, newY, args){
+        if (args.length > 1)
+            return new Y.YArray(args);
+    }
+});
+
+function getYType(o){
+    var name   = type_of(o)
+    ,   yname  = 'Y' + name.charAt(0).toUpperCase() + name.slice(1);
+    return Y[yname];
+}
+
+// Builtin type-specific wrappers
+Y.plugin({
+    'call' : function(o, newY, args){
+        var YType = getYType(o);
+        if ( YType && YType !== Y.YObject || YType !== Y.YBase )
+            return new YType(o);
+    }
+});
+
+
diff --git a/src/Y/y-function.js b/src/Y/y-function.js
new file mode 100644 (file)
index 0000000..f7654b4
--- /dev/null
@@ -0,0 +1,204 @@
+function YFunction(fn){
+    fn._o = fn;
+    fn.__y__ = true;
+    return install(fn);
+}
+
+// YFunction = YCollection.subclass({
+//     init : YFunction,
+//     reduce : reduce,
+//     attr : attr,
+//     extend : extend,
+//     end : function(){ return this; }
+// })
+
+Y.YFunction = YFunction;
+YFunction.prototype.end    = function(){ return this; };
+YFunction.prototype.attr   = methodize(Y.attr);
+YFunction.prototype.extend = methodize(Y.extend);
+YFunction.prototype.reduce = methodize(Y.reduce);
+
+YFunction.install = install;
+function install(target){
+    target = target || Function.prototype;
+    var proto = YFunction.prototype;
+    for (var k in proto) {
+        if ( isFunction(proto[k]) && (k == 'bind' || !target[k]) )
+            target[k] = proto[k];
+    }
+    return target;
+}
+
+
+
+Y.compose = compose;
+YFunction.prototype.compose = methodize(compose);
+function _composer(x,fn){ return fn.call(this, x); }
+function compose(f,g){
+    var fns = Y(arguments);
+    return function(){
+        return fns.reduce(_composer, Y(arguments), this);
+    };
+}
+
+Y.chain = chain;
+YFunction.prototype.chain = methodize(chain);
+function chain(f,g){
+    var fns = Y(arguments);
+    
+    if ( g.__sequence__ )
+        fns = g.__sequence__.concat( fns.slice(1) );
+    
+    var wrapper = function(){
+            var args = Y(arguments)
+            ,   i = fns.length;
+            while (i-- > 0) args = fns[i].call(this, x);
+            return args;
+        };
+    
+    wrapper.__sequence__ = fns;
+    return wrapper;
+}
+
+function splat(fn, x){
+    return fn[ isArray(x) ? "apply" : "call"](this, x);
+}
+
+function unwrap(fn){
+    return (fn && isFunction(fn)) ? unwrap(fn.__wraps__) || fn : fn;
+}
+
+YFunction.prototype.bind = function(context, args){
+    var bound = Y( _Function.prototype.bind.apply(this, arguments) );
+    bound.__wraps__ = this;
+    return bound;
+};
+
+
+// Remembers arguments but obeys current context
+YFunction.prototype.partial = partial;
+function partial(){
+    var fn = this
+    ,   args = Y(arguments)
+    ,   partially = Y(function(){
+            return fn.apply( this, args.concat(arguments) );
+        });
+    partially.__wraps__ = fn;
+    return partially;
+}
+
+// Collects arguments but ignores context: fn.curry( arg1, arg2, ... )
+YFunction.prototype.curry = methodize(curry);
+function curry(fn){
+    if (fn.__curried__)
+        return fn.apply(this, arguments);
+    
+    var args = Y(arguments, 1)
+    ,   L = unwrap(fn).length;
+    
+    function curried(){
+        var self = arguments.callee
+        ,   _args = self.__curried__ = args.concat(arguments);
+        self.__wraps__ = fn;
+        
+        if ( _args.length >= L )
+            return fn.apply(this, _args);
+        else
+            return curried;
+    }
+    
+    return curried();
+}
+
+// YFunction.prototype.lazy = methodize(lazy);
+// function lazy(fn){
+//     var args = Y(arguments, 1)
+//     ,   L = unwrap(fn).length
+//     ,   lazied = function(){
+//             var _args = Y(arguments)
+//             ,   f = _args[0];
+//             
+//             if ( isFunction(f) ) {
+//                 var lazied = arguments.callee.compose(_args.shift());
+//                 if (_args.length !== 0)
+//                     return lazied.lazy.apply(lazied, _args);
+//             }
+//             
+//             args.splice.apply(args, [args.length, 0].concat(_args));
+//             if ( args.length >= L )
+//                 return fn.apply(this, args);
+//             else
+//                 return arguments.callee;
+//         }
+//     ;
+//     lazied.__wraps__ = fn;
+//     lazied.__args = args;
+//     return lazied();
+// }
+
+
+Y.methodize = methodize;
+YFunction.prototype.methodize = methodize(methodize); // heh
+function methodize( fn ) {
+    if (fn.__methodized)
+        return fn.__methodized;
+    
+    var m = fn.__methodized =
+        Y(function(){
+            var target = (this instanceof YBase) ? this._o : this
+            ,   result = fn.apply(target, [target].concat(Y(arguments)));
+            if (result === target)
+                return this;
+            else
+                return result;
+            // return fn.apply(this, [this].concat(Y(arguments)));
+        });
+    m.__wraps__ = fn;
+    return m;
+}
+
+Y.genericize = genericize;
+YFunction.prototype.genericize = methodize(genericize); // heh
+function genericize( fn ) {
+    if (fn.__genericize)
+        return fn.__genericize;
+    
+    var g = fn.__genericize =
+        Y(function(){
+            var args = Y(arguments), self = args.shift();
+            return fn.apply(self, args);
+        });
+    g.__wraps__ = fn;
+    return g;
+}
+
+/** Returns the declared name of a function. */
+YFunction.prototype.getname = Y.getname = getname;
+function getname( fn ){
+    if ( !fn && isFunction(this) )
+        fn = this;
+    if ( !isFunction(fn) )
+        return fn;
+    else
+        return fn.className || fn.name || (fn+'').match( /function\s*([^\(]*)\(/ )[1] || '';
+}
+
+
+Y.mixinNames = mixinNames;
+function mixinNames(o, Donor, names, override, yWrap){
+    var target = ( isFunction(o)     ? o.prototype     : o)
+    ,   proto  = ( isFunction(Donor) ? Donor.prototype : Donor);
+    
+    // Need a closure to capture the name
+    names.forEach(function(name){
+        if ( isFunction(proto[name]) && (override || !target[name]) )
+            target[name] = function(){
+                var r = proto[name].apply(this._o || target, arguments);
+                return (yWrap ? Y(r) : r);
+            };
+    });
+    
+    return o;
+}
+
+
diff --git a/src/Y/y-number.js b/src/Y/y-number.js
new file mode 100644 (file)
index 0000000..724a8e6
--- /dev/null
@@ -0,0 +1,40 @@
+
+var
+YNumber =
+Y.YNumber =
+YCollection.subclass('YNumber', {
+    init: function(o){
+        if (!o) o = 0;
+        YCollection.init.call(this, o);
+    },
+    
+    compare : function(n){
+        var m = this._o;
+        return (m > n ?  1 :
+               (m < n ? -1 : 0 ));
+    },
+    
+    toString : function(){
+        return this.end()+'';
+    }
+});
+
+
+Y.range = range;
+function range(start, end){
+    // range() --> []
+    if ( arguments.length === 0 ) return [];
+    // range(3) --> range(0,3)
+    else if ( arguments.length === 1 ) { end = start; start = 0; }
+    // range(5,2) --> range(1,6).reverse()
+    if (start > end) { 
+        var r = range(end-1, start+1);
+        r.reverse();
+        return r;
+    } else {
+        var i = end - start - 1, r = new Array(i);
+        while ( i >= 0 ) r[i] = start + i--;
+        return r;
+    }
+}
+
diff --git a/src/Y/y-object.js b/src/Y/y-object.js
new file mode 100644 (file)
index 0000000..2c82bf1
--- /dev/null
@@ -0,0 +1,12 @@
+
+var
+YObject =
+Y.YObject =
+YCollection.subclass('YObject', {
+    'init': function (o){
+        if (!o) o = {};
+        this._o = o;
+        YCollection.init.call(this, o);
+    }
+});
+
diff --git a/src/Y/y-op.js b/src/Y/y-op.js
new file mode 100644 (file)
index 0000000..38d05b5
--- /dev/null
@@ -0,0 +1,59 @@
+
+/* A subset of the functional operators, used in Y's core */
+Y.op = { 
+    
+    // comparison
+    cmp:    function(x,y){  return x == y ? 0 : (x > y ? 1 : -1); },
+    eq:     function(x,y){  return x == y; },
+    ne:     function(x,y){  return x != y; },
+    gt:     function(x,y){  return x  > y; },
+    ge:     function(x,y){  return x >= y; },
+    lt:     function(x,y){  return x  < y; },
+    le:     function(x,y){  return x <= y; },
+    
+    // math
+    add:    function(x,y){  return x  + y; },
+    sub:    function(x,y){  return x  - y; },
+    mul:    function(x,y){  return x  * y; },
+    div:    function(x,y){  return x  / y; },
+    flrdiv: function(x,y){  return Math.floor(x / y); },
+    mod:    function(x,y){  return x  % y; },
+    
+    // logic
+    and:    function(x,y){  return x && y; },
+    or:     function(x,y){  return x || y; },
+    xor:    function(x,y){  return x != y; },
+    not:    function(x){    return !x; },
+    neg:    function(x){    return -x; },
+    
+    // bitwise
+    bitnot: function(x){    return ~x; },
+    bitand: function(x,y){  return x  & y; },
+    bitor:  function(x,y){  return x  | y; },
+    bitxor: function(x,y){  return x  ^ y; },
+    lshift: function(x,y){  return x << y; },
+    rshift: function(x,y){  return x >> y; },
+    zrshift: function(x,y){ return x >>> y; },
+    
+    // typecast
+    bool:   function(x){    return !!x; },
+    number: function(x){    return Number(x); },
+    int:    function(x){    return parseInt(x); },
+    float:  function(x){    return parseFloat(x); },
+    str:    function(x){    return String(x); },
+    
+    // functional
+    I:      function(x){    return x; },
+    K:      function(k){    return function(){ return k; }; },
+    nop:    function(){},
+    
+    // accessors
+    isset:  function(o,def){   return o !== undefined ? o : def; },
+    has:    function(x,y){     return y in x; },
+    getkey: function(k,o){     return o[k] },
+    getdef: function(o,k,def){ return (k in o ? o[k] : def); },
+    set:    function(o,k,v){   if (o) o[k] = v; return o; }
+    
+};
+
+
diff --git a/src/Y/y-string.js b/src/Y/y-string.js
new file mode 100644 (file)
index 0000000..57d2ddc
--- /dev/null
@@ -0,0 +1,66 @@
+
+function strip(){ 
+    return this._o.replace(/(^\s+|\s+$)/g, '');
+}
+
+function trim(val){
+    var s = this._o+'';
+    val = val+'';
+    if (s.length < val.length)
+        return s;
+    else if ( s.indexOf(val) === 0 )
+        return s.slice(val.length);
+    else
+        return s;
+}
+
+function rtrim(val){
+    var s = this._o+'';
+    val = val+'';
+    
+    var idx = (s.length - val.length);
+    if (idx < 0)
+        return s;
+    else if ( s.lastIndexOf(val) === idx )
+        return s.slice(0, idx);
+    else
+        return s;
+}
+
+function startsWith(val){
+    return this._o.indexOf(val) === 0;
+}
+
+function endsWith(val){
+    var s = this._o;
+    return s.lastIndexOf(val) === (s.length - val.length);
+}
+
+var
+YString =
+Y.YString =
+YCollection.subclass('YString', {
+    init : function(o){
+        if (!o) o = "";
+        this._o = o;
+        YCollection.init.call(this, o);
+    },
+    strip: strip,
+    trim: trim,
+    ltrim: trim,
+    rtrim: rtrim,
+    startsWith: startsWith,
+    endsWith: endsWith,
+    
+    compare : function(n){
+        var m = this._o;
+        return (m > n ?  1 :
+               (m < n ? -1 : 0 ));
+    },
+    
+    toString: function(){
+        // return 'Y"'+this._o+'"';
+        return this._o;
+    }
+});
+
diff --git a/src/Y/y.js.php b/src/Y/y.js.php
new file mode 100644 (file)
index 0000000..e86a8af
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+function dump_file($path, $add_newline=true){
+    $size = filesize($path);
+    if ($size > 0) {
+        $f = fopen($path, "r");
+        echo fread($f, $size);
+        fclose($f);
+    }
+    if ($add_newline) echo "\n";
+}
+
+$y_files = array(
+    'alias',
+    'type',
+    'core',
+    'y-core',
+    'y-function',
+    'y-op',
+    'y-class',
+    'y-collection',
+    'y-object',
+    'y-array',
+    'y-string',
+    'y-number'
+);
+
+function y_list($path='') {
+    global $y_files;
+    $path = $path ? $path : dirname($_SERVER["REQUEST_URI"]);
+    // echo $path;
+    foreach ($y_files as $f) {
+        echo "<script src='$path/$f.js' type='text/javascript'></script>\n";
+    }
+}
+
+function y_dump($expose=false) {
+    global $y_files;
+    if (!$expose)
+        dump_file("./_intro.js");
+    foreach ($y_files as $f)
+        dump_file("./$f.js");
+    if (!$expose)
+        dump_file("./_outro.js");
+}
+
+if ( basename($_SERVER["SCRIPT_FILENAME"]) == basename(__FILE__) ) {
+    if ( $_REQUEST["list"] )
+        y_list();
+    else
+        y_dump();
+}
\ No newline at end of file
diff --git a/src/js/_intro.js b/src/js/_intro.js
new file mode 100644 (file)
index 0000000..599c5c9
--- /dev/null
@@ -0,0 +1,7 @@
+(function(){
+
+var undefined,
+globals  = this,
+_toString = _Object.prototype.toString,
+_hasOwn   = _Object.prototype.hasOwnProperty,
+_isArray  = _Array.isArray;
diff --git a/src/js/_outro.js b/src/js/_outro.js
new file mode 100644 (file)
index 0000000..efe95da
--- /dev/null
@@ -0,0 +1,6 @@
+
+globals.reduce = reduce;
+globals.extend = extend;
+globals.attr   = attr;
+
+})();
diff --git a/src/js/array.js b/src/js/array.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/js/core.js b/src/js/core.js
new file mode 100644 (file)
index 0000000..40c493e
--- /dev/null
@@ -0,0 +1,39 @@
+// Generic Collection Functions
+
+function notSelfOrWrapped(fn){
+    var self = arguments.callee.caller;
+    return fn && fn !== self && fn.__wraps !== self;
+}
+
+function reduce(o, fn, acc, cxt){
+    if ( !o )
+        return acc;
+    
+    if ( notSelfOrWrapped(o.reduce) )
+        return o.reduce.apply(o, slice.call(arguments,1));
+    
+    cxt = cxt || o;
+    for ( var name in o )
+        acc = fn.call(cxt, acc, o[name], name, o);
+    
+    return acc;
+}
+
+function attr(o, key, value, def){
+    if ( o && notSelfOrWrapped(o.attr) )
+        return o.attr.apply(o, slice.call(arguments,1));
+    
+    if ( value !== undefined || def !== undefined ){
+        o[key] = (value !== undefined ? value : def);
+        return o;
+    } else
+        return o[key];
+}
+
+function extend( A, B ){
+    return slice.call(arguments,1).reduce(function(A, donor){
+        return reduce(donor, function(o, v, k){
+            return attr(o, k, v, o[k]);
+        }, A);
+    }, A);
+}
diff --git a/src/js/function.js b/src/js/function.js
new file mode 100644 (file)
index 0000000..c52011f
--- /dev/null
@@ -0,0 +1,46 @@
+var WR_P = "__wraps__";
+
+
+function unwrap(fn){
+    return ( fn && isFunction(fn) ) ? unwrap(fn[WR_P]) || fn : fn;
+}
+
+Function.prototype.curry = curry;
+function curry(){
+    var fn = this
+    ,   args = Array.slice(arguments,0)
+    ,   L = unwrap(fn).length;
+    
+    function curried(){
+        var _args = args.concat(Array.slice(arguments,0));
+        if ( _args.length >= L )
+            return fn.apply(this, _args);
+        else
+            return curry.apply(fn, _args);
+    }
+    curried[WR_P] = fn;
+    
+    return curried;
+}
+
+Function.prototype.methodize = methodize;
+function methodize() {
+    var fn = this;
+    if ( fn.__methodized__ )
+        return fn.__methodized__;
+    
+    var m = fn.__methodized__ =
+        function(){
+            return fn.apply(this, [this].concat(Array.slice(arguments, 0)));
+        };
+    m[WR_P] = fn;
+    return m;
+}
+
+/** Returns the declared name of a function. */
+Function.prototype.getName = getName;
+function getName(){
+    var fn = this;
+    return fn.className || fn.name || (fn+'').match( /function\s*([^\(]*)\(/ )[1] || '';
+}
+
diff --git a/src/js/js.js.php b/src/js/js.js.php
new file mode 100644 (file)
index 0000000..8868e17
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+function dump_file($path, $add_newline=true){
+    $size = filesize($path);
+    if ($size > 0) {
+        $f = fopen($path, "r");
+        echo fread($f, $size);
+        fclose($f);
+    }
+    if ($add_newline) echo "\n";
+}
+
+$jsjs_files = array(
+    'type',
+    'core',
+    'function',
+    'object',
+    'array',
+    'string',
+    'number'
+);
+
+function jsjs_list($path='') {
+    global $jsjs_files;
+    $path = $path ? $path : dirname($_SERVER["REQUEST_URI"]);
+    // echo $path;
+    foreach ($jsjs_files as $f) {
+        echo "<script src='$path/$f.js' type='text/javascript'></script>\n";
+    }
+}
+
+function jsjs_dump($expose=false) {
+    global $jsjs_files;
+    if (!$expose)
+        dump_file("./_intro.js");
+    foreach ($jsjs_files as $f)
+        dump_file("./$f.js");
+    if (!$expose)
+        dump_file("./_outro.js");
+}
+
+if ( basename($_SERVER["SCRIPT_FILENAME"]) == basename(__FILE__) ) {
+    if ( $_REQUEST["list"] )
+        jsjs_list();
+    else
+        jsjs_dump();
+}
\ No newline at end of file
diff --git a/src/js/number.js b/src/js/number.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/js/object.js b/src/js/object.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/js/regexp.js b/src/js/regexp.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/js/string.js b/src/js/string.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/js/type.js b/src/js/type.js
new file mode 100644 (file)
index 0000000..1fd1447
--- /dev/null
@@ -0,0 +1,49 @@
+// Type Utilities //
+// Much borrowed from jQuery
+
+var class2type = "Boolean Number String Function Array Date RegExp Object"
+    .split(" ")
+    .reduce(function(class2type, name) {
+        class2type[ "[object "+name+"]" ] = name.toLowerCase();
+        return class2type;
+    }, {});
+
+function type_of(obj){
+    return obj == null ?
+        String( obj ) :
+        class2type[ toString.call(obj) ] || "object";
+}
+
+function isFunction(obj) { return type_of(obj) === "function"; }
+function isString(obj)   { return type_of(obj) === "string"; }
+function isNumber(obj)   { return type_of(obj) === "number"; }
+
+// A crude way of determining if an object is a window
+function isWindow( obj ) {
+       return obj && typeof obj === "object" && "setInterval" in obj;
+}
+
+function isPlainObject( obj ){
+    // Must be an Object.
+    // Because of IE, we also have to check the presence of the constructor property.
+    // Make sure that DOM nodes and window objects don't pass through, as well
+    if ( !obj || type_of(obj) !== "object" || obj.nodeType || 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.
+    
+    var key;
+    for ( key in obj ) {}
+    
+    return key === undefined || hasOwn.call( obj, key );
+}
+
+
diff --git a/src/lessly/bitgrid.js b/src/lessly/bitgrid.js
new file mode 100644 (file)
index 0000000..37efb7b
--- /dev/null
@@ -0,0 +1,381 @@
+(function(){
+
+// Constants
+var CELL_X    = 16 // 16x2 cells
+,   CELL_Y    = 2
+,   CELL_MASK = 0xffffffff
+,   A         = [] // Empty array for guarded lookups
+;
+
+/*@inline*/
+function toBits(x,y){ return (1 << (x % CELL_X)) << ((y % CELL_Y) * CELL_X); }
+/*@inline*/
+function toX(x){ return Math.floor(x/CELL_X); }
+/*@inline*/
+function toY(y){ return Math.floor(y/CELL_Y); }
+
+
+/**
+ * A bitset grid (for path-mapping).
+ * TODO: Make rect() routines agnostic to cell dimensions
+ * TODO: Optimize set operations
+ */
+BitGrid = new Y.Class('BitGrid', {
+    init : function(w, h){
+        this.width = w;
+        this.height = h;
+        
+        var x_cells = this.x_cells = toX(w)
+        ,   y_cells = this.y_cells = toY(h)
+        ,   cells   = this.cells = []
+        ;
+        
+        while (x_cells-- > 0) cells.push([]);
+        
+        // for (var x=0; x<x_cells; x++) {
+        //     cells.push([]);
+        // }
+    },
+    
+    _getCol : function(x){
+        var xi    = toX(x)
+        ,   cells = this.cells
+        ,   xcell = cells[xi]
+        ;
+        return xcell || (cells[xi] = []);
+    },
+    
+    _getCell : function(x,y){
+        var xi    = toX(x)
+        ,   cells = this.cells
+        ,   xcell = cells[xi]
+        ;
+        return (xcell ? xcell[ toY(y) ] || 0 : 0);
+    },
+    
+    _setCell : function(x,y, b){
+        this._getCol(x)[ toY(y) ] = b;
+    },
+    
+    /**
+     * Iterates a function over each cell;
+     * (x,y) is top-left coord of cell;
+     * iter has sig:
+     *      function iter(acc, cell, x, y) -> new acc
+     * Returns the final accumulated value.
+     */
+    reduce : function(iter, acc, context){
+        context = context || this;
+        var cells = this.cells
+        ,   xi_max = toX(this.width)
+        ,   yi_max = toY(this.height)
+        ;
+        for (var xi=0; xi<=xi_max; xi++) {
+            var col = cells[xi];
+            for (var yi=0; yi<yi_max; yi++) {
+                acc = iter.call(context, acc, (col || A)[yi] || 0, xi*CELL_X, yi*CELL_Y);
+            }
+        }
+        return acc;
+    },
+    
+    /**
+     * Iterates a function over each cell, mutating the grid;
+     * (x,y) is top-left coord of cell;
+     * iter has sig:
+     *      function iter(cell, x, y) -> new value
+     */
+    map : function(iter, context){
+        context = context || this;
+        var col, cell
+        ,   cells = this.cells
+        ,   xi_max = toX(this.width)
+        ,   yi_max = toY(this.height)
+        ;
+        for (var xi=0; xi<=xi_max; xi++) {
+            col = cells[xi];
+            for (var yi=0; yi<yi_max; yi++) {
+                cell = iter.call(context, (col || A)[yi] || 0, xi*CELL_X, yi*CELL_Y);
+                if (cell && !col) col = cells[xi] = [];
+                if (col) col[yi] = cell;
+            }
+        }
+        return this;
+    },
+    
+    /**
+     * Iterates a function over each cell;
+     * (x,y) is top-left coord of cell;
+     * iter has sig:
+     *      function iter(cell, x, y) -> void
+     */
+    each : function(iter, context){
+        context = context || this;
+        var col
+        ,   cells = this.cells
+        ,   xi_max = toX(this.width)
+        ,   yi_max = toY(this.height)
+        ;
+        for (var xi=0; xi<=xi_max; xi++) {
+            col = cells[xi] || A;
+            for (var yi=0; yi<yi_max; yi++) {
+                iter.call(context, col[yi] || 0, xi*CELL_X, yi*CELL_Y);
+            }
+        }
+        return this;
+    },
+    
+    isSet : function(x,y){
+        return this._getCell(x,y) & toBits(x,y);
+    },
+    
+    addPoint : function(x,y){
+        // if (x < 0 || x > this.width || y < 0 || y > this.height)
+        //     return undefined;
+        this._getCol(x)[ toY(y) ] |= toBits(x,y);
+        return this;
+    },
+    
+    addRect : function(x1,y1, x2,y2){
+        var cells = this.cells
+        ,   x_min = toX(x1), x_max = toX(x2)
+        ,   y_min = toY(y1), y_max = toY(y2)
+        ,   y_min_ragged = ((y1%CELL_Y) !== 0)
+        ,   y_max_ragged = ((y2%CELL_Y) !== 1)
+        ;
+        
+        for (var xi=x_min; xi<=x_max; xi++) {
+            var xbits = 0xffff;
+            if ( xi === x_min ) {
+                var off = (x1%CELL_X);
+                xbits &= (0xffff >> off) << off;
+            }
+            if ( xi === x_max ) {
+                xbits &= 0xffff >> (CELL_X - (x2%CELL_X));
+            }
+            var bits = (xbits << CELL_X) | xbits
+            ,   xcell = cells[xi] || (cells[xi] = []);
+            for (var yi=y_min; yi<=y_max; yi++) {
+                if (yi === y_min && y_min_ragged) {
+                    xcell[yi] |= xbits << CELL_X;
+                } else if (yi === y_max && y_max_ragged) {
+                    xcell[yi] |= xbits;
+                } else {
+                    xcell[yi] |= bits;
+                }
+            }
+        }
+        return this;
+    },
+    
+    addGrid : function(other){
+        var cells = this.cells
+        ,   o_cells = other.cells
+        ,   x_len = o_cells.length
+        ;
+        for (var xi=0; xi<x_len; xi++) {
+            var col = cells[xi]
+            ,   o_col = o_cells[xi]
+            ;
+            if ( !(o_col && o_col.length) )
+                continue;
+            if ( !col )
+                col = cells[xi] = [];
+            for (var yi=0; yi<o_col.length; yi++) {
+                col[yi] |= o_col[yi];
+            }
+        }
+        return this;
+    },
+    
+    add : function(x1,y1, x2,y2){
+        var L = arguments.length;
+        if (L == 4) {
+            this.addRect(x1,y1, x2,y2);
+        } else if (L == 2) {
+            this.addPoint(x1,y1);
+        } else if (L == 1) {
+            this.addGrid(x1);
+        } else {
+            throw new Error('Grid.add needs 2 or 4 arguments, got '+L+'! ['+arguments+']');
+        }
+        return this;
+    },
+    
+    subtractPoint : function(x,y){
+        this._getCol(x)[ toY(y) ] &= CELL_MASK ^ toBits(x,y);
+    },
+    
+    subtractRect : function(x1,y1, x2,y2){
+        var cells = this.cells
+        ,   x_min = toX(x1), x_max = toX(x2)
+        ,   y_min = toY(y1), y_max = toY(y2)
+        ,   y_min_ragged = ((y1%CELL_Y) !== 0)
+        ,   y_max_ragged = ((y2%CELL_Y) !== 1)
+        ;
+        
+        for (var xi=x_min; xi<=x_max; xi++) {
+            var xbits = 0xffff;
+            if ( xi === x_min ) {
+                var off = (x1%CELL_X);
+                xbits &= (0xffff >> off) << off;
+            }
+            if ( xi === x_max ) {
+                xbits &= 0xffff >> (CELL_X - (x2%CELL_X));
+            }
+            var bits = CELL_MASK ^ ((xbits << CELL_X) | xbits)
+            ,   xcell = cells[xi] || (cells[xi] = []);
+            for (var yi=y_min; yi<=y_max; yi++) {
+                if (yi === y_min && y_min_ragged) {
+                    xcell[yi] &= CELL_MASK ^ (xbits << CELL_X);
+                } else if (yi === y_max && y_max_ragged) {
+                    xcell[yi] &= CELL_MASK ^ xbits;
+                } else {
+                    xcell[yi] &= bits;
+                }
+            }
+        }
+        return this;
+    },
+    
+    subtractGrid : function(other){
+        var cells = this.cells
+        ,   o_cells = other.cells
+        ,   x_len = o_cells.length
+        ;
+        for (var xi=0; xi<x_len; xi++) {
+            var col = cells[xi]
+            ,   o_col = o_cells[xi]
+            ;
+            if ( !(o_col && o_col.length) ) {
+                if (col) cells[xi] = [];
+                continue;
+            }
+            if ( !col )
+                col = cells[xi] = [];
+            for (var yi=0; yi<o_col.length; yi++) {
+                col[yi] &= ~o_col[yi];
+            }
+        }
+        return this;
+    },
+    
+    subtract : function(x1,y1, x2,y2){
+        var L = arguments.length;
+        if (L == 4) {
+            this.subtractRect(x1,y1, x2,y2);
+        } else if (L == 2) {
+            this.subtractPoint(x1,y1);
+        } else if (L == 1) {
+            this.subtractGrid(x1);
+        } else {
+            throw new Error('Grid.subtract needs 2 or 4 arguments, got '+L+'! ['+arguments+']');
+        }
+        return this;
+    },
+    
+    intersection : function(other){
+        return this.reduce(function(g, cell, x, y){
+            g._setCell(x,y, cell & other._getCell(x,y));
+            return g;
+        }, new Grid(this.width, this.height));
+    },
+    
+    union : function(other){
+        return this.reduce(function(g, cell, x, y){
+            g._setCell(x,y, cell | other._getCell(x,y));
+            return g;
+        }, new Grid(this.width, this.height));
+    },
+    
+    difference : function(other){
+        return this.reduce(function(g, cell, x, y){
+            g._setCell(x,y, cell & (CELL_MASK ^ other._getCell(x,y)));
+            return g;
+        }, new Grid(this.width, this.height));
+    },
+    
+    symmetricDifference : function(grid){
+        return this.difference(grid).addGrid( grid.difference(this) );
+    },
+    
+    toDiagram : function(label, selector){
+        var 
+        self = this,
+        el = $('<div class="grid diagram"><div class="close">&otimes;</div><canvas/><label/></div>'),
+        canvas = $('canvas', el);
+        
+        $('.close', el)
+            .css({
+                'position': 'absolute',
+                'top': '5px',
+                'right': '5px',
+                'line-height': '1em',
+                'cursor': 'pointer'
+            })
+            .click(function(){
+                el.remove();
+            });
+        
+        if (label)
+            $('label', el)
+                .text(label)
+                .css({
+                    'display': 'block',
+                    'padding-top': '0.25em'
+                });
+        
+        canvas.css({
+            'display': 'block',
+            'margin': '0 auto',
+            'width' : this.width,
+            'height': this.height
+        });
+        
+        var d = $('.diagram:last'),
+            top = (d.length ? (d.position().top + d.outerHeight() + 24)+'px' : '1em');
+        if (d.length)
+            console.log('top:', top, '=', d.position().top, '+', d.height(), '+', 24);
+        else
+            console.log('top:', top);
+        
+        el  .css({
+                'z-index'    : '1000000',
+                'position'   : 'absolute',
+                'top'        : top,
+                'right'      : '1em',
+                'padding'    : '1em',
+                'background' : '#182B53',
+                'color'      : '#fff',
+                'text-align' : 'center',
+                'border-radius':'10px',
+                '-moz-border-radius':'10px',
+                '-webkit-border-radius':'10px'
+            })
+            .appendTo(selector || 'body');
+        
+        return new Processing(canvas[0], function(P,C){
+            this.P = P;
+            this.C = C;
+            P.draw = function(){
+                var w = self.width, h = self.height;
+                // el.width(w);
+                P.size(w, h);
+                P.background(255);
+                for (var x=0; x<w; x++) {
+                    for (var y=0; y<h; y++) {
+                        self.isSet(x,y) && P.set(x, y, 0);
+                    }
+                }
+            }
+        });
+    },
+    
+    toString : function(){
+        return 'Grid['+this.width+','+this.height+']()';
+    }
+});
+
+this['Grid'] = Grid;
+})();
+
diff --git a/src/lessly/draw.js b/src/lessly/draw.js
new file mode 100644 (file)
index 0000000..b23b5b0
--- /dev/null
@@ -0,0 +1,44 @@
+(function(){
+
+function scale(args){
+    var A = arguments
+    , v   = (A.length > 1 ? A : args)
+    , i   = v.length
+    , r   = new Array(i)
+    ;
+    
+    while (i-- > 0) r[i] = SCALE * v[i];
+    return r;
+}
+
+function wrap(name){
+    return function(){
+        var P = this._o;
+        return P[name].apply(P, scale(arguments));
+    };
+}
+
+// Drawing utils that obey scaling
+function YProcessing(P){
+    this._o = P;
+    for (var k in P) {
+        if ( this[k] ) continue;
+        if ( P[k] instanceof Function )
+            this[k] = Y(P[k]).bind(P);
+        else
+            this[k] = P[k];
+    }
+}
+var methods = {
+    init : YProcessing,
+    rectXY : function(x1,y1, x2,y2){
+        return this.rect( x1,y2, x2-x1,y2-y1 );
+    }
+    
+};
+this.YProcessing = YProcessing = Y.YObject.subclass(
+    Y(['triangle', 'rect', 'elipse', 'line'])
+        .generate(wrap)
+        .extend(methods).end() );
+
+})();
diff --git a/src/lessly/future.js b/src/lessly/future.js
new file mode 100644 (file)
index 0000000..c95d7a8
--- /dev/null
@@ -0,0 +1,107 @@
+(function(){
+
+var _Object = Object
+,   _Array = Array
+,   _Function = Function
+,   AP = _Array.prototype
+,   FP = _Function.prototype
+,   slice = AP.slice;
+
+if ( !_Array.slice ) {
+    _Array.slice = function(a){
+        return slice.apply(a, slice.call(arguments, 1));
+    };
+}
+
+// JavaScript 1.6 & 1.7
+
+if ( !AP.indexOf ) {
+    AP.indexOf = function( value ){
+        for ( var A = this, i = 0, l = A.length; i < l; ++i )
+            if ( A[i] === value )
+                return i;
+        return -1;
+    };
+}
+
+if ( !AP.lastIndexOf ) {
+    AP.indexOf = function( value ){
+        for ( var A = this, i = A.length-1; i >= 0; --i )
+            if ( A[i] === value )
+                return i;
+        return -1;
+    };
+}
+
+
+if ( !AP.map ) {
+    AP.forEach = function( fn, context ){
+        for ( var A = this, context = context||A, i = 0, l = A.length; i < l; ++i )
+            fn.call( context, A[i], i, A );
+    };
+    
+    AP.map = function( fn, context ){
+        for ( var A = this, context = context||A, i = 0, l = A.length, r = new _Array(l); i < l; ++i )
+            r[i] = fn.call( context, A[i], i, A );
+        return r;
+    };
+    
+    AP.filter = function( fn, context ){
+        for ( var A = this, context = context||A, i = 0, l = A.length, r = [], v = A[0]; i < l; v = A[++i] )
+            if ( fn.call( context, v, i, A ) )
+                r.push(v);
+        return r;
+    };
+    
+    AP.every = function( fn, context ){
+        var A = this, context = context||A;
+        for (var i=0, l = A.length; i<l; ++i) {
+            if (i in A && !fn.call(context, A[i], i, A) )
+                return false;
+        }
+        return true;
+    }
+    
+    AP.some = function( fn, context ){
+        var A = this, context = context||A;
+        for (var i=0, l = A.length; i<l; ++i) {
+            if (i in A && fn.call(context, A[i], i, A) )
+                return true;
+        }
+        return false;
+    }
+}
+
+if ( !AP.reduce ){
+    AP.reduce = function( fn, acc, context ){
+        for ( var A = this, context = context||A, i = 0, l = A.length; i < l; ++i )
+            acc = fn.call( context, acc, A[i], i, A );
+        return acc;
+    }
+}
+
+
+
+// JavaScript 1.8.5
+
+if ( !FP.bind ) {
+    FP.bind = function( context ){
+        var fn = this,
+            args = slice.call(arguments, 1);
+        return function(){
+            return fn.apply(context, args.concat( slice.call(arguments,0) ));
+        };
+    };
+}
+
+if ( !_Object.getPrototypeOf ) {
+    _Object.getPrototypeOf =
+        ( (typeof "".__proto__ === "object")
+            ? function(object){ return object.__proto__; }
+            : function(object){ return object.constructor.prototype; }
+        );
+}
+
+_Array.isArray = _Array.isArray || function(o) { return _Object.prototype.toString.call(o) === '[object Array]'; };
+
+})();
diff --git a/src/lessly/log.js b/src/lessly/log.js
new file mode 100644 (file)
index 0000000..7125be6
--- /dev/null
@@ -0,0 +1,64 @@
+Log = new Y.Class('Log', {
+    _n : 0,
+    DEFAULT_OPTIONS : {
+        autoScroll : true,
+        prefix : ''
+    },
+    
+    init : function(el, options){
+        this.el = el;
+        var o = Y({}, this.DEFAULT_OPTIONS, options || {});
+        this.options = o.end();
+        this.prefix = (this.options.prefix ? '['+prefix+'] ' : '');
+        this.id = Log._n++;
+        
+        jQuery( this.ready.bind(this) );
+        
+        var Logger = this.log = this.log.bind(this);
+        for (var k in this) {
+            var v = this[k];
+            if ( k !== 'log' && Y.isFunction(v) )
+                this[k] = Logger[k] = v.bind(this);
+        }
+        Logger.self = this;
+        
+        return Logger;
+    },
+    
+    ready : function(evt){
+        var self = this
+        ,   el = self.el = $(self.el)
+        ,   n = this.id
+        ,   msgs = this.msgs = $('<div class="log_container" />')
+        ,   meta_spacer = this.meta_spacer = $('<div />')
+        ,   meta = this.meta = $('<div class="log_meta" />')
+        ,   menu = this.menu = $('<div class="log_menu" />')
+        ,   clear_btn = $('<a title="Clear Log" class="log_clear_btn">clear</a>')
+        ,   autosc_btn = $('<label for="log_autoscroll_btn_'+n+'">autoscroll?</label> <input id="log_autoscroll_btn_'+n+'" type="checkbox" checked="checked"/>')
+        ;
+        
+        menu.append(clear_btn, " | ", autosc_btn);
+        meta.append(el.children(), menu);
+        el  .append(meta, msgs)
+            .addClass('log', 'log_'+n);
+        
+        meta_spacer
+            .appendTo(msgs)
+            .height(meta.height());
+        
+        clear_btn.bind('click', this.clear);
+        autosc_btn.bind('change', function(evt){
+            self.options.autoScroll = !(self.options.autoScroll);
+        });
+    },
+    
+    log : function(){
+        var msgs = this.msgs;
+        msgs.append('<div class="log_item">'+this.prefix+Y(arguments).join(' ')+'</div>');
+        if (this.options.autoScroll) msgs.scrollTop(msgs.attr('scrollHeight'));
+    },
+    
+    clear : function(){ 
+        this.msgs.empty().append(this.meta_spacer);
+    }
+});
diff --git a/src/lessly/log.uki.js b/src/lessly/log.uki.js
new file mode 100644 (file)
index 0000000..ccdd89b
--- /dev/null
@@ -0,0 +1,4 @@
+uki([
+    { view: 'uki.more.view.ToggleButton', rect: '90 40 100 24', text: 'Toggle me' },
+    { view: 'uki.more.view.ToggleButton', rect: '210 40 100 24', text: 'Me to' }
+]).attachTo( document.getElementById('test'), '400 100' );
diff --git a/src/lessly/viewport.js b/src/lessly/viewport.js
new file mode 100644 (file)
index 0000000..9a82530
--- /dev/null
@@ -0,0 +1,60 @@
+(function(){
+
+function Viewport(canvas){
+    this._o = this;
+    Y.event.Emitter.call(this);
+    this.canvas = $(canvas)[0];
+}
+
+Y.subclass(Viewport, Y.event.Emitter, {
+    all_events: Y(['setup', 'draw']),
+    
+    /**
+     * Creates Processing instance and begins dispatch. 
+     * Clients must listen for `setup` prior to init().
+     */
+    init : function(){
+        var self = this;
+        self.processing = new Processing(
+            self.canvas, 
+            function(api, constants){
+                self.api = api;
+                self.constants = constants;
+                self.all_events.each(function(name){
+                    api[name] = 
+                    self[name] =
+                        function(){
+                            self.fire( name, self, {
+                                api       : self.api,
+                                constants : self.constants,
+                                args      : arguments
+                            });
+                        };
+                });
+            });
+    },
+    
+    dispatchEvent : function(evt){
+        // log.debug('dispatchEvent('+evt.type+')', this);
+        this.getQueue(evt.type).each(function(fn){
+            fn.call(evt.target, evt, evt.data.api, evt.data.constants);
+        });
+        return evt;
+    },
+    
+    subscribe: function(o, names){
+        var self = this;
+        Y(names || self.all_events).each(function(name){
+            if (o[name]) self.addEventListener( name, Y(o[name]).bind(o) );
+        });
+        return o;
+    },
+    
+    toString: function(){
+        return this.constructor.name+'(canvas='+this.canvas+')';
+    }
+});
+
+
+this.Viewport = Viewport;
+})();
diff --git a/src/portal/layer.js b/src/portal/layer.js
new file mode 100644 (file)
index 0000000..53f1fe6
--- /dev/null
@@ -0,0 +1,274 @@
+(function($, undefined){
+
+// Install CSS styles
+$(function(){
+    $('<style />')
+        .text([
+            '.portal.layer { position:absolute; z-index:1; overflow:hidden; }',
+            '.portal.layer canvas { z-index:0; }'
+        ].join('\n'))
+        .appendTo('head');
+});
+
+function makeDelegate(name, dirties, prop){
+    prop = prop || 'layer';
+    return function(){
+        if (dirties && arguments.length)
+            this.dirty = true;
+        
+        var target = this[prop]
+        ,   result = target[name].apply(target, arguments);
+        
+        return (result !== target ? result : this);
+    };
+}
+
+Layer = new Y.Class('Layer', {
+    _cssClasses : 'portal layer',
+    
+    canvas   : null,
+    
+    parent   : null,
+    children : null,
+    
+    ctx      : null,
+    dirty    : true,
+    
+    x: 0,
+    y: 0,
+    
+    
+    /// Setup ///
+    
+    init : function(){
+        this.children = new Y.YArray();
+        
+        this.canvas = jQuery('<canvas />');
+        this.ctx = this.canvas[0].getContext('2d');
+        
+        this.layer = jQuery('<div/>')
+            .addClass(this._cssClasses)
+            .attr('layer', this)
+            .append(this.canvas);
+        
+        // this.layer[0].layer = this;
+    },
+    
+    /// Scene Graph Heirarchy ///
+    
+    /** @param {Layer} child */
+    append : function(child){
+        new Y(arguments).invoke('appendTo', this);
+        // if (child) child.appendTo(this);
+        return this;
+    },
+    
+    /** @param {Layer} parent */
+    appendTo : function(parent){
+        if (!parent) return this;
+        
+        // Always ensure we detach from the DOM and redraw this node
+        this.remove();
+        this.dirty = true;
+        
+        // Layer? Add self as new child, fix DOM
+        if ( parent instanceof Layer ){
+            this.parent = parent;
+            parent.children.push(this);
+            parent.layer.append(this.layer);
+            
+        // Otherwise: Attach to a DOM node as a new root layer node, leave parent null
+        } else
+            $(parent).append(this.layer);
+        
+        return this;
+    },
+    
+    /**
+     * Removes this layer from its parent and the DOM.
+     */
+    remove : function(){
+        if (this.parent)
+            this.parent.children.remove(this);
+        this.parent = null;
+        this.layer.remove();
+    },
+    
+    
+    
+    
+    
+    /// Attributes ///
+    
+    width : function(w){
+        if (w === undefined)
+            return this.layer.width();
+        
+        this.layer.add(this.canvas)
+            .attr({ width : w      })
+            .css( { width : w+'px' });
+        this.canvas[0].width = w;
+        
+        return this;
+    },
+    
+    height : function(h){
+        if (h === undefined)
+            return this.layer.height();
+        
+        this.layer.add(this.canvas)
+            .css( { height : h+'px' });
+        this.canvas[0].height = h;
+        
+        return this;
+    },
+    
+    viewportWidth  : makeDelegate('width' , true, 'canvas'),
+    viewportHeight : makeDelegate('height', true, 'canvas'),
+    
+    hide : makeDelegate('hide'),
+    show : makeDelegate('show'),
+    
+    /** Position relative to document. */
+    offset : makeDelegate('offset'),
+    
+    /**
+     * position() -> object 
+     * Gets position of the layer relative to the parent.
+     */
+    /**
+     * position(top, left) -> this
+     * Sets the position of this node, and then returns it.
+     * @param {Number|String|undefined} top If omitted, this method must be invoked with `undefined` as the first argument.
+     * @param {Number|String|undefined} left
+     */
+    /**
+     * position(pos) -> this
+     * Sets the position of this node, and then returns it.
+     * @param {Object} pos An object with "top" and/or "left" properties as in `position(top, left)`.
+     */
+    position : function(top, left){
+        if (top === undefined && left === undefined)
+            return this.layer.position();
+        
+        if (top && Y.isPlainObject(top))
+            var pos = top;
+        else
+            var pos = { 'top': top, 'left':left };
+        
+        this.css(pos);
+        return this;
+    },
+    
+    /** CSS properties */
+    css : makeDelegate('css'),
+    
+    
+    // position : function(pos){
+    //     if (pos) {
+    //         this.x = (pos.x || pos.left);
+    //         this.y = (pos.y || pos.top );
+    //         return this._updatePos();
+    //     } else {
+    //         var x = this.x, y = this.y;
+    //         return { x: x, left: x
+    //                , y: y, top : y };
+    //     }
+    // },
+    // 
+    // _updatePos : function(ppos){
+    //     ppos = ppos || this.parent.position();
+    //     this.dirty = true;
+    //     this.canvas.css({
+    //         'left' : ppos.x + this.x ,
+    //         'top'  : ppos.y + this.y
+    //     });
+    //     
+    //     this.children.invoke('_updatePos', this.position());
+    //     
+    //     return this;
+    // },
+    
+    
+    
+    /// Drawing Functions ///
+    
+    /**
+     * To be implemented by subclasses.
+     */
+    drawShape : function(ctx){ return this; },
+    
+    /**
+     * @param {CanvasDrawingContext2D} [ctx=this.ctx] Forces context to use rather than the layer's own.
+     * @param {Boolean} [force=false] Forces redraw.
+     */
+    draw : function(ctx, force){
+        if ( !(this.dirty || force) )
+            return this;
+        
+        var _ctx = ctx || this.ctx;
+        this._openPath(_ctx)
+            .drawShape(_ctx)
+            ._closePath(_ctx);
+        
+        this.dirty = false;
+        this.children.invoke('draw', ctx);
+        return this;
+    },
+    
+    _openPath : function(ctx){
+        ctx.beginPath();
+        // ctx.lineWidth = 0;
+        // ctx.fillStyle = "transparent";
+        return this;
+    },
+    
+    _closePath : function(ctx){
+        ctx.closePath();
+        return this;
+    },
+    
+    clear : function(ctx){
+        ctx = ctx || this.ctx;
+        ctx.beginPath();
+        ctx.clearRect(0,0, this.canvas.width(),this.canvas.height());
+        ctx.closePath();
+        return this;
+    },
+    
+    
+    
+    /// Iterators ///
+    
+    /**
+     * fold across this and parents, inner to outer:
+     *      acc = fn.call(context || node, acc, node)
+     */
+    foldParents : function(acc, fn, context){
+        // if ( Y.isFunction(fn) )
+        //     acc = fn.call(context || this, acc, this);
+        // else
+        //     acc = this[fn].call(context || this, acc, this);
+        acc = fn.call(context || this, acc, this);
+        return ( this.parent ? this.parent.foldParents(acc, fn, context) : acc );
+    },
+    
+    /**
+     * fold across this and children, depth-first:
+     *      acc = fn.call(context || node, acc, node)
+     */
+    foldChildren : function(acc, fn, context){
+        acc = fn.call(context || this, acc, this);
+        return this.children.foldChildren(acc, fn, context);
+    },
+    
+    
+    
+    /// Misc ///
+    
+    toString : function(){
+        return this.className+'['+this.x+','+this.y+']( children='+this.children.size()+' )';
+    }
+});
+
+})(jQuery);
diff --git a/src/portal/path.js b/src/portal/path.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/portal/portal.js b/src/portal/portal.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/portal/shape.js b/src/portal/shape.js
new file mode 100644 (file)
index 0000000..b2aef21
--- /dev/null
@@ -0,0 +1,61 @@
+Shape = new Y.Class('Shape', Layer, {
+    _cssClasses : 'portal layer shape',
+    
+    _fill       : false,    // same as 'transparent'
+    _line_width : 0,        // will ignore strokeStyle
+    _stroke     : false,    // same as 'transparent'
+    
+    
+    _updateStyle : function(ctx, apply, force){
+        ctx.lineWidth   = this._line_width;
+        ctx.strokeStyle = this._stroke || 'transparent';
+        ctx.fillStyle   = this._fill || 'transparent';
+        
+        if (apply) {
+            if (force || (this._line_width && this._stroke))
+                ctx.stroke();
+            
+            if (force || this._fill)
+                ctx.fill();
+        }
+        
+        return this;
+    }
+    
+});
+
+Rect = new Y.Class('Rect', Shape, {
+    _cssClasses : 'portal layer shape rect',
+    
+    _fill   : "#E73075",
+    _width  : 0,
+    _height : 0,
+    
+    
+    init : function(w, h){
+        Layer.init.call(this);
+        
+        this._width = w;
+        this.width(w);
+        
+        this._height = h;
+        this.height(h);
+    },
+    
+    drawShape : function(ctx){
+        ctx.rect(0,0, this._width,this._height);
+        this._updateStyle(ctx, true);
+        return this;
+    }
+    
+});
+
+
+Ellipse = new Y.Class('Ellipse', Shape, {
+    
+    drawShape : function(ctx){
+        return this;
+    }
+    
+});
+
diff --git a/src/portal/simpleclass.js b/src/portal/simpleclass.js
new file mode 100644 (file)
index 0000000..c0851ba
--- /dev/null
@@ -0,0 +1,34 @@
+function subclass(Parent, Child, body){
+    
+    // Prototype passed:
+    // - all 3 arguments supplied
+    // - Parent is an object
+    // - Child is a function
+    // - body is an object literal
+    var proto = Y.extend(Parent, { constructor:Child }, body);
+    Child.prototype = proto;
+    return Child;
+    
+    
+    // Superclass passed:
+    // - all 3 arguments supplied
+    // - Parent is a function
+    // - Child is a function
+    // - body is an object literal
+    Child.constructor = Parent;
+    return subclass(new Parent(), Child, body);
+    
+    
+    var args = arguments, nargs = args.length;
+    switch (args.length) {
+        case 2:
+            // subclass(Array, { [methods] })
+            if ( !Y.isFunction(Child) )
+                body = Child;
+                if ( Y.isFunction(Parent) )
+                    Child = function(){ Parent.apply(this, arguments); };
+                else
+                    Child = function(){  }
+        default: break;
+    }
+}
diff --git a/src/portal/util/cooldown.js b/src/portal/util/cooldown.js
new file mode 100644 (file)
index 0000000..5ba12cd
--- /dev/null
@@ -0,0 +1,41 @@
+Cooldown = new Y.Class('Cooldown', {
+    'init' : function(cooldown, isReady){
+        this.cooldown = cooldown; // ms
+        this.ready    = isReady || true;
+        this.last     = new Date().getTime();
+    },
+    
+    'activate' : function(now){
+        now = now || new Date().getTime();
+        if ( this.ready ) {
+            this.ready = false;
+            this.last  = now;
+            return true;
+        }
+        return false;
+    },
+    
+    'update' : function(now){
+        if (this.ready) return true;
+        
+        now = now || new Date().getTime();
+        if ( (this.last+this.cooldown) <= now )
+            this.ready = true;
+        
+        return this.ready;
+    },
+    
+    'setCooldown' : function(cooldown, now){
+        this.cooldown = cooldown;
+        return this.update(now);
+    },
+    
+    'ratio' : function(now){
+        now = now || new Date().getTime();
+        if ( this.update(now) )
+            return 1.0;
+        else
+            return now / (this.last+this.cooldown);
+    }
+    
+});
diff --git a/src/portal/util/eventloop.js b/src/portal/util/eventloop.js
new file mode 100644 (file)
index 0000000..1e2febb
--- /dev/null
@@ -0,0 +1,106 @@
+(function(){
+
+function add(a,b){ return a+b; }
+
+function decorate(delegate){
+    if (!delegate) return;
+    Y.event.Emitter.prototype.decorate.call(this, delegate);
+    'start stop reset'.split(' ').forEach(function(k){
+        delegate[k] = methods[k].bind(this);
+    }, this);
+    return delegate;
+}
+
+var
+MIN_TICK_INTERVAL = 0, // ms
+NUM_SAMPLES = 33,
+
+methods = {
+    // framerate : 0, // Target framerate
+    // frametime : 0, // 1000 / framerate
+    // samples   : NUM_SAMPLES, // Number of frames to track for effective fps
+    // 
+    // now       : 0, // Last tick time (ms)
+    // fps       : 0, // Effective framerate
+    // ticks     : 0, // Number of ticks since start
+    // 
+    // timer     : null,
+    // running   : false,
+    // times     : null, // Last `samples` frame durations
+    
+    
+    init : function(target, framerate, samples){
+        Y.event.Emitter.init.call(this, target);
+        decorate.call(this, target);
+        this.tick = this.tick.bind(this);
+        this.decorate = decorate.bind(this);
+        
+        this.framerate = framerate;
+        this.targetTime = 1000 / framerate;
+        this.samples = samples || NUM_SAMPLES;
+        
+        this.reset();
+    },
+    
+    reset : function(){
+        this.stop();
+        this.ticks = 0;
+        this.times = [];
+        this.realtimes = [];
+    },
+    
+    start : function(){
+        if (this.running) return;
+        this.running = true;
+        this.now = new Date().getTime() - (1000/this.framerate);
+        this.fire('start');
+        this.tick();
+    },
+    
+    stop : function(){
+        if (!this.running) return;
+        
+        clearTimeout(this.timer);
+        this.timer = null;
+        this.running = false;
+        this.fire('stop');
+    },
+    
+    tick : function(){
+        var lastTick = this.now;
+        this.now     = new Date().getTime();
+        this.elapsed = this.now - lastTick;
+        
+        clearTimeout(this.timer);
+        
+        this.fire('tick', this, {
+            now     : this.now,
+            elapsed : this.elapsed,
+            ticks   : ++this.ticks
+        });
+        
+        var tFrame  = new Date().getTime() - this.now
+        ,   tickInt = Math.max(MIN_TICK_INTERVAL, this.targetTime - tFrame);
+        
+        this.timer = setTimeout(this.tick, tickInt);
+        
+        this.realtimes.push(tFrame);
+        this.times.push(Math.max(tFrame, this.targetTime));
+        if (this.times.length > this.samples){
+            this.times.shift();
+            this.realtimes.shift();
+        }
+    },
+    
+    fps : function(){
+        return 1000 / (this.times.reduce(add,0) / this.times.length);
+    },
+    
+    frametime : function(){
+        return (this.realtimes.reduce(add,0) / this.realtimes.length);
+    }
+    
+};
+this.EventLoop = new Y.Class('EventLoop', Y.event.Emitter, methods);
+
+})();
diff --git a/src/portal/util/loc.js b/src/portal/util/loc.js
new file mode 100644 (file)
index 0000000..ef44e23
--- /dev/null
@@ -0,0 +1,113 @@
+Loc = new Y.Class('Loc', {
+    init : function(x, y){
+        this.x = Math.max(x,0);
+        this.y = Math.max(y,0);
+    },
+    
+    clone : function(){
+        return new Loc(this.x, this.y);
+    },
+    
+    toSquare : function(){
+        return Square.fromLoc(this.x, this.y);
+    },
+    
+    toString : function(){
+        return '('+Math.round(this.x, 2)+','+Math.round(this.y, 2)+')';
+    }
+    
+});
+Y(Loc).extend({
+    
+    fromSquare : function(col, row){
+        return new Loc(
+            col * REF_SIZE,
+            row * REF_SIZE );
+    },
+    
+    centerInSquare : function(v){
+        return Math.floor(v/REF_SIZE)*REF_SIZE + REF_SIZE/2;
+    }
+    
+});
+
+
+Rect = new Y.Class('Rect', {
+    init : function(x1,y1, x2,y2){
+        if (x1 instanceof Loc && y1 instanceof Loc) {
+            var top    = x1,
+                bottom = y1;
+        } else {
+            var top    = new Loc(x1,y1),
+                bottom = new Loc(x2,y2);
+        }
+        this.left  = this.top    = top;
+        this.right = this.bottom = bottom;
+        this.x1 = top.x; this.x2 = bottom.x;
+        this.y1 = top.y; this.y2 = bottom.y;
+    },
+    
+    // top    : function(){ return new Loc(this.x1, this.y1); },
+    // bottom : function(){ return new Loc(this.x2, this.y2); },
+    // 
+    // left   : function(){ return new Loc(this.x1, this.y1); },
+    // right  : function(){ return new Loc(this.x2, this.y2); },
+    
+    midpoint : function(){
+        var t = this.top,
+            b = this.bottom;
+        return new Loc(
+            t.x + (b.x-t.x)/2,
+            t.y + (b.y-t.y)/2 );
+    },
+    
+    clone : function(){
+        return new Rect(
+            this.top.clone(),
+            this.bottom.clone() );
+    },
+    
+    contains : function(x,y){
+        var t = this.top, b = this.bottom;
+        return ( x >= t.x && x <= b.x  &&
+                 y >= t.y && y <= b.y  );
+    },
+    
+    toString : function(){
+        return '['+this.top+' '+this.bottom+']';
+    }
+    
+});
+
+Square = new Y.Class('Square', Rect, {
+    init : function(col, row){
+        col = this.col = Math.max(col,0);
+        row = this.row = Math.max(row,0);
+        
+        var x1 = this.x = col * REF_SIZE
+        ,   y1 = this.y = row * REF_SIZE
+        ,   x2 = x1 + REF_SIZE
+        ,   y2 = y1 + REF_SIZE;
+        
+        Rect.init.call(this, x1,y1, x2,y2);
+    },
+    
+    clone : function(){
+        return new Square(this.col, this.row);
+    },
+    
+    toLoc : function(){
+        return Loc.fromSquare(this.col, this.row);
+    },
+    
+    toString : function(){
+        return this.col+'_'+this.row;
+    }
+    
+});
+Square.fromLoc = function(x, y){
+    return new Square(
+        Math.floor(x / REF_SIZE),
+        Math.floor(y / REF_SIZE) );
+};
+
diff --git a/src/portal/util/pointquadtree.js b/src/portal/util/pointquadtree.js
new file mode 100644 (file)
index 0000000..e2bc43b
--- /dev/null
@@ -0,0 +1,580 @@
+(function(){
+
+var
+cellId = 0,
+NodeType = {
+    EMPTY    : 0,
+    LEAF     : 1,
+    POINTER  : 2,
+    toString : function(type){
+        switch (type) {
+            case NodeType.EMPTY:    return "Empty";
+            case NodeType.LEAF:     return "Leaf";
+            case NodeType.POINTER:  return "Pointer";
+        }
+    }
+},
+RangeCellType = {
+    TOP    : 2,
+    LEFT   : 4,
+    BOTTOM : 8,
+    RIGHT  : 16
+},
+LeafType = {
+    POINT    : 0,
+    RANGE    : 1,
+    RANGE_TL : 1 | RangeCellType.TOP    | RangeCellType.LEFT,
+    RANGE_TR : 1 | RangeCellType.TOP    | RangeCellType.RIGHT,
+    RANGE_BL : 1 | RangeCellType.BOTTOM | RangeCellType.LEFT,
+    RANGE_BR : 1 | RangeCellType.BOTTOM | RangeCellType.RIGHT,
+    toString : function(type){
+        switch (type) {
+            case LeafType.POINT:    return "Point";
+            case LeafType.RANGE:    return "Range";
+            case LeafType.RANGE_TL: return "RangeTL";
+            case LeafType.RANGE_TR: return "RangeTR";
+            case LeafType.RANGE_BL: return "RangeBL";
+            case LeafType.RANGE_BR: return "RangeBR";
+        }
+    }
+},
+
+Node = new Y.Class('Node', {
+    init : function Node(x, y, w, h, parent) {
+        this.w = w;
+        this.h = h;
+        this.x1 = this.x = x;
+        this.y1 = this.y = y;
+        this.x2 = x+w;
+        this.y2 = y+h;
+        this.parent = parent || null;
+    },
+    type  : NodeType.EMPTY,
+    nw    : null,
+    ne    : null,
+    sw    : null,
+    se    : null,
+    value : null,
+    
+    reduce : function(fn, acc, context){
+        context = context || this;
+        if (this.nw) acc = fn.call(context, acc, this.nw, 'nw', this);
+        if (this.ne) acc = fn.call(context, acc, this.ne, 'ne', this);
+        if (this.sw) acc = fn.call(context, acc, this.sw, 'sw', this);
+        if (this.se) acc = fn.call(context, acc, this.se, 'se', this);
+        return acc;
+    },
+    
+    toString : function(){
+        return (NodeType.toString(this.type)+'( '+
+            'x='+this.x+',y='+this.y+', '+
+            'w='+this.w+',h='+this.h+
+        ' )');
+    }
+}),
+
+Range = new Y.Class('Range', Y.YCollection, {
+    init : function(tree, x1,y1, x2,y2, value){
+        this.x1 = this.x = x1;
+        this.y1 = this.y = y1;
+        this.x2 = x2;
+        this.y2 = y2;
+        this.w = x2-x1;
+        this.h = y2-y1;
+        this.value = value;
+        this._o = this.cells = {};
+        
+        this.tree = tree;
+        tree._ranges.push(this);
+    },
+    
+    contains : function(x,y){
+        return (this.x1 <= x && x <= this.x2)
+            && (this.y1 <= y && y <= this.y2);
+    },
+    
+    containsRange : function(x1,y1, x2,y2){
+        var sm_x = Math.min(x1,x2), lg_x = Math.max(x1,x2)
+        ,   sm_y = Math.min(y1,y2), lg_y = Math.max(y1,y2);
+        return !( sm_x > this.x2 || sm_y > this.y2
+               || lg_x < this.x1 || lg_y < this.y1 );
+    },
+    
+    getCell : function(x1,y1, x2,y2){
+        var cell =  new RangeCell(cellId++, x1,y1, x2,y2, this);
+        this.cells[cell.id] = cell;
+        return cell;
+    },
+    
+    removeCell : function(cell){
+        delete this.cells[cell.id];
+    },
+    
+    removeAll : function(){
+        var tree = this.tree;
+        this.forEach(function(cell){
+            this.removeCell(cell);
+            var cellNode = tree.closest(cell.x,cell.y);
+            if (cellNode) tree._remove(cellNode, true);
+        }, this);
+        tree._ranges.remove(this);
+    },
+    
+    reduce : function(fn, acc, context){
+        context = context || this;
+        return Y(this.cells).clone().reduce(fn, acc, context);
+    },
+    
+    toString : function(){
+        return 'Range('+this.x1+','+this.y1+', '+this.x2+','+this.y2+', value='+this.value+')'
+    }
+}),
+
+Leaf = new Y.Class('Leaf', {
+    toString : function(){
+        return LeafType.toString(this.type)+'('+this.x+','+this.y+', value='+this.value+')';
+    }
+}),
+Point = new Y.Class('Point', Leaf, {
+    init : function Point(x,y, value) {
+        this.x = x;
+        this.y = y;
+        this.value = (value !== undefined ? value : null);
+        this.type  = LeafType.POINT;
+    }
+}),
+RangeCell = new Y.Class('RangeCell', Leaf, {
+    init : function(id, x1,y1, x2,y2, owner) {
+        this.id = id;
+        this.x1 = this.x = x1;
+        this.y1 = this.y = y1;
+        this.x2 = x2;
+        this.y2 = y2;
+        this.w = x2-x1;
+        this.h = y2-y1;
+        this.type  = RangeCellType.RANGE;
+        this.owner = owner;
+        this.value = owner.value;
+    },
+    
+    containsRange : function(x1,y1, x2,y2){
+        var sm_x = Math.min(x1,x2), lg_x = Math.max(x1,x2)
+        ,   sm_y = Math.min(y1,y2), lg_y = Math.max(y1,y2);
+        return !( sm_x > this.x2 || sm_y > this.y2
+               || lg_x < this.x1 || lg_y < this.y1 );
+    },
+    
+    calc : function(node){
+        var r = this, t = LeafType.RANGE;
+        
+        if (node.x2 > r.x2)
+            t = t | RangeCellType.RIGHT;
+        else if (node.x1 <= r.x1) {
+            t = t | RangeCellType.LEFT;
+            r.x2 = node.x2;
+            r.w = r.x2 - r.x1;
+        }
+        
+        if (node.y2 > r.y2)
+            t = t | RangeCellType.BOTTOM;
+        else if (node.y1 <= r.y1) {
+            t = t | RangeCellType.TOP;
+            r.y2 = node.y2;
+            r.h = r.y2 - r.y1;
+        }
+        
+        this.type = t;
+    },
+    
+    removeRange : function(){
+        this.owner.removeAll();
+    }
+}),
+
+PointQuadTree = new Y.Class('PointQuadTree', {
+    'init' : function(minX,minY, maxX,maxY) {
+        this.x1 = this.x = minX;
+        this.y1 = this.y = minY;
+        this.x2 = maxX;
+        this.y2 = maxY;
+        this.width  = maxX-minX;
+        this.height = maxY-minY;
+        this.clear();
+    },
+    _root : null,
+    count : 0,
+    _ranges : null,
+    
+    inBounds : function(x,y){
+        var root = this._root;
+        return !( x<root.x1 || y<root.y1 || x>root.x2 || y>root.y2 );
+    },
+    
+    _inSubrange : function(x1,y1, x2,y2){
+        if (arguments.length === 4)
+            return this._ranges.invoke('containsRange', x1,y1, x2,y2).any();
+        else
+            return this._ranges.invoke('contains', x1,y1).any();
+    },
+    
+    // XXX: Hm. Should this be contains ALL or contains ANY for the range query?
+    contains : function(x1,y1, x2,y2){
+        if (arguments.length === 4)
+            return this._inSubrange(x1,y1, x2,y2);
+        else
+            return this._inSubrange(x1,y1) || this.get(x1,y1) !== null;
+    },
+    
+    isEmpty : function(){
+        return this._root.type == NodeType.EMPTY;
+    },
+    
+    clear : function(){
+        this._root = new Node(this.x1,this.y1, this.width,this.height);
+        this._ranges = Y([]);
+        this.count = 0;
+    },
+    
+    get : function(x,y, def){
+        var node = this.closest(x,y);
+        if (node && node.value.x === x && node.value.y === y)
+            return node.value.value;
+        else
+            return def;
+    },
+    
+    set : function(x,y, value){
+        if ( !this.inBounds(x,y) )
+            throw new Error('Out of bounds: ('+x+', '+y+')');
+        if ( this._inSubrange(x,y) )
+            throw new Error('Cannot place point in existing subrange!');
+        
+        this._insert(this._root, new Point(x,y, value), 1);
+    },
+    
+    addRange : function(x1,y1, x2,y2, value){
+        if ( !this.inBounds(x1,y1) )
+            throw new Error('Out of bounds: ('+x1+', '+y1+')');
+        if ( !this.inBounds(x2,y2) )
+            throw new Error('Out of bounds: ('+x2+', '+y2+')');
+        if ( this._inSubrange(x1,y1, x2,y2) )
+            throw new Error('Cannot overlap subrange!');
+        
+        var points = this.find(x1,y1, x2,y2);
+        if (points.length)
+            throw new Error('Cannot add range where points exist: ['+points.join(', ')+']');
+        
+        var sm_x = Math.min(x1,x2), lg_x = Math.max(x1,x2)
+        ,   sm_y = Math.min(y1,y2), lg_y = Math.max(y1,y2);
+        return this._addRangeUnsafe(sm_x,sm_y, lg_x,lg_y, value)
+    },
+    
+    _addRangeUnsafe : function(x1,y1, x2,y2, value){
+        var range = new Range(this, x1,y1, x2,y2, value);
+        this._addRange(x1,y1, x2,y2, range);
+        return range;
+    },
+    
+    _addRange : function(x1,y1, x2,y2, range){
+        if (x1 === x2 || y1 === y2) return;
+        
+        var r = range.getCell(x1,y1, x2,y2)
+        ,   n = this._insert(this._root, r)
+        ;
+        
+        // Horizontal recursion?
+        if (n.x2 < x2) this._addRange(n.x2,y1, x2,y2, range);
+        
+        // Vertical recursion?
+        if (n.y2 < y2) this._addRange(x1,n.y2, x2,y2, range);
+        
+        r.calc(n);
+        if ( (n.x1 < x1 && n.x2 > x2) || (n.y1 < y1 && n.y2 > y2) )
+            this._split(n);
+        
+        return n;
+    },
+    
+    remove : function(x,y){
+        var self = this
+        ,   node = this.closest(x,y)
+        ,   leaf = node && node.value;
+        if (leaf && leaf.x == x && leaf.y == y) {
+            return this._remove(node);
+        } else
+            return null;
+    },
+    
+    _removeClosest : function(x,y){
+        var node = this.closest(x,y);
+        if (node && node.type === NodeType.LEAF)
+            return this._remove(node);
+        else
+            return null;
+    },
+    
+    _remove : function(node, range){
+        var leaf = node.value;
+        node.value = null;
+        node.type = NodeType.EMPTY;
+        if (!range) {
+            this.count--;
+            if (leaf && (leaf.type & LeafType.RANGE))
+                leaf.owner.removeAll();
+        }
+        this._balance(node);
+        return leaf ? leaf.value : leaf;
+    },
+    
+    find : function(x1,y1, x2,y2, fn, acc, context){
+        var self = this
+        ,   cxt  = context || self
+        ,   sm_x = Math.min(x1,x2), lg_x = Math.max(x1,x2)
+        ,   sm_y = Math.min(y1,y2), lg_y = Math.max(y1,y2)
+        ;
+        
+        if (!fn) {
+            acc = [];
+            fn = function(acc, v, k){ acc.push(v); return acc; };
+        }
+        
+        function collect(acc, node) {
+            var np = node.value
+            ,   nx1=node.x,     ny1=node.y
+            ,   nx2=nx1+node.w, ny2=ny1+node.h ;
+            
+            if ( node.type === NodeType.EMPTY
+                 || nx2 < sm_x || ny2 < sm_y
+                 || nx1 > lg_x || ny1 > lg_y )
+                    return acc;
+            
+            if ( np && np.value !== null && (
+                   (np.type & LeafType.RANGE && np.containsRange(x1,y1, x2,y2)) ||
+                   (np.x >= sm_x && np.x <= lg_x && np.y >= sm_y && np.y <= lg_y) ) )
+                    acc = fn(acc, np.value, np, cxt);
+            
+            return node.reduce(collect, acc, self);
+        }
+        
+        return collect(acc, this._root);
+    },
+    
+    closest : function(x,y){
+        if ( !this.inBounds(x,y) )
+            return null;
+        return this._closest(x,y, this._root);
+    },
+    
+    _closest : function(x,y, node){
+        if ( !(node && this.inBounds(x,y)) )
+            return null;
+        
+        switch (node.type) {
+            case NodeType.EMPTY:
+                return null;
+            
+            case NodeType.LEAF:
+                return node;
+            
+            case NodeType.POINTER:
+                return this._closest(x,y, this._getQuadrantForPoint(node, x,y));
+            
+            default:
+                throw new Error('Invalid type');
+        }
+    },
+    
+    _getQuadrantForPoint : function(parent, x,y){
+        var mx = parent.x + parent.w/2;
+        var my = parent.y + parent.h/2;
+        if (x < mx) {
+            return y < my ? parent.nw : parent.sw;
+        } else {
+            return y < my ? parent.ne : parent.se;
+        }
+    },
+    
+    _insert : function(parent, value, isNew){
+        switch (parent.type) {
+            case NodeType.EMPTY:
+                this._setPointForNode(parent, value);
+                if (isNew) this.count++;
+                return parent;
+            
+            case NodeType.LEAF:
+                if (parent.value.x === value.x && parent.value.y === value.y) {
+                    this._setPointForNode(parent, value);
+                    return parent;
+                } else {
+                    this._split(parent);
+                    return this._insert(parent, value);
+                }
+            
+            case NodeType.POINTER:
+                return this._insert(
+                        this._getQuadrantForPoint(parent, value.x, value.y), value);
+            
+            default:
+                throw new Error('Invalid type in parent');
+        }
+    },
+    
+    _setPointForNode : function(node, value){
+        if (node.type == NodeType.POINTER)
+            throw new Error('Can not set value for node of type POINTER');
+        node.type = NodeType.LEAF;
+        node.value = value;
+    },
+    
+    _split : function(node){
+        var v = node.value;
+        node.value = null;
+        node.type = NodeType.POINTER;
+        
+        var x = node.x;
+        var y = node.y;
+        var hw = node.w / 2;
+        var hh = node.h / 2;
+        
+        node.nw = new Node(x,    y,    hw, hh, node);
+        node.ne = new Node(x+hw, y,    hw, hh, node);
+        node.sw = new Node(x,    y+hh, hw, hh, node);
+        node.se = new Node(x+hw, y+hh, hw, hh, node);
+        
+        if (v.type & LeafType.RANGE) {
+            v.owner.removeCell(v);
+            this._addRange(v.x1,v.y1, v.x2,v.y2, v.owner);
+        } else
+            this._insert(node, v);
+    },
+    
+    // TODO: beautify
+    _balance : function(node){
+        switch (node.type) {
+            case NodeType.EMPTY:
+            case NodeType.LEAF:
+                if (node.parent) {
+                    this._balance(node.parent);
+                }
+                break;
+            
+            case NodeType.POINTER:
+                var nw = node.nw, ne = node.ne, sw = node.sw, se = node.se;
+                var firstLeaf = null;
+                
+                // Look for the first non-empty child, if there is more than one then we
+                // break as this node can't be balanced.
+                if (nw.type != NodeType.EMPTY) {
+                    firstLeaf = nw;
+                }
+                if (ne.type != NodeType.EMPTY) {
+                    if (firstLeaf) {
+                        break;
+                    }
+                    firstLeaf = ne;
+                }
+                if (sw.type != NodeType.EMPTY) {
+                    if (firstLeaf) {
+                        break;
+                    }
+                    firstLeaf = sw;
+                }
+                if (se.type != NodeType.EMPTY) {
+                    if (firstLeaf) {
+                        break;
+                    }
+                    firstLeaf = se;
+                }
+                
+                if (!firstLeaf) {
+                    // All child nodes are empty: so make this node empty.
+                    node.type = NodeType.EMPTY;
+                    node.nw = node.ne = node.sw = node.se = null;
+                    
+                } else if (firstLeaf.type == NodeType.POINTER) {
+                    // Only child was a pointer, therefore we can't rebalance.
+                    break;
+                    
+                } else {
+                    // Only child was a leaf: so update node's value and make it a leaf.
+                    node.type = NodeType.LEAF;
+                    node.nw = node.ne = node.sw = node.se = null;
+                    node.value = firstLeaf.value;
+                }
+                
+                // Try and balance the parent as well.
+                if (node.parent) {
+                    this._balance(node.parent);
+                }
+                
+                break;
+        }
+    },
+    
+    getKeys : function(){
+        var arr = [];
+        this._traverse(this._root, function(node) {
+            arr.push({ 'x':node.value.x, 'y':node.value.y });
+        });
+        return arr;
+    },
+    
+    getValues : function(){
+        var arr = [];
+        this._traverse(this._root, function(node) {
+            // Must have a value because it's a leaf.
+            arr.push(node.value.value);
+        });
+        return arr;
+    },
+    
+    forEach : function(fn, context){
+        this._traverse(this._root, function(node) {
+            var coord = { 'x':node.value.x, 'y':node.value.y };
+            fn.call(context || this, node.value.value, coord, this);
+        });
+    },
+    
+    clone : function(){
+        var x1 = this._root.x;
+        var y1 = this._root.y;
+        var x2 = x1 + this._root.w;
+        var y2 = y1 + this._root.h;
+        var clone = new PointQuadTree(x1, y1, x2, y2);
+        // This is inefficient as the clone needs to recalculate the structure of the
+        // tree, even though we know it already.    But this is easier and can be
+        // optimized when/if needed.
+        this._traverse(this._root, function(node) {
+            clone.set(node.value.x, node.value.y, node.value.value);
+        });
+        return clone;
+    },
+    
+    _traverse : function(node, fn){
+        switch (node.type) {
+            case NodeType.LEAF:
+                fn.call(this, node);
+                break;
+            
+            case NodeType.POINTER:
+                this._traverse(node.ne, fn);
+                this._traverse(node.se, fn);
+                this._traverse(node.sw, fn);
+                this._traverse(node.nw, fn);
+                break;
+        }
+    }
+    
+});
+
+this.PointQuadTree = PointQuadTree;
+PointQuadTree.NodeType = NodeType;
+PointQuadTree.LeafType = LeafType;
+PointQuadTree.RangeCellType = RangeCellType;
+PointQuadTree.Node = Node;
+PointQuadTree.Range = Range;
+PointQuadTree.Leaf = Leaf;
+PointQuadTree.Point = Point;
+PointQuadTree.RangeCell = RangeCell;
+
+})();
\ No newline at end of file
diff --git a/src/portal/util/quadtree.js b/src/portal/util/quadtree.js
new file mode 100644 (file)
index 0000000..1637321
--- /dev/null
@@ -0,0 +1,205 @@
+(function(){
+
+function Rect(x1,y1, x2,y2){
+    var sm_x = Math.min(x1,x2), lg_x = Math.max(x1,x2)
+    ,   sm_y = Math.min(y1,y2), lg_y = Math.max(y1,y2);
+    this.x1 = this.x = sm_x;
+    this.y1 = this.y = sm_y;
+    this.x2 = lg_x;
+    this.y2 = lg_y;
+    this.width  = lg_x-sm_x;
+    this.height = lg_y-sm_y;
+}
+
+var
+CAPACITY  = 8,
+REGION_ID = 0,
+
+Region = new Y.Class('Region', {
+    init : function(x1,y1, x2,y2, value){
+        Rect.call(this, x1,y1, x2,y2);
+        this.id = REGION_ID++;
+        this.value = value;
+    },
+    
+    // Expects caller will have ordered x1 < x2, y1 < y2
+    overlaps : function(x1,y1, x2,y2){
+        return !( x1 > this.x2 || y1 > this.y2
+               || x2 < this.x1 || y2 < this.y1 );
+    },
+    
+    toString : function(){
+        return this.className+'(id='+this.id+', value='+this.value+')';
+    }
+}),
+
+QuadTree = new Y.Class('QuadTree', {
+    depth    : 0,
+    parent   : null,
+    regions  : [],
+    overflow : false,
+    
+    children : Y([]),
+    nw : null, ne : null,
+    sw : null, se : null,
+    
+    
+    init : function(x1,y1, x2,y2, capacity, parent) {
+        Rect.call(this, x1,y1, x2,y2);
+        this.capacity = capacity || CAPACITY;
+        this.parent   = parent   || null;
+        if (parent) this.depth = parent.depth + 1;
+        this.clear();
+    },
+    
+    // Expects caller will have ordered x1 < x2, y1 < y2
+    overlaps : function(x1,y1, x2,y2){
+        return !( x1 > this.x2 || y1 > this.y2
+               || x2 < this.x1 || y2 < this.y1 );
+    },
+    
+    
+    get : function(x1,y1, x2,y2){
+        return this.collect(x1,y1, x2,y2, this._get, { vals:Y([]) }, this).vals;
+    },
+    
+    _get : function(acc, value, region){
+        if ( !acc[region.id] ) {
+            acc[region.id] = region;
+            acc.vals.push(value);
+        }
+        return acc;
+    },
+    
+    set : function(x1,y1, x2,y2, value){
+        return this._set(new Region(x1,y1, x2,y2, value));
+    },
+    
+    _set : function(r){
+        return this.leaves(r.x1,r.y1, r.x2,r.y2, function(acc, regions, tree){
+            if ( regions.length < tree.capacity || tree.overflow )
+                regions.push(r);
+            else
+                // Splitting transforms this into an internal node, and
+                // causes leaves() to continue us down into the next level.
+                tree._split();
+            return r;
+        });
+    },
+    
+    remove : function(region){
+        var trees =
+            this.leaves(
+                region.x1,region.y1, region.x2,region.y2,
+                function(trees, regions, tree){
+                    Y(regions).remove(region);
+                    
+                    if ( !trees[tree.depth] )
+                        trees[tree.depth] = [];
+                    trees[tree.depth].push();
+                    
+                    return trees;
+                }, []);
+        
+        Y(trees.pop()).invoke('_balance');
+        
+        return region;
+    },
+    
+    removeAll : function(x1,y1, x2,y2){
+        this.leaves(x1,y1, x2,y2, function(_, regions, tree){
+            tree.clear();
+        });
+    },
+    
+    leaves : function(x1,y1, x2,y2, fn, acc, context){
+        var self = this
+        ,   cxt = context || self
+        ,   _x1 = Math.min(x1,x2), _x2 = Math.max(x1,x2)
+        ,   _y1 = Math.min(y1,y2), _y2 = Math.max(y1,y2) ;
+        
+        if ( !self.overlaps(_x1,_y1, _x2,_y2) )
+            return acc;
+        
+        // Implies this is a leaf: check its values
+        if ( self.regions ) acc = fn.call(cxt, acc, self.regions, self, self);
+        
+        // Recurse into any children -- Note we do not place this in an else-block,
+        // as mutation during the above call might cause the tree to split.
+        if (self.nw) acc = self.nw.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
+        if (self.ne) acc = self.ne.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
+        if (self.sw) acc = self.sw.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
+        if (self.se) acc = self.se.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
+        
+        return acc;
+    },
+    
+    collect : function(x1,y1, x2,y2, fn, acc, context){ // XXX: unique=false, limit=Infinity, depth=Infinity
+        var _x1 = Math.min(x1,x2), _x2 = Math.max(x1,x2)
+        ,   _y1 = Math.min(y1,y2), _y2 = Math.max(y1,y2);
+        return this.leaves(
+            _x1,_y1, _x2,_y2,
+            function(acc, rs, tree){
+                for (var i=0, L=rs.length, r=rs[i]; i<L; r=rs[++i])
+                    if ( r.overlaps(_x1,_y1, _x2,_y2) )
+                        acc = fn.call(this, acc, r.value, r, tree);
+                return acc;
+            },
+            acc,
+            context );
+    },
+    
+    reduce : function(fn, acc, context){
+        return this.collect(this.x1,this.y1, this.x2,this.y2, fn, acc, context);
+    },
+    
+    clear : function(){
+        this.overflow = false;
+        this.regions  = [];
+        this.children = null;
+        this.nw = this.ne = this.sw = this.se = null;
+    },
+    
+    _split : function(){
+        if (this.overflow) return;
+        
+        var regions = this.regions
+        ,   cap = this.capacity
+        ,   hw  = this.width  / 2
+        ,   hh  = this.height / 2
+        ,   x1  = this.x1, hx = x1+hw, x2 = this.x2
+        ,   y1  = this.y1, hy = y1+hh, y2 = this.y2 ;
+        
+        this.regions = null;
+        var nw = this.nw = new QuadTree(x1,y1, hx,hy, cap, this)
+        ,   ne = this.ne = new QuadTree(hx,y1, x2,hy, cap, this)
+        ,   sw = this.sw = new QuadTree(x1,hy, hx,y2, cap, this)
+        ,   se = this.se = new QuadTree(hx,hy, x2,y2, cap, this)
+        ,   children = this.children = Y([nw, ne, sw, se]) ;
+        
+        regions.forEach(this._set, this);
+        
+        // If any children are still at max capacity, undo and set the overflow flag
+        if ( Math.max.apply(Math, children.pluck('regions').pluck('length').end()) > cap ){
+            this.clear();
+            this.overflow = true;
+            this.regions  = regions;
+        }
+    },
+    
+    _balance : function(){
+        
+    },
+    
+    toString : function(){
+        var indent = '', d = this.depth+1;
+        while ( d-- > 0 ) indent += '\t';
+        return this.className+'('+this.x1+','+this.y1+', '+this.x2+','+this.y2+(this.regions ? ', regions='+this.regions.length : 0)+') '+
+            (this.children ? '\n'+indent+this.children.end().join('\n'+indent) : '');
+    }
+});
+
+this['QuadTree'] = QuadTree;
+QuadTree['Region'] = Region;
+
+})();
diff --git a/src/portal/util/rbtree.js b/src/portal/util/rbtree.js
new file mode 100644 (file)
index 0000000..b3d4b7d
--- /dev/null
@@ -0,0 +1,467 @@
+(function(){
+var RED        = "Red"
+,   BLACK      = "Black"
+,   FLIP_COLOR = {};
+FLIP_COLOR[RED]   = BLACK;
+FLIP_COLOR[BLACK] = RED;
+
+
+function isRed(n)   { return n && (n.color == RED); }
+function isBlack(n) { return n && (n.color == BLACK); }
+function size(n)    { return (n && n.N) || 0; }
+
+
+function Node(key, val, color, N) {
+    this.key   = key;
+    this.val   = val;
+    this.color = color;
+    this.N     = N;
+    this.left  = null;
+    this.right = null;
+}
+Node.prototype.toString = function(){
+    var S = (this.color === RED ? '[' : '(')
+    ,   E = (this.color === RED ? ']' : ')');
+    return S+this.key+' '+this.val+E;
+};
+
+var
+RedBlackTree = new Y.Class('RedBlackTree', {
+    init : function(key, value){
+        if (this.key !== undefined)
+            this.setKey(key, value);
+    },
+    
+    _root : null,
+
+   /*************************************************************************
+    *  Size methods
+    *************************************************************************/
+
+    // return number of key-value pairs in this symbol table
+    'size' : function() { return size(this._root); },
+
+    // is this symbol table empty?
+    'isEmpty' : function() {
+        return this._root === null;
+    },
+
+   /*************************************************************************
+    *  Standard BST search
+    *************************************************************************/
+
+    // value associated with the given key; null if no such key
+    'getKey' : function(key) {
+        return this._getKey(this._root, key);
+    },
+
+    // value associated with the given key in subtree rooted at x; null if no such key
+    _getKey : function(x, key) {
+        while (x !== null) {
+            if      (key < x.key) x = x.left;
+            else if (key > x.key) x = x.right;
+            else                  return x.val;
+        }
+        return null;
+    },
+
+    // is there a key-value pair with the given key?
+    'contains' : function(key) {
+        return (this.getKey(key) !== null);
+    },
+
+    // is there a key-value pair with the given key in the subtree rooted at x?
+    _contains : function(x, Key) {
+        return (this._getKey(x, key) !== null);
+    },
+
+   /*************************************************************************
+    *  Red-black insertion
+    *************************************************************************/
+
+    // insert the key-value pair; overwrite the old value with the new value
+    // if the key is already present
+    'setKey' : function(key, val) {
+        if (key === undefined) throw new Exception("Key cannot be undefined!");
+        this._root = this._setKey(this._root, key, val);
+        this._root.color = BLACK;
+        // assert isRedBlackBST();
+    },
+
+    // insert the key-value pair in the subtree rooted at h
+    _setKey : function(h, key, val) {
+        if (h === null) return new Node(key, val, RED, 1);
+        
+        if      (key < h.key) h.left  = this._setKey(h.left,  key, val);
+        else if (key > h.key) h.right = this._setKey(h.right, key, val);
+        else                  h.val   = val;
+        
+        return this._balance(h);
+    },
+
+    // remove the key-value pair with the given key
+    'remove' : function(key) {
+        if (!this.contains(key)) {
+            System.err.println("symbol table does not contain " + key);
+            return;
+        }
+
+        // if both children of this._root are black, set this._root to red
+        if (!isRed(this._root.left) && !isRed(this._root.right))
+            this._root.color = RED;
+
+        this._root = this._remove(this._root, key);
+        if (!this.isEmpty()) this._root.color = BLACK;
+        // assert isRedBlackBST();
+    },
+
+    // remove the key-value pair with the given key rooted at h
+    _remove : function(h, key) {
+        // assert this.contains(h, key);
+
+        if (key < h.key)  {
+            if (!isRed(h.left) && !isRed(h.left.left))
+                h = this._moveRedLeft(h);
+            h.left =  this._remove(h.left, key);
+        } else {
+            if (isRed(h.left))
+                h = this._rotateRight(h);
+            if (key === h.key && (h.right === null))
+                return null;
+            if (!isRed(h.right) && !isRed(h.right.left))
+                h = this._moveRedRight(h);
+            if (key === h.key) {
+                h.val = this._getKey(h.right, this._min(h.right).key);
+                h.key = this._min(h.right).key;
+                h.right = this._removeMin(h.right);
+            }
+            else h.right = this._remove(h.right, key);
+        }
+        return this._balance(h);
+    },
+    
+    // remove the key-value pair with the minimum key
+    _removeMin : function(h) {
+        if (h) {
+            if (h.left === null)
+                return null;
+
+            if (!isRed(h.left) && !isRed(h.left.left))
+                h = this._moveRedLeft(h);
+
+            h.left = this._removeMin(h.left);
+            return this._balance(h);
+            
+        } else {
+            if (this.isEmpty()) throw new Exception("BST underflow");
+
+            // if both children of root are black, set root to red
+            if (!isRed(this._root.left) && !isRed(this._root.right))
+                this._root.color = RED;
+
+            this._root = this._removeMin(this._root);
+            if (!this.isEmpty()) this._root.color = BLACK;
+            // assert isRedBlackBST();
+        }
+    },
+
+    // remove the key-value pair with the maximum key
+    _removeMax : function(h){
+        if (h) {
+            if (isRed(h.left))
+                h = this._rotateRight(h);
+
+            if (h.right === null)
+                return null;
+
+            if (!isRed(h.right) && !isRed(h.right.left))
+                h = this._moveRedRight(h);
+
+            h.right = this._removeMax(h.right);
+
+            return this._balance(h);
+            
+        } else {
+            if (this.isEmpty()) throw new Exception("BST underflow");
+
+            // if both children of root are black, set root to red
+            if (!isRed(this._root.left) && !isRed(this._root.right))
+                this._root.color = RED;
+
+            this._root = this._removeMax(this._root);
+            if (!this.isEmpty()) this._root.color = BLACK;
+            // assert isRedBlackBST();
+        }
+    },
+
+   /*************************************************************************
+    *  red-black tree helper functions
+    *************************************************************************/
+
+    // make a left-leaning link lean to the right
+    _rotateRight : function(h) {
+        // assert (h !== null) && isRed(h.left);
+        var x = h.left;
+        h.left = x.right;
+        x.right = h;
+        x.color = x.right.color;
+        x.right.color = RED;
+        x.N = h.N;
+        h.N = size(h.left) + size(h.right) + 1;
+        return x;
+    },
+
+    // make a right-leaning link lean to the left
+    _rotateLeft : function(h) {
+        // assert (h !== null) && isRed(h.right);
+        var x = h.right;
+        h.right = x.left;
+        x.left = h;
+        x.color = x.left.color;
+        x.left.color = RED;
+        x.N = h.N;
+        h.N = size(h.left) + size(h.right) + 1;
+        return x;
+    },
+
+    // flip the colors of a node and its two children
+    _flipColors : function(h) {
+        // h must have opposite color of its two children
+        // assert (h !== null) && (h.left !== null) && (h.right !== null);
+        // assert (!isRed(h) &&  isRed(h.left) &&  isRed(h.right)) ||
+        //         (isRed(h) && !isRed(h.left) && !isRed(h.right));
+        h.color       = FLIP_COLOR[h.color];
+        h.left.color  = FLIP_COLOR[h.left.color];
+        h.right.color = FLIP_COLOR[h.right.color];
+    },
+
+    // Assuming that h is red and both h.left and h.left.left
+    // are black, make h.left or one of its children red.
+    _moveRedLeft : function(h) {
+        // assert (h !== null);
+        // assert isRed(h) && !isRed(h.left) && !isRed(h.left.left);
+
+        this._flipColors(h);
+        if (isRed(h.right.left)) {
+            h.right = this._rotateRight(h.right);
+            h = this._rotateLeft(h);
+            // this._flipColors(h);
+        }
+        return h;
+    },
+
+    // Assuming that h is red and both h.right and h.right.left
+    // are black, make h.right or one of its children red.
+    _moveRedRight : function(h) {
+        // assert (h !== null);
+        // assert isRed(h) && !isRed(h.right) && !isRed(h.right.left);
+        this._flipColors(h);
+        if (isRed(h.left.left)) {
+            h = this._rotateRight(h);
+            // this._flipColors(h);
+        }
+        return h;
+    },
+
+    // restore red-black tree invariant
+    _balance : function(h) {
+        // assert (h !== null);
+        if (h === null)
+            throw new Exception("wtf balance a null node?");
+        
+        if (isRed(h.right))                      h = this._rotateLeft(h);
+        if (isRed(h.left) && isRed(h.left.left)) h = this._rotateRight(h);
+        if (isRed(h.left) && isRed(h.right))     this._flipColors(h);
+
+        h.N = size(h.left) + size(h.right) + 1;
+        return h;
+    },
+
+
+   /*************************************************************************
+    *  Utility functions
+    *************************************************************************/
+
+    // height of tree; 0 if empty
+    'height' : function() { return this._height(this._root); },
+    _height : function(x) {
+        if (x === null) return 0;
+        return 1 + Math.max(this._height(x.left), this._height(x.right));
+    },
+
+   /*************************************************************************
+    *  Ordered symbol table methods.
+    *************************************************************************/
+
+    // the smallest key; null if no such key
+    'min' : function() {
+        if (this.isEmpty()) return null;
+        return this._min(this._root).key;
+    },
+
+    // the smallest key in subtree rooted at x; null if no such key
+    _min : function(x) {
+        // assert x !== null;
+        if (x.left === null) return x;
+        else                 return this._min(x.left);
+    },
+
+    // the largest key; null if no such key
+    'max' : function() {
+        if (this.isEmpty()) return null;
+        return this._max(this._root).key;
+    },
+
+    // the largest key in the subtree rooted at x; null if no such key
+    _max : function(x) {
+        // assert x !== null;
+        if (x.right === null) return x;
+        else                  return this._max(x.right);
+    },
+
+    // the largest key less than or equal to the given key
+    'floor' : function(key) {
+        var x = this._floor(this._root, key);
+        if (x === null) return null;
+        else            return x.key;
+    },
+
+    // the largest key in the subtree rooted at x less than or equal to the given key
+    _floor : function(x, key) {
+        if (x === null)    return null;
+        if (key === x.key) return x;
+        if (key < x.key)   return this._floor(x.left, key);
+        var t = this._floor(x.right, key);
+        if (t !== null)    return t;
+        else               return x;
+    },
+
+    // the smallest key greater than or equal to the given key
+    'ceiling' : function(key) {
+        var x = this._ceiling(this._root, key);
+        if (x === null) return null;
+        else            return x.key;
+    },
+
+    // the smallest key in the subtree rooted at x greater than or equal to the given key
+    _ceiling : function(x, key) {
+        if (x === null)    return null;
+        if (key === x.key) return x;
+        if (key > x.key)   return this._ceiling(x.right, key);
+        var t = this._ceiling(x.left, key);
+        if (t !== null)    return t;
+        else               return x;
+    },
+
+
+    // the key of rank i
+    'iRank' : function(i) {
+        if (i < 0 || i >= this.size())
+            return null;
+        var x = this._iRank(this._root, i);
+        return x.key;
+    },
+
+    // the key of rank i in the subtree rooted at x
+    _iRank : function(x, i) {
+        // assert x !== null;
+        // assert i >= 0 && i < size(x);
+        var t = size(x.left);
+        if      (t > i) return this._iRank(x.left,  i);
+        else if (t < i) return this._iRank(x.right, i-t-1);
+        else            return x;
+    },
+
+    // number of keys less than key
+    'rank' : function(key) {
+        return this._rank(key, this._root);
+    },
+
+    // number of keys less than key in the subtree rooted at x
+    _rank : function(key, x) {
+        if (x === null) return 0;
+        if      (key < x.key) return this._rank(key, x.left);
+        else if (key > x.key) return 1 + size(x.left) + this._rank(key, x.right);
+        else                  return size(x.left);
+    },
+
+   /***********************************************************************
+    *  Range count and range search.
+    ***********************************************************************/
+
+    // the keys between lo and hi, as an Iterable
+    'keys' : function(lo, hi) {
+        var queue = [];
+        lo = lo || this.min();
+        hi = hi || this.max();
+        // if (this.isEmpty() || lo > hi) return queue;
+        this._keys(this._root, queue, lo, hi);
+        return queue;
+    },
+
+    // add the keys between lo and hi in the subtree rooted at x
+    // to the queue
+    _keys : function(x, queue, lo, hi) {
+        if (!x) return;
+        if (lo < x.key) this._keys(x.left, queue, lo, hi);
+        if (lo <= x.key && hi >= x.key) queue.push(x.key);
+        if (hi > x.key) this._keys(x.right, queue, lo, hi);
+    },
+
+    // number keys between lo and hi
+    'count' : function(lo, hi) {
+        if (lo > hi) return 0;
+        if (this.contains(hi)) return this._rank(hi) - this._rank(lo) + 1;
+        else                   return this._rank(hi) - this._rank(lo);
+    },
+    
+    /**
+     * Visits all pairs in [lo..hi], defaulting to [min..max].
+     * Visitor defaults to creating an array of nodes.
+     */
+    'reduce' : function(fn, acc, lo, hi, context){
+        return this._reduce( this._root, fn, acc,
+            lo || this.min(), hi || this.max(), context || this );
+    },
+    
+    _reduce : function(x, fn, acc, lo, hi, context){
+        if (!x) return acc;
+        if (lo < x.key) acc = this._reduce(x.left, fn, acc, lo, hi, context);
+        if (lo <= x.key && hi >= x.key) acc = fn.call(context, acc, x.val, x.key, this);
+        if (hi > x.key) acc = this._reduce(x.right, fn, acc, lo, hi, context);
+        return acc;
+    },
+    
+    /**
+     * Returns a new tree with the same keyset and values
+     * generated by applying the given function.
+     */
+    'map' : function(fn, lo, hi, context){
+        return this.reduce(function(newTree, v, k, tree){
+            newTree.setKey(k, fn.call(this, v, k, tree));
+            return newTree;
+        }, new RedBlackTree(), lo, hi, context);
+    },
+    
+    /**
+     * Iterates across keys between [lo..hi].
+     */
+    'forEach' : function(fn, lo, hi, context){
+        this.reduce(function(acc, v, k, tree){
+            fn.call(this, v, k, tree);
+        }, null, lo, hi, context);
+    },
+    
+    'toString' : function(){
+        return this.className+"(size="+this.size()+", height="+this.height()+")";
+    }
+    
+
+});
+
+RedBlackTree['RED']   = RED;
+RedBlackTree['BLACK'] = BLACK;
+RedBlackTree['Node']  = Node;
+this['RedBlackTree']  = RedBlackTree;
+
+})();
diff --git a/src/simoon/ability/ability.js b/src/simoon/ability/ability.js
new file mode 100644 (file)
index 0000000..33664e9
--- /dev/null
@@ -0,0 +1,61 @@
+Ability = Agent.subclass('Ability', {
+    init : function(owner, target, loc){
+        this.owner  = owner;
+        this.facing = owner.facing;
+        this.target = target;
+        
+        Agent.init.call(this, owner.game, owner.align);
+        
+        if ( loc ) {
+            var x = loc.x, y = loc.y;
+        } else {
+            var x = owner.front()
+            ,   y = owner.loc.toSquare().midpoint().y;
+        }
+        
+        this.setLocation(x,y);
+    },
+    blocking : false,
+    ticks    : 0,
+    
+    stats : {
+        move      : 2.0, // move speed (squares/sec)
+        range     : 0.1  // attack range (squares)
+    },
+    
+    fillStats : function(){
+        this.stats = Y( {},
+            Agent.fillStats(this.owner.stats),
+            Agent.fillStats(this.stats) ).end();
+    },
+    
+    act : function(){
+        this.ticks += 1;
+        Agent.prototype.act.call(this);
+    },
+    
+    filterTarget : function(unit){
+        return (unit.align !== this.align && unit !== this && unit !== this.owner);
+    },
+    
+    doAttack : function(target){
+        this.target = target;
+        var damage = Calc.unitDamage(this, target),
+            killed = false;
+        
+        target.stats.hp -= damage;
+        if ( target.stats.hp <= 0 ) {
+            killed = true;
+            target.destroy();
+            // this.game.killAgent(target);
+        }
+        this.destroy();
+        
+        logger(this.owner+"'s", this, 'hits', target, 'for', damage, 'damage'+(killed? ', killing it!' : '!'));
+    },
+    
+    toString : function(){
+        return this.className+this.id;
+    }
+    
+});
\ No newline at end of file
diff --git a/src/simoon/ability/laser.js b/src/simoon/ability/laser.js
new file mode 100644 (file)
index 0000000..99c6b1b
--- /dev/null
@@ -0,0 +1,75 @@
+Laser = Ability.subclass('Laser', {
+    init : function(owner, target, loc){
+        Ability.init.apply(this, arguments);
+        this.duration = (1000 / this.stats.speed) * 0.5;
+        this.expires  = NOW + this.duration;
+        
+        this.owner.addEventListener('destroy', this.destroy.bind(this));
+    },
+    duration : 1.0,
+    elapsed  : 0.0,
+    
+    stats : {
+        move  : 0.0,    // move speed (squares/sec)
+        range : COLUMNS // attack range (squares)
+    },
+    
+    act : function(){
+        if (this.dead) return;
+        
+        this.elapsed += ELAPSED;
+        
+        if (this.elapsed >= this.duration)
+            this.game.killAgent(this);
+        else
+            Agent.prototype.act.call(this);
+    },
+    
+    drawShape : function(ctx){
+        var thick = 4.0
+        ,   obox = this.owner.boundingBox
+        ,   tbox = this.target.boundingBox
+        ,   omid = obox.midpoint()
+        ,   tmid = tbox.midpoint()
+        ,   x1 = tbox.right.x + 2
+        ,   y1 = omid.y
+        ,   x2 = obox.left.x - 2
+        ,   y2 = omid.y
+        ;
+        
+        ctx.beginPath();
+        ctx.lineWidth = thick;
+        // ctx.fillStyle = '#FFF6AE';
+        ctx.strokeStyle = '#FFF6AE';
+        
+        ctx.moveTo(x1,y1);
+        ctx.lineTo(x2,y2);
+        
+        // ctx.fill();
+        ctx.stroke();
+        ctx.closePath();
+    },
+    
+    attack : function(target){
+        this.target = target;
+        
+        var full_damage = Calc.unitDamage(this, target)
+        ,   damage = full_damage * (ELAPSED / this.duration)
+        ,   killed = false
+        ;
+        
+        target.stats.hp -= damage;
+        if ( target.stats.hp <= 0 ) {
+            killed = true;
+            // this.game.killAgent(target);
+            target.destroy();
+        }
+        
+        if ( killed || (this.elapsed >= this.duration) ) {
+            this.destroy();
+            // this.game.killAgent(this);
+            logger(this.owner+"'s", this, 'hits', target, 'for', full_damage, 'damage'+(killed? ', killing it!' : '!'));
+        }
+    }
+    
+});
diff --git a/src/simoon/ability/projectile.js b/src/simoon/ability/projectile.js
new file mode 100644 (file)
index 0000000..9567d77
--- /dev/null
@@ -0,0 +1,28 @@
+Projectile = Ability.subclass('Projectile', {
+    stats : {
+        move      : 2.0, // move speed (squares/sec)
+        range     : 0.1  // attack range (squares)
+    },
+    
+    
+    
+    drawShape : function(ctx){
+        var radius = 5.0
+        ,   x1 = this.loc.x
+        ,   y1 = this.loc.y
+        ,   x2 = x1 + radius*2
+        ,   y2 = y1
+        ;
+        
+        ctx.beginPath();
+        ctx.lineWidth = 3;
+        ctx.fillStyle = '#5992FF';
+        ctx.strokeStyle = '#244792';
+        
+        ctx.arc(x1,y1, radius, 0, TWO_PI, false);
+        
+        ctx.fill();
+        ctx.stroke();
+        ctx.closePath();
+    }
+});
diff --git a/src/simoon/game/calc.js b/src/simoon/game/calc.js
new file mode 100644 (file)
index 0000000..9632226
--- /dev/null
@@ -0,0 +1,24 @@
+(function(){
+
+/* Inline all these */
+Calc = {
+    moveX : function(unit){
+        return unit.loc.x + (unit.facing * unit.stats.move * REF_SIZE * FRAMETH);
+    },
+    
+    rangeX : function(unit){
+        return unit.loc.x + (unit.facing * unit.stats.range * REF_SIZE);
+    },
+    
+    damage : function(power, armor, hard){
+        return Math.max(0, (power - hard) * (armor ? 1 - Math.log(armor/2)/5 : 1));
+    },
+    
+    unitDamage : function(attacker, defender){
+        var a = attacker.stats, d = defender.stats;
+        return Calc.damage(a.power, d.armor, d.hard);
+    }
+    
+};
+
+})();
diff --git a/src/simoon/game/draw.js b/src/simoon/game/draw.js
new file mode 100644 (file)
index 0000000..5eb677a
--- /dev/null
@@ -0,0 +1,73 @@
+Y(Game.prototype).extend({
+    
+    initDraw : function(){
+        this.canvas = $('<canvas />').appendTo(this.el);
+        this.ctx = this.canvas[0].getContext('2d');
+        this.resize();
+    },
+    
+    clearGrid : function(){
+        var ctx = this.ctx;
+        ctx.beginPath();
+        ctx.clearRect(0,0, this.canvas.width(),this.canvas.height());
+        ctx.closePath();
+    },
+    
+    drawGrid : function(){
+        var ctx = this.ctx
+        ,   w = REF_SIZE* COLUMNS
+        ,   h = REF_SIZE* ROWS;
+        
+        ctx.beginPath();
+        ctx.lineWidth   = 1;
+        ctx.strokeStyle = '#6E6E6E';
+        
+        for (var row=0, y=0; row<=ROWS; y = (++row) * REF_SIZE){
+            ctx.moveTo(0,y);
+            ctx.lineTo(w,y);
+        }
+        
+        for (var col=0, x=0; col<=COLUMNS; x = (++col) * REF_SIZE){
+            ctx.moveTo(x,0);
+            ctx.lineTo(x,h);
+        }
+        
+        ctx.stroke();
+        ctx.closePath();
+    },
+    
+    resize : function(){
+        var ratio = COLUMNS / ROWS
+        ,   el = this.el
+        ,   p  = el.parent()
+        ,   pw = p.width(), ph = p.height()
+        ,   pRatio = pw / ph
+        ;
+        
+        if ( ratio > pRatio )
+            CELL_SIZE = Math.floor((pw-GRID_OFFSET*2) / COLUMNS);
+        else
+            CELL_SIZE = Math.floor((ph-GRID_OFFSET*2) / ROWS);
+        
+        SCALE = CELL_SIZE/REF_SIZE;
+        
+        var w = COLUMNS*CELL_SIZE
+        ,   h = ROWS*CELL_SIZE
+        ,   canvas = this.canvas[0];
+        
+        this.el.width(w).height(h);
+        this.canvas.width(w).height(h);
+        canvas.width = w;
+        canvas.height = h;
+        
+        this.el.offset({
+            top :  (ph - h) / 2,
+            left : (pw - w) / 2
+        });
+        
+        this.ctx.scale(SCALE,SCALE);
+    }
+    
+});
+
+
diff --git a/src/simoon/game/game.js b/src/simoon/game/game.js
new file mode 100644 (file)
index 0000000..82af1ff
--- /dev/null
@@ -0,0 +1,54 @@
+
+Game = new Y.Class('Game', {
+    
+    init : function(el){
+        this.loop = new EventLoop(this, FRAME_RATE);
+        
+        // Seal all methods
+        // Y.bindAll(this);
+        this.resize = this.resize.bind(this);
+        this.tick   = this.tick.bind(this);
+        
+        this.el = $(el);
+        this.initMap();
+        this.initDraw();
+        
+        this.addEventListener('tick', this.tick);
+    },
+    showOverlay : false,
+    
+    /**
+     * Main Event Loop.
+     */
+    tick : function(evt){
+        var d = evt.data;
+        
+        NOW     = d.now;
+        ELAPSED = d.elapsed;
+        TICKS   = d.ticks;
+        
+        var ctx = this.ctx
+        ,   Ps  = this.abilities.clone()
+        ,   Us  = this.units.clone().sort(Unit.turnOrdering) // Copy to avoid mutation under iteration
+        ;
+        
+        this.clearGrid();
+        this.drawGrid();
+        
+        // Prompt all projectiles and units to act
+        Ps.invoke('act');
+        Us.invoke('act');
+        
+        // Prompt all remaining units to draw
+        this.abilities.invoke('draw', ctx);
+        this.units.invoke('draw', ctx);
+        
+        // XXX: Collect the dead
+        
+        this.grid.removeOverlay(this.el);
+        if (this.showOverlay) this.grid.overlay(this.el);
+    }
+    
+});
+
+
diff --git a/src/simoon/game/map.js b/src/simoon/game/map.js
new file mode 100644 (file)
index 0000000..7a6668f
--- /dev/null
@@ -0,0 +1,89 @@
+Y(Game.prototype).extend({
+    
+    initMap : function(){
+        var self = this;
+        
+        // this.blockers = new Y.YArray();
+        this.byId = {};
+        this.units = new Y.YArray();
+        this.abilities = new Y.YArray();
+        this.grid = new Grid(0,0, COLUMNS*REF_SIZE, ROWS*REF_SIZE, GRID_CAPACITY);
+        
+        Agent.addEventListener('create', function(evt){
+            self.addAgent(evt.instance);
+        });
+        Agent.addEventListener('destroy', function(evt){
+            self.killAgent(evt.instance);
+        });
+    },
+    
+    
+    // *** Path Map Management *** //
+    
+    addBlocker : function(agent){
+        var bb = agent.boundingBox;
+        if (agent.blocking && bb)
+            agent.region = this.grid.set(bb.x1,bb.y1, bb.x2,bb.y2, agent);
+        return agent;
+    },
+    
+    removeBlocker : function(agent){
+        if (agent.region)
+            this.grid.remove(agent.region);
+        return agent;
+    },
+    
+    updateBlocker : function(agent){
+        this.removeBlocker(agent);
+        this.addBlocker(agent);
+    },
+    
+    
+    // *** Agent Management *** //
+    
+    addAgent : function(agent){
+        agent.game = this;
+        if (agent.id === undefined) return agent;
+        
+        this.addBlocker(agent);
+        
+        if ( !this.byId[agent.id] ) {
+            this.byId[agent.id] = agent;
+            if (agent instanceof Ability)
+                this.abilities.push(agent);
+            else
+                this.units.push(agent);
+        }
+        
+        return agent;
+    },
+    
+    killAgent : function(agent){
+        delete this.byId[agent.id];
+        if (agent instanceof Ability)
+            this.abilities.remove(agent);
+        else
+            this.units.remove(agent);
+        
+        this.removeBlocker(agent);
+        return agent;
+    },
+    
+    moveAgentTo : function(agent, x,y){
+        this.removeBlocker(agent);
+        agent.setLocation(x,y);
+        this.addBlocker(agent);
+        return agent;
+    },
+    
+    getUnitAt : function(x,y){
+        return this.grid.get(x,y);
+    },
+    
+    getUnitsAt : function(x1,y1, x2,y2){
+        return this.grid.get(x1,y1, x2,y2);
+    }
+    
+});
+
+
diff --git a/src/simoon/globals.js b/src/simoon/globals.js
new file mode 100644 (file)
index 0000000..5cbd160
--- /dev/null
@@ -0,0 +1,32 @@
+var logger = new Log('#log')
+
+,   PI            = Math.PI
+,   TWO_PI        = PI*2
+,   HALF_PI       = PI/2
+
+,   COLUMNS       = 10
+,   ROWS          = 8
+,   GRID_CAPACITY = 32
+
+,   REF_SIZE      = 50
+,   CELL_SIZE     = REF_SIZE
+,   GRID_OFFSET   = 10
+
+,   VIEWPORT_DIST = 100
+,   MIN_X_DIST    = -1*VIEWPORT_DIST
+,   MAX_X_DIST    = COLUMNS*REF_SIZE + VIEWPORT_DIST
+
+,   FRAME_RATE    = 30
+,   MS_PER_FRAME  = 1000 / FRAME_RATE
+,   FRAMETH       = 1.0 / FRAME_RATE
+
+,   SCALE         = CELL_SIZE / REF_SIZE
+,   SCALE_SPEED   = SCALE / FRAME_RATE
+
+,   FACING_RIGHT  = 1
+,   FACING_LEFT   = -1
+
+,   NOW           = new Date().getTime() // Current tick's timestamp (ms)
+,   ELAPSED       = FRAMETH              // Time (ms) since previous tick
+,   TICKS         = 0                    // Ticks since start of game
+;
diff --git a/src/simoon/grid/grid.js b/src/simoon/grid/grid.js
new file mode 100644 (file)
index 0000000..047f8f4
--- /dev/null
@@ -0,0 +1,52 @@
+Grid = new Y.Class('Grid', QuadTree, {
+    
+    overlay : function(gridEl){
+        var w = this.width  *SCALE
+        ,   h = this.height *SCALE
+        ,   canvas = $('.overlay', gridEl)[0];
+        
+        if (!canvas)
+            canvas = $('<canvas class="overlay" style="position:absolute"/>').prependTo(gridEl)[0];
+        
+        $(canvas).width(w).height(h);
+        canvas.width = w;
+        canvas.height = h;
+        
+        var ctx = canvas.getContext('2d');
+        ctx.scale(SCALE, SCALE);
+        
+        // Clear the canvas
+        ctx.beginPath();
+        ctx.fillStyle = 'rgba(0,0,0,0)';
+        ctx.lineWidth = 0;
+        ctx.fillRect(this.x,this.y, w,h);
+        ctx.closePath();
+        
+        // Draw regions
+        this.reduce(function(acc, value, r, tree){
+            if ( acc[r.id] ) return acc;
+            
+            acc[r.id] = r;
+            
+            ctx.beginPath();
+            ctx.fillStyle   = 'rgba(255,255,255,0.1)';
+            ctx.lineWidth   = 1;
+            ctx.strokeStyle = 'rgba(255,255,255,0.2)';
+            ctx.rect(r.x1,r.y1, r.width,r.height);
+            ctx.fill();
+            ctx.stroke();
+            ctx.closePath();
+            
+            return acc;
+        }, {});
+        
+        $(canvas).show();
+    },
+    
+    removeOverlay : function(gridEl){
+        $('.overlay', gridEl).hide();
+    }
+    
+    
+});
+
diff --git a/src/simoon/player/player.js b/src/simoon/player/player.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/simoon/player/test-player.js b/src/simoon/player/test-player.js
new file mode 100644 (file)
index 0000000..b314e01
--- /dev/null
@@ -0,0 +1,48 @@
+TestPlayer = new Y.Class('TestPlayer', {
+    init : function(game){
+        var self = this;
+        this.game = game;
+        this.game.el.bind('click', function(evt){
+            evt.preventDefault();
+            evt.stopPropagation();
+            if (evt.shiftKey)
+                self.addTowerClick(evt);
+            else
+                self.addCreepClick(evt);
+        });
+    },
+    
+    pageToGrid : function(x,y){
+        var off = this.game.el.offset();
+        return {
+            'x' : (x - off.left)/SCALE,
+            'y' : (y - off.top)/SCALE
+        };
+    },
+    
+    pageToCell : function(x,y){
+        var pos = this.pageToGrid(x,y);
+        return {
+            'x' : Math.floor(pos.x / REF_SIZE) * REF_SIZE,
+            'y' : Math.floor(pos.y / REF_SIZE) * REF_SIZE
+        };
+    },
+    
+    addTowerClick : function(evt){
+        var cell = this.pageToCell(evt.pageX,evt.pageY)
+        ,   units = this.game.getUnitsAt(cell.x,cell.y, cell.x+REF_SIZE,cell.y+REF_SIZE);
+        if (units.size() === 0)
+            new Creep(this.game, 1).addToSquare(cell.x/REF_SIZE, cell.y/REF_SIZE);
+        else
+            logger("Cannot create Creep -- unit exists there!");
+    },
+    
+    addCreepClick : function(evt){
+        var cell = this.pageToCell(evt.pageX,evt.pageY)
+        ,   units = this.game.getUnitsAt(cell.x,cell.y, cell.x+REF_SIZE,cell.y+REF_SIZE);
+        if (units.size() === 0)
+            new Tower(this.game, 0).addToSquare(cell.x/REF_SIZE, cell.y/REF_SIZE);
+        else
+            logger("Cannot create Tower -- unit exists there!");
+    }
+});
\ No newline at end of file
diff --git a/src/simoon/simoon.js b/src/simoon/simoon.js
new file mode 100644 (file)
index 0000000..3af3351
--- /dev/null
@@ -0,0 +1,16 @@
+jQuery(function(){
+    Simoon = new Game('#grid');
+    
+    pl = Simoon.player = new TestPlayer(Simoon);
+    
+    for (var row=0; row<ROWS; row++) {
+        var c = new Creep(Simoon, 1);
+        c.addToSquare(COLUMNS-(4 + row % 2),row);
+    }
+    
+    for (var row=0; row<ROWS; row++) {
+        var t = new Tower(Simoon, 0);
+        t.addToSquare(0,row);
+    }
+});
+
diff --git a/src/simoon/ui.js b/src/simoon/ui.js
new file mode 100644 (file)
index 0000000..3f8a66a
--- /dev/null
@@ -0,0 +1,99 @@
+
+function shhh(evt){
+    if (!evt) return false;
+    evt.preventDefault();
+    evt.stopPropagation();
+}
+
+
+function toggleGame(evt){
+    if (Simoon.loop.running)
+        Simoon.stop();
+    else
+        Simoon.start();
+    
+    updateInfo();
+    return false;
+}
+
+// Update performance info periodically
+function updateInfo(){
+    var loop = Simoon.loop
+    ,   fps = loop.fps()
+    ,   n_units = Simoon.units.size()
+    ,   n_projs = Simoon.abilities.size()
+    ;
+    
+    $('#info [name=fps]').val( fps.toFixed(2) + " / " + loop.framerate );
+    $('#info [name=frame]').val( loop.frametime().toFixed(3)+" ms" );
+    $('#info #state').text( loop.running ? 'Running!' : ('Paused (tick '+TICKS+')') );
+    
+    $('#info [name=agents]').val( n_units+n_projs );
+    $('#info [name=units]').val( n_units );
+    $('#info [name=bullets]').val( n_projs );
+    
+    return false;
+}
+
+function fixStartText(){
+    var txt  = (Simoon.loop.running ? 'pause' : 'start');
+    $('.start_btn').text(txt).attr('title', txt);
+    return false;
+}
+function fixOverlayText(){
+    var txt = (Simoon.showOverlay ? 'hide' : 'show') + ' bounds';
+    $('.overlay_btn').text(txt).attr('title', txt);
+    return false;
+}
+
+
+
+jQuery(function($){
+    logger('Click "Start" to begin!');
+    $('#log').hide(); // Hide log-related crap
+    
+    
+    // Tick once to draw grid, initial units
+    Simoon.start();
+    Simoon.stop();
+    
+    // Add Buttons
+    // logger.menu.prepend('<a class="start_btn" />', ' | ', '<a class="overlay_btn"></a>', ' || ');
+    
+    setInterval(updateInfo, 1000);
+    updateInfo();
+    
+    // Update Text on Click
+    Simoon.addEventListener('start', fixStartText);
+    Simoon.addEventListener('stop',  fixStartText);
+    
+    // Start button (click or return key)
+    $('.start_btn').bind('click', toggleGame);
+    $(document).bind('keydown', 'return', toggleGame);
+    
+    $(document).bind('keydown', 'ctrl+l', function(evt){ $('#log').toggle(); });
+    $(document).bind('keydown', 'ctrl+o', function(evt){ Simoon.showOverlay = !(Simoon.showOverlay); });
+    
+    // Show Overlays
+    $('.overlay_btn').bind('click', function(evt){
+        Simoon.showOverlay = !(Simoon.showOverlay);
+        fixOverlayText();
+        return false;
+    });
+    
+    // Fix Starting text
+    fixStartText();
+    fixOverlayText();
+    
+    // Fix grid-size on resize
+    $(window).bind('resize', function(evt){
+        Simoon.resize(evt);
+        
+        if (!Simoon.loop.running) {
+            Simoon.start();
+            Simoon.stop();
+        }
+    });
+    
+    // 
+});
\ No newline at end of file
diff --git a/src/simoon/unit/agent.js b/src/simoon/unit/agent.js
new file mode 100644 (file)
index 0000000..a25406d
--- /dev/null
@@ -0,0 +1,243 @@
+(function(){
+var uid = 0;
+
+this.Agent = new Y.Class('Agent', {
+    init : function(game, align){
+        this.id    = uid++;
+        this.align = align;
+        this.game  = game;
+        
+        this.projectiles = new Y.YArray();
+        
+        // this.filterTarget = this.filterTarget.bind(this);
+        this.fillStats();
+        this.createCooldowns();
+        
+        this.fire('ready');
+    },
+    
+    // Attributes
+    stats: {
+        hp        : 100, // hitpoints
+        move      : 0.2, // move speed (squares/sec)
+        
+        range     : 2.0, // attack range (squares)
+        power     : 1,   // attack power
+        speed     : 1.0, // attacks/sec
+        
+        armor     : 0.0, // armor
+        hard      : 0    // hardness (damage reduction)
+    },
+    
+    // *** Bookkeeping *** //
+    
+    dead : false,
+    
+    destroy : function(){
+        if (this.dead) return;
+        
+        this.dead = true;
+        this.fire('destroy', this);
+    },
+    
+    loc : null,
+    boundingBox : null,
+    
+    facing : FACING_LEFT,
+    
+    // Bounding box size
+    width  : REF_SIZE,
+    height : REF_SIZE,
+    
+    // Offset from top-left when squaring this unit
+    offsetX : 0,
+    offsetY : 0,
+    
+    setLocation : function(x,y){
+        var loc = this.loc;
+        if (loc && loc.x === x && loc.y === y)
+            return loc;
+        loc = this.loc = new Loc(x,y);
+        this.boundingBox = new Rect(x,y, x+this.width,y+this.height);
+        return loc;
+    },
+    
+    /**
+     * Front-most point of the agent, accounting for facing.
+     */
+    front : function(){
+        return this.boundingBox[ this.facing === FACING_LEFT ? 'x1' : 'x2' ];
+    },
+    
+    
+    
+    // *** Gameplay Methods *** //
+    
+    addToSquare : function(col,row){
+        var x = col*REF_SIZE + this.offsetX
+        ,   y = row*REF_SIZE + this.offsetY;
+        this.setLocation(x,y);
+        this.game.addAgent(this);
+        return this;
+    },
+    
+    fillStats : function(){
+        this.stats = Agent.fillStats(this.stats);
+    },
+    
+    createCooldowns : function(){
+        this.cooldowns = Y({
+            attack: new Cooldown(1000 * this.stats.speed)
+        });
+    },
+    
+    /**
+     * Determines what the creep should do -- move, attack, etc.
+     */
+    act : function(){
+        if (this.dead) return this;
+        
+        this.cooldowns.invoke('update', NOW);
+        
+        var bb = this.boundingBox
+        ,   x1 = Calc.rangeX(this), x2 = bb.left.x
+        ,   y1 = bb.top.y,          y2 = bb.bottom.y
+        ,   units = this.game
+                .getUnitsAt(x1,y1, x2,y2)
+                .filter(this.filterTarget, this)
+        ,   target = this.closest(units)
+        ;
+        if (target)
+            this.attack(target);
+        else
+            this.move();
+        
+        if (bb.x1 <= MIN_X_DIST || bb.x2 >= MAX_X_DIST)
+            this.destroy();
+        
+        return this;
+    },
+    
+    filterTarget : function(unit){ return unit.align !== this.align; },
+    
+    closest : function(units){
+        if (units.length === 0 || units.size() === 0) return;
+        
+        var bb   = this.boundingBox
+        ,   d    = this.facing
+        ,   p    = (d === FACING_LEFT ? 'x2' : 'x1')
+        ,   x    = (d === FACING_LEFT ? bb.x1 : bb.x2)
+        ,   sign = (d === FACING_LEFT ? 1 : -1)
+        ,   best = units
+                .reduce(function(best, unit){
+                    var ub = unit.boundingBox
+                    ,   dist = (x - ub[p]) * sign
+                    
+                    if (dist < 0) dist = Infinity;
+                    
+                    if (!best || dist <= best.distance)
+                        return { unit:unit, distance:dist };
+                    else
+                        return best;
+                }, null);
+        return best.end().unit;
+    },
+    
+    move : function(){
+        this.game.moveAgentTo(this, Calc.moveX(this), this.loc.y);
+        return this;
+    },
+    
+    attack : function(unit){
+        var atk_cool = this.cooldowns.attr('attack');
+        if ( atk_cool.activate(NOW) ) {
+            // logger(this+'.attack(', unit, ')');
+            this.doAttack(unit);
+        }
+        return this;
+    },
+    
+    doAttack : function(target){
+        this.fireProjectile(target);
+    },
+    
+    fireProjectile : function(target){
+        var AbilityType = this.projectile
+        ,   p = new AbilityType(this, target);
+        this.projectiles.push(p);
+        this.game.addAgent(p);
+        return p;
+    },
+    
+    
+    
+    draw : function(ctx){
+        if (this.dead) return;
+        
+        this.drawShape(ctx);
+        if (this.blocking) this.drawHealth(ctx);
+    },
+    
+    drawShape : function(ctx){},
+    
+    drawHealth : function(ctx){
+        var stats = this.stats
+        ,   ratio = stats.hp / stats.hp_max
+        ,   x  = this.loc.x - this.offsetX
+        ,   sq = this.loc.toSquare()
+        ,   x1 = x + 5,     x2 = x + REF_SIZE - 5
+        ,   w  = x2-x1,     h  = 5
+        ,   y1 = sq.y2 - h - 3;
+        
+        // draw box outline
+        ctx.beginPath();
+        ctx.lineWidth = 1;
+        ctx.strokeStyle = "#ffffff";
+        ctx.fillStyle = "#980011";
+        ctx.rect(x1,y1, w,h);
+        ctx.fill();
+        // ctx.stroke();
+        ctx.closePath();
+        
+        // draw hp
+        ctx.beginPath();
+        ctx.fillStyle = "#83BB32";
+        ctx.rect(x1,y1, w*ratio,h);
+        ctx.fill();
+        ctx.closePath();
+        
+    },
+    
+    toString : function(){
+        return this.className+'(id='+this.id+', loc='+this.loc+')';
+    }
+});
+
+
+
+Y(Agent).extend({
+    
+    fillStats : function(stats){
+        var st = Y(stats)
+        ,   stats = st.clone().end()
+        ;
+        
+        st.forEach(function(v, k){
+            var k = Y(k)
+            ,   k_ = k.rtrim('_max')
+            ;
+            
+            if ( k.endsWith('_max') ) {
+                if ( stats[k_] === undefined )
+                    stats[k_] = v;
+                
+            } else if ( stats[k+'_max'] === undefined )
+                stats[k+'_max'] = v;
+            
+        });
+        
+        return stats;
+    }
+});
+
+})();
diff --git a/src/simoon/unit/creep.js b/src/simoon/unit/creep.js
new file mode 100644 (file)
index 0000000..c713fea
--- /dev/null
@@ -0,0 +1,47 @@
+(function(){
+var
+OFF  = 10,
+SIZE = REF_SIZE - 2*OFF,
+HALF = SIZE/2;
+
+this.Creep = Unit.subclass('Creep', {
+    facing  : FACING_LEFT,
+    projectile : Laser,
+    
+    // Attributes
+    stats: {
+        hp        : 10, // hitpoints
+        move      : 0.25, // move speed (squares/sec)
+        
+        range     : 2.0, // attack range (squares)
+        power     : 2.0, // attack power
+        speed     : 0.5, // attacks/sec
+        
+        armor     : 0.0, // armor
+        hard      : 0 // hardness (damage reduction)
+    },
+    offsetX : OFF,
+    offsetY : OFF,
+    width   : SIZE,
+    height  : SIZE,
+    
+    drawShape : function(ctx){
+        var x = this.loc.x
+        ,   y = this.loc.y;
+        
+        ctx.beginPath();
+        ctx.lineWidth = 0;
+        ctx.fillStyle = '#E73075';
+        
+        ctx.moveTo(x,y+HALF);
+        ctx.lineTo(x+SIZE,y);
+        ctx.lineTo(x+SIZE,y+SIZE);
+        ctx.lineTo(x,y+HALF);
+        
+        ctx.fill();
+        ctx.closePath();
+    }
+    
+});
+
+})();
diff --git a/src/simoon/unit/tower.js b/src/simoon/unit/tower.js
new file mode 100644 (file)
index 0000000..0f4b2e7
--- /dev/null
@@ -0,0 +1,43 @@
+(function(){
+var
+OFFSET = 10,
+SIZE   = REF_SIZE - 2*OFFSET,
+
+Tower = Unit.subclass('Tower', {
+    facing  : FACING_RIGHT,
+    projectile : Projectile,
+    
+    // Attributes
+    stats: {
+        hp        : 10, // hitpoints
+        move      : 0, // move speed (squares/sec)
+        
+        range     : 5, // attack range (squares)
+        power     : 1, // attack power
+        speed     : 1.0, // attacks/sec
+        
+        armor     : 10.0, // armor
+        hard      : 0 // hardness (damage reduction)
+    },
+    
+    offsetX : OFFSET,
+    offsetY : OFFSET,
+    width   : SIZE,
+    height  : SIZE,
+    
+    drawShape : function(ctx){
+        var x = this.loc.x
+        ,   y = this.loc.y;
+        
+        ctx.beginPath();
+        ctx.lineWidth = 0;
+        ctx.fillStyle = "#F25522";
+        ctx.rect(x,y, SIZE,SIZE);
+        ctx.fill();
+        ctx.closePath();
+    }
+    
+});
+this.Tower = Tower;
+
+})();
diff --git a/src/simoon/unit/unit.js b/src/simoon/unit/unit.js
new file mode 100644 (file)
index 0000000..4a66316
--- /dev/null
@@ -0,0 +1,26 @@
+Unit = Agent.subclass('Unit', {
+    blocking : true,
+    projectile : Projectile,
+    
+    move : function(){
+        var d  = this.facing
+        ,   bb = this.boundingBox
+        ,   x1 = Calc.moveX(this), x2 = bb.right.x - d
+        ,   y1 = bb.top.y, y2 = bb.bottom.y
+        ,   units = this.game.getUnitsAt(bb.left.x+(d*5),y1, x2,y2).remove(this);
+        if (units.size() === 0)
+            try {
+                this.game.moveAgentTo(this, x1,this.loc.y);
+            } catch (e) {
+                console.log('Error moving units to ('+x1+','+this.loc.y+')', e);
+            }
+    }
+    
+});
+
+Y(Unit).extend({
+    turnOrdering : function(u1, u2){
+        return Y(u1.stats.speed).compare( u2.stats.speed );
+    }
+    
+});
diff --git a/src/tanks/game.js b/src/tanks/game.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/tanks/lttl.js b/src/tanks/lttl.js
new file mode 100644 (file)
index 0000000..5dcafa1
--- /dev/null
@@ -0,0 +1,15 @@
+jQuery(function($){
+
+v = $('#viewport');
+
+L = new Layer(v)
+    .width(v.width())
+    .height(v.height())
+    .append( new Rect(50,50).position(50,50) )
+    .append(
+        new Rect(250,250).position(50,150) )
+    .appendTo(v)
+    .draw()
+;
+
+});
\ No newline at end of file
diff --git a/src/tanks/map.js b/src/tanks/map.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/tanks/tank.js b/src/tanks/tank.js
new file mode 100644 (file)
index 0000000..139597f
--- /dev/null
@@ -0,0 +1,2 @@
+
+
diff --git a/src/tanks/ui.js b/src/tanks/ui.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/u.js b/src/u.js
new file mode 100644 (file)
index 0000000..aadb066
--- /dev/null
+++ b/src/u.js
@@ -0,0 +1,88 @@
+
+var 
+undefined,
+_window   = this,
+_document = document,
+_Array    = Array,
+_Object   = Object,
+_encode   = encodeURIComponent,
+_decode   = decodeURIComponent;
+
+function slice(a){
+    var _asl = _Array.prototype.slice;
+    return _asl.apply(a, _asl.call(arguments, 1));
+}
+
+function strip(s){ 
+    return s.replace(/(^\s+|\s+$)/g, '');
+}
+
+function reduce( o, fn, acc, cxt ){
+    if ( !o )
+        return acc;
+    
+    if ( o instanceof _Array )
+        for( var i = 0, len = o.length, v = o[0]; i < len; v = o[++i] )
+            acc = fn.call( cxt || o, acc, v, i, o );
+    else 
+        for ( var name in o )
+            acc = fn.call( cxt || o, acc, o[ name ], name, o );
+    
+    return acc;
+}
+
+function map( o, fn, cxt ){
+    if ( !o )
+        return o;
+    
+    var acc;
+    if ( o instanceof _Array )
+        acc = new _Array(o.length);
+    else
+        acc = new (o.constructor || _Object)();
+    
+    return reduce(o, function(acc, v, k, o){
+        acc[k] = fn.call(this, v, k, o);
+        return acc;
+    }, acc, cxt);
+}
+
+function toKV(o, del){
+    return reduce(o, function(acc, v, k){
+        acc.push( _encode(k) + '=' + _encode(v) ); return acc;
+    }, []).join(del || "&");
+}
+
+function fromKV(q, del){
+    return reduce(q.split(del || '&'), function(acc, pair){
+        var kv = pair.split('='), k = kv[0], v = kv[1];
+        if (k) acc[_decode(k)] = _decode(v);
+        return acc;
+    }, {});
+}
+
+// Takes any number of arguments and dumps their (k,v) pairs on A
+function extend( A, B ){
+    return reduce(slice(arguments,1), function(A, donor){
+        return reduce(donor, function(o, v, k){ 
+            o[k] = v;
+            return o;
+        }, A);
+    }, A);
+}
+
+function bind( fn, context ){
+    var args = slice(arguments,0), 
+        fn = args.shift(), 
+        context = args.shift();
+    return function(){
+        return fn.apply( context, args.concat(slice(arguments,0)) );
+    };
+}
+
+function setCookie(k, v, expires, path){
+    expires = 'expires='+(expires || "Sun, 24-Mar-2024 11:11:11 GMT");
+    path = 'path='+(path || "/");
+    _document.cookie = [ k+'='+_enc(v), expires, path ].join('; ');
+    return fromKV(_document.cookie, '; ');
+}