Quellcode durchsuchen

big big big update

Signed-off-by: bachy <git@g-u-i.net>
bachy vor 12 Jahren
Ursprung
Commit
3b4f1e024f
96 geänderte Dateien mit 32139 neuen und 430 gelöschten Zeilen
  1. 27 0
      images/search.ai
  2. BIN
      images/search.png
  3. BIN
      js/libraries/balupton-history.js-e84ad00.zip
  4. 1 0
      js/libraries/balupton-history/.gitignore
  5. 76 0
      js/libraries/balupton-history/History.md
  6. 245 0
      js/libraries/balupton-history/README.md
  7. 281 0
      js/libraries/balupton-history/buildr.coffee
  8. 30 0
      js/libraries/balupton-history/demo/bcherry-orig.html
  9. 30 0
      js/libraries/balupton-history/demo/bcherry.html
  10. 37 0
      js/libraries/balupton-history/demo/chrome.html
  11. 19 0
      js/libraries/balupton-history/demo/index.html
  12. 43 0
      js/libraries/balupton-history/demo/native-auto.html
  13. 62 0
      js/libraries/balupton-history/demo/native.html
  14. 23 0
      js/libraries/balupton-history/demo/navigator.html
  15. 61 0
      js/libraries/balupton-history/demo/safari.html
  16. 9 0
      js/libraries/balupton-history/license.txt
  17. 57 0
      js/libraries/balupton-history/package.json
  18. 0 0
      js/libraries/balupton-history/scripts/bundled/html4+html5/jquery.history.js
  19. 0 0
      js/libraries/balupton-history/scripts/bundled/html4+html5/mootools.history.js
  20. 0 0
      js/libraries/balupton-history/scripts/bundled/html4+html5/native.history.js
  21. 0 0
      js/libraries/balupton-history/scripts/bundled/html4+html5/right.history.js
  22. 0 0
      js/libraries/balupton-history/scripts/bundled/html4+html5/zepto.history.js
  23. 0 0
      js/libraries/balupton-history/scripts/bundled/html5/jquery.history.js
  24. 0 0
      js/libraries/balupton-history/scripts/bundled/html5/mootools.history.js
  25. 0 0
      js/libraries/balupton-history/scripts/bundled/html5/native.history.js
  26. 0 0
      js/libraries/balupton-history/scripts/bundled/html5/right.history.js
  27. 0 0
      js/libraries/balupton-history/scripts/bundled/html5/zepto.history.js
  28. 1 0
      js/libraries/balupton-history/scripts/compressed/history.adapter.jquery.js
  29. 0 0
      js/libraries/balupton-history/scripts/compressed/history.adapter.mootools.js
  30. 0 0
      js/libraries/balupton-history/scripts/compressed/history.adapter.native.js
  31. 1 0
      js/libraries/balupton-history/scripts/compressed/history.adapter.right.js
  32. 1 0
      js/libraries/balupton-history/scripts/compressed/history.adapter.zepto.js
  33. 0 0
      js/libraries/balupton-history/scripts/compressed/history.html4.js
  34. 0 0
      js/libraries/balupton-history/scripts/compressed/history.js
  35. 0 0
      js/libraries/balupton-history/scripts/compressed/json2.js
  36. 77 0
      js/libraries/balupton-history/scripts/uncompressed/history.adapter.jquery.js
  37. 84 0
      js/libraries/balupton-history/scripts/uncompressed/history.adapter.mootools.js
  38. 121 0
      js/libraries/balupton-history/scripts/uncompressed/history.adapter.native.js
  39. 78 0
      js/libraries/balupton-history/scripts/uncompressed/history.adapter.right.js
  40. 74 0
      js/libraries/balupton-history/scripts/uncompressed/history.adapter.zepto.js
  41. 621 0
      js/libraries/balupton-history/scripts/uncompressed/history.html4.js
  42. 1943 0
      js/libraries/balupton-history/scripts/uncompressed/history.js
  43. 480 0
      js/libraries/balupton-history/scripts/uncompressed/json2.js
  44. 22 0
      js/libraries/balupton-history/tests.src/_header.php
  45. 45 0
      js/libraries/balupton-history/tests.src/all.php
  46. 51 0
      js/libraries/balupton-history/tests.src/each.php
  47. 23 0
      js/libraries/balupton-history/tests.src/index.php
  48. 13 0
      js/libraries/balupton-history/tests/.htaccess
  49. 43 0
      js/libraries/balupton-history/tests/html4+html5.jquery.html
  50. 43 0
      js/libraries/balupton-history/tests/html4+html5.mootools.html
  51. 43 0
      js/libraries/balupton-history/tests/html4+html5.native.html
  52. 43 0
      js/libraries/balupton-history/tests/html4+html5.right.html
  53. 43 0
      js/libraries/balupton-history/tests/html4+html5.zepto.html
  54. 43 0
      js/libraries/balupton-history/tests/html5.jquery.html
  55. 43 0
      js/libraries/balupton-history/tests/html5.mootools.html
  56. 43 0
      js/libraries/balupton-history/tests/html5.native.html
  57. 43 0
      js/libraries/balupton-history/tests/html5.right.html
  58. 43 0
      js/libraries/balupton-history/tests/html5.zepto.html
  59. 3 0
      js/libraries/balupton-history/tests/image.php
  60. 23 0
      js/libraries/balupton-history/tests/index.html
  61. 254 0
      js/libraries/balupton-history/tests/tests.js
  62. 9046 0
      js/libraries/balupton-history/vendor/jquery.js
  63. 5815 0
      js/libraries/balupton-history/vendor/mootools.js
  64. 6 0
      js/libraries/balupton-history/vendor/qunit/.gitignore
  65. 27 0
      js/libraries/balupton-history/vendor/qunit/README.md
  66. 21 0
      js/libraries/balupton-history/vendor/qunit/package.json
  67. 225 0
      js/libraries/balupton-history/vendor/qunit/qunit/qunit.css
  68. 1442 0
      js/libraries/balupton-history/vendor/qunit/qunit/qunit.js
  69. 24 0
      js/libraries/balupton-history/vendor/qunit/test/headless.html
  70. 19 0
      js/libraries/balupton-history/vendor/qunit/test/index.html
  71. 17 0
      js/libraries/balupton-history/vendor/qunit/test/logs.html
  72. 150 0
      js/libraries/balupton-history/vendor/qunit/test/logs.js
  73. 1421 0
      js/libraries/balupton-history/vendor/qunit/test/same.js
  74. 324 0
      js/libraries/balupton-history/vendor/qunit/test/test.js
  75. 6014 0
      js/libraries/balupton-history/vendor/right.js
  76. 1193 0
      js/libraries/balupton-history/vendor/zepto.js
  77. 0 0
      js/libraries/jquery.history.js
  78. 4 0
      js/materio_search_api_ajax-ck.js
  79. 206 0
      js/materio_search_api_ajax.js
  80. 49 0
      materio_search_api.admin.inc
  81. 10 8
      materio_search_api.info
  82. 362 0
      materio_search_api.module
  83. 252 0
      materio_search_api.pages.inc
  84. 6 6
      materio_search_api_ajax.info
  85. 37 0
      materio_search_api_ajax.module
  86. 60 0
      materio_search_api_ajax.pages.inc
  87. 0 4
      materiobaseajax-ck.js
  88. 0 37
      materiobaseajax.js
  89. 0 74
      materiobaseajax.module
  90. 0 22
      materiobaseajax.pages.inc
  91. 0 176
      materiobasemod.module
  92. 0 100
      materiobasemod.pages.inc
  93. 57 0
      templates/materio-search-api-results.tpl.php
  94. 3 0
      templates/materio-search-api-search-block.tpl.php
  95. 3 0
      templates/materio-search-api-select-viewmode-block.tpl.php
  96. 0 3
      templates/materiobase-search-block.tpl.php

Datei-Diff unterdrückt, da er zu groß ist
+ 27 - 0
images/search.ai


BIN
images/search.png


BIN
js/libraries/balupton-history.js-e84ad00.zip


+ 1 - 0
js/libraries/balupton-history/.gitignore

@@ -0,0 +1 @@
+.build

+ 76 - 0
js/libraries/balupton-history/History.md

@@ -0,0 +1,76 @@
+## History
+
+- v1.7.1 - October 4 2011
+	- Added a new native adapter which is framework agnostic (can be used with, or without any framework)
+	- Provided bundled files
+	- Added RightJS adapter
+	- Updated supported browser listing
+	- Added sessionStorage support in core instead of optional Amplify.js Store support
+	- Fixed issue with state id generation growing slower over time
+	- Closes #104, #95, #102, #92, #81, #90, #94, #93, #91, #67, #83, #54, #45
+
+- v1.7.0 - April 1 2011
+	- Added `History.enabled` property (refer to usage section). This reflects whether or not History.js is enabled for our particular browser. For instance, if we have not included support for a HTML4 browser and we are accessing through a HTML4 browser then `History.enabled` will be `false`.
+	- Added (optional but recommended) Data Persistance and Synchronisation Support thanks to [AppendTo's](http://appendto.com/) [Amplify.js](http://amplifyjs.com/) (refer to installation and compatibility sections for details)
+	- Made HTML5 SUIDs more transparent - [Reported](https://github.com/balupton/history.js/issues#issue/34) by [azago](https://github.com/azago) and [Mark Jaquith](http://markjaquith.com/)
+	- Fixed Session Storage Issue - Reported by a whole bunch of different people; [one](https://github.com/balupton/history.js/issues#issue/36), [two](https://github.com/balupton/history.js/issues#issue/37), [three](http://getsatisfaction.com/balupton/topics/history_js_1_6_losing_state_after_manual_page_reload)
+	- Fixed URL Encoding Issue - [Reported](https://github.com/balupton/history.js/issues/#issue/33) by [Rob Madole](http://robmadole.com/)
+	- Disabled support for IE6,7,8 when using the Prototype Adapter (there is nothing we can do about this, it is due to a bug in the prototype library) - [Reported](https://github.com/balupton/history.js/issues#issue/39) by [Sindre Wimberger](http://sindre.at/)
+	- URLs in the State Hashes for HTML4 Browsers are now even shorter - [Discussion](https://github.com/balupton/history.js/issues#issue/28)
+	- Fixed a issue with the MooTools Adapter and JSON with IE7 and IE8
+
+- v1.6.0 - March 22 2011
+	- Added Zepto adapter thanks to [Matt Garrett](http://twitter.com/#!/matthewgarrett)
+	- The readme now references the supported versions of the libraries we use
+	- Updated vendors to the most recent versions. jQuery 1.5.1 and Mootools 1.3.1
+	- Reverted versions of Safari iOS prior to version 4.3 to be HTML4 browsers, Safari iOS 4.3 is a HTML5 browser
+	- Refined code in History.js and its adapters
+	- Fixed issue with extra state being inserted on Safari 5 requiring an extra click on the back button to go home - [Reported](https://github.com/balupton/history.js/issues#issue/17) by [Rob Madole](http://robmadole.com/)
+	- Fixed issue with Safari 5 and Safari iOS 4 sometimes failing to apply the state change under busy conditions - Solution conceived with [Matt Garrett](http://twitter.com/matthewgarrett)
+	- Fixed issue with HTML4 browsers requiring a query-string in the urls of states - [Reported](https://github.com/balupton/history.js/issues#issue/26) by [azago](https://github.com/azago)
+	- Fixed issue with HTML4 browsers requiring title in the states in order to use state data - [Reported](https://github.com/balupton/history.js/issues#issue/25) by [Jonathan McLaughlin](http://system-werks.com/)
+	- Fixed issue with HTML4 browsers failing is a state is pushed/replaced twice in a row - [Reported](https://github.com/balupton/history.js/issues#issue/17) by [Joey Baker](http://byjoeybaker.com/)
+	- **B/C BREAK:** The `statechange` event now only fires if the state has changed; it no longer fires on page initialisation. This is following the [Firefox 4 History API Changes](http://hacks.mozilla.org/2011/03/history-api-changes-in-firefox-4/) which we agree with - this breaks standard, but makes more sense.
+
+- v1.5.0 - February 12 2011
+	- Moved to UglifyJS instead of Google Closure
+	- Split HTML4 functionality from HTML5 functionality
+	- Installation details have changed (the filenames are different)
+
+- v1.4.1 - February 10 2011
+	- Added HTML History API Support for Safari 5 and Safari iOS 4.2.1
+	- Cleaned code a bit (mostly with unit tests)
+
+- v1.4.0 - February 10 2011
+	- Unit Testing now uses [QUnit](http://docs.jquery.com/Qunit)
+	- Corrected Safari 5 Support
+	- Now uses queues instead of timeouts
+		- This means the API works exactly as expected, no more need to wrap calls in timeouts
+	- Included a Subscribe Form in the Demo for Version Updates via Email
+	- Small updates to Documentation
+
+- v1.3.1 - February 4 2011
+	- Improved Documentation
+
+- v1.3.0 - January 31 2011
+	- Support for cleaner HTML4 States
+
+- v1.2.1 - January 30 2011
+	- Fixed History.log always being called - [reported by dlee](https://github.com/balupton/history.js/issues/#issue/2)
+	- Re-Added `History.go(index)` support
+
+- v1.2.0 - January 25 2011
+	- Support for HTML4 States in HTML5 Browsers (added test)
+	- Updates of Documentation
+
+- v1.1.0 - January 24 2011
+	- Developed a series of automated test cases
+	- Fixed issue with traditional anchors
+	- Fixed issue with differing replaceState functionality in HTML4 Browsers
+	- Fixed issue with Google Chrome artefacts being carried over to the initial state
+	- Provided `onstatechange` and `onanchorchange` events
+
+- v1.0.0 - January 22 2011
+	- Supported `History.pushState` and `History.replaceState` degradation
+	- Supported jQuery, MooTools and Prototype Frameworks
+

+ 245 - 0
js/libraries/balupton-history/README.md

@@ -0,0 +1,245 @@
+Welcome to History.js (v1.7.1 - October 4 2011)
+==================
+
+[![Flattr this project](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=balupton&url=https://github.com/balupton/history.js&title=History.js&language=&tags=github&category=software)
+
+
+This project is the successor of [jQuery History](http://balupton.com/projects/jquery-history), it aims to:
+
+- Follow the [HTML5 History API](https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history) as much as possible
+- Provide a cross-compatible experience for all HTML5 Browsers (they all implement the HTML5 History API a little bit differently causing different behaviours and sometimes bugs - History.js fixes this ensuring the experience is as expected / the same / great throughout the HTML5 browsers)
+- Provide a backwards-compatible experience for all HTML4 Browsers using a hash-fallback (including continued support for the HTML5 History API's `data`, `title`, `pushState` and `replaceState`) with the option to [remove HTML4 support if it is not right for your application](https://github.com/balupton/history.js/wiki/Intelligent-State-Handling)
+- Provide a forwards-compatible experience for HTML4 States to HTML5 States (so if a hash-fallbacked url is accessed by a HTML5 browser it is naturally transformed into its non-hashed url equivalent)
+- Provide support for as many javascript frameworks as possible via adapters; especially [jQuery](http://jquery.com/), [MooTools](http://mootools.net/), [Prototype](http://www.prototypejs.org/) and [Zepto](http://zeptojs.com/)
+
+
+## Usage
+
+### Working with History.js:
+
+``` javascript
+(function(window,undefined){
+
+	// Prepare
+	var History = window.History; // Note: We are using a capital H instead of a lower h
+	if ( !History.enabled ) {
+		 // History.js is disabled for this browser.
+		 // This is because we can optionally choose to support HTML4 browsers or not.
+		return false;
+	}
+
+	// Bind to StateChange Event
+	History.Adapter.bind(window,'statechange',function(){ // Note: We are using statechange instead of popstate
+		var State = History.getState(); // Note: We are using History.getState() instead of event.state
+		History.log(State.data, State.title, State.url);
+	});
+
+	// Change our States
+	History.pushState({state:1}, "State 1", "?state=1"); // logs {state:1}, "State 1", "?state=1"
+	History.pushState({state:2}, "State 2", "?state=2"); // logs {state:2}, "State 2", "?state=2"
+	History.replaceState({state:3}, "State 3", "?state=3"); // logs {state:3}, "State 3", "?state=3"
+	History.pushState(null, null, "?state=4"); // logs {}, '', "?state=4"
+	History.back(); // logs {state:3}, "State 3", "?state=3"
+	History.back(); // logs {state:1}, "State 1", "?state=1"
+	History.back(); // logs {}, "Home Page", "?"
+	History.go(2); // logs {state:3}, "State 3", "?state=3"
+
+})(window);
+```
+
+To ajaxify your entire website with the HTML5 History API, History.js and jQuery [this snippet of code](https://gist.github.com/854622) is all you need. It's that easy.
+
+### How would the above operations look in a HTML5 Browser?
+
+1. www.mysite.com
+1. www.mysite.com/?state=1
+1. www.mysite.com/?state=2
+1. www.mysite.com/?state=3
+1. www.mysite.com/?state=4
+1. www.mysite.com/?state=3
+1. www.mysite.com/?state=1
+1. www.mysite.com
+1. www.mysite.com/?state=3
+
+> Note: These urls also work in HTML4 browsers and Search Engines. So no need for the hashbang (`#!`) fragment-identifier that google ["recommends"](https://github.com/balupton/history.js/wiki/Intelligent-State-Handling).
+
+### How would they look in a HTML4 Browser?
+
+1. www.mysite.com
+1. www.mysite.com/#?state=1&_suid=1
+1. www.mysite.com/#?state=2&_suid=2
+1. www.mysite.com/#?state=3&_suid=3
+1. www.mysite.com/#?state=4
+1. www.mysite.com/#?state=3&_suid=3
+1. www.mysite.com/#?state=1&_suid=1
+1. www.mysite.com
+1. www.mysite.com/#?state=3&_suid=3
+
+> Note 1: These urls also work in HTML5 browsers - we use `replaceState` to transform these HTML4 states into their HTML5 equivalents so the user won't even notice :-)
+>
+> Note 2: These urls will be automatically url-encoded in IE6 to prevent certain browser-specific bugs.
+>
+> Note 3: Support for HTML4 browsers (this hash fallback) is optional [- why supporting HTML4 browsers could be either good or bad based on my app's use cases](https://github.com/balupton/history.js/wiki/Intelligent-State-Handling)
+
+### What's the deal with the SUIDs used in the HTML4 States?
+
+- SUIDs (State Unique Identifiers) are used when we utilise a `title` and/or `data` in our state. Adding a SUID allows us to associate particular states with data and titles while keeping the urls as simple as possible (don't worry it's all tested, working and a lot smarter than I'm making it out to be).
+- If you aren't utilising `title` or `data` then we don't even include a SUID (as there is no need for it) - as seen by State 4 above :-)
+- We also shrink the urls to make sure that the smallest url will be used. For instance we will adjust `http://www.mysite.com/#http://www.mysite.com/projects/History.js` to become `http://www.mysite.com/#/projects/History.js` automatically. (again tested, working, and smarter).
+- It works with domains, subdomains, subdirectories, whatever - doesn't matter where you put it. It's smart.
+- Safari 5 will also have a SUID appended to the URL, it is entirely transparent but just a visible side-effect. It is required to fix a bug with Safari 5.
+
+### Is there a working demo?
+
+- Sure is, give it a download and navigate to the demo directory in your browser :-)
+- If you are after something a bit more adventurous than a end-user demo, open up the tests directory in your browser and editor - it'll rock your world and show all the vast use cases that History.js supports.
+
+
+## Download & Installation
+
+- Download History.js and upload it to your webserver. Download links: [tar.gz](https://github.com/balupton/history.js/tarball/master) or [zip](https://github.com/balupton/history.js/zipball/master)
+
+- Include History.js
+
+	- For [jQuery](http://jquery.com/) v1.3+
+
+		``` html
+		<script src="http://www.yourwebsite.com/history.js/scripts/bundled/html4+html5/jquery.history.js"></script>
+		```
+
+	- For [Mootools](http://mootools.net/) v1.3+
+
+		``` html
+		<script src="http://www.yourwebsite.com/history.js/scripts/bundled/html4+html5/mootools.history.js"></script>
+		```
+
+	- For [Right.js](http://rightjs.org/) v2.2+
+
+		``` html
+		<script src="http://www.yourwebsite.com/history.js/scripts/bundled/html4+html5/right.history.js"></script>
+		```
+
+	- For [Zepto](http://zeptojs.com/) v0.5+
+
+		``` html
+		<script src="http://www.yourwebsite.com/history.js/scripts/bundled/html4+html5/zepto.history.js"></script>
+		```
+
+	- For everything else
+
+		``` html
+		<script src="http://www.yourwebsite.com/history.js/scripts/bundled/html4+html5/native.history.js"></script>
+		```
+
+> Note: If you want to only support HTML5 Browsers and not HTML4 Browsers (so no hash fallback support) then just change the `/html4+html5/` part in the urls to just `/html5/`. [Why supporting HTML4 browsers could be either good or bad based on my app's use cases](https://github.com/balupton/history.js/wiki/Intelligent-State-Handling)
+
+
+## Get Updates
+
+- For Commit RSS/Atom Updates:
+	- You can subscribe via the [GitHub Commit Atom Feed](http://feeds.feedburner.com/historyjs)
+- For GitHub News Feed Updates:
+	- You can click the "watch" button up the top right of History.js's [GitHub Project Page](https://github.com/balupton/history.js)
+
+
+## Get Support
+
+- History.js is maintained by people like you. If you find a bug, report it to the [GitHub Issue Tracker](https://github.com/balupton/history.js/issues). If you've fixed a bug submit a [Pull Request](https://github.com/balupton/history.js/pulls) and add your fork to the [Network Wiki Page](https://github.com/balupton/history.js/wiki/Network).
+
+- If you would like paid support and trainings, or have job offers, then refer to the [Network Wiki Page](https://github.com/balupton/history.js/wiki/Network). If you are qualified with History.js, then be sure to add your details to that page too.
+
+- If your company uses History.js on your projects, and would like to see it grow and prosper (better documentation, bugfixes, upgrades, maintenance, etc.) and would love to become a corporate sponsor then do email sponsor@bevry.me
+
+- If you would like free support for History.js, then [post your question](http://stackoverflow.com/questions/ask) on [Stackoverflow](http://stackoverflow.com/about) and be sure to use the `history.js` tag when asking your question.
+
+- If you've created a website that uses History.js, or know of one. Then be sure to add it to the [Showcase Wiki Page](https://github.com/balupton/history.js/wiki/Showcase).
+
+- If you'd love to +1 or like this project, then be sure to tweet about it and click the "watch" button up the top of its [Project Page](https://github.com/balupton/history.js).
+
+- For anything else, refer to the [History.js GitHub Wiki Site](https://github.com/balupton/history.js/wiki).
+
+Thanks! every bit of help really does make a difference!
+
+
+## Browsers: Tested and Working In
+
+### HTML5 Browsers
+
+- Firefox 4+
+- Chrome 8+
+- Opera 11.5
+- Safari 5.0+
+- Safari iOS 4.3+
+
+### HTML4 Browsers
+
+- IE 6, 7, 8, 9
+- Firefox 3
+- Opera 10, 11.0
+- Safari 4
+- Safari iOS 4.2, 4.1, 4.0, 3.2
+
+
+## Exposed API
+
+### Functions
+
+- `History.pushState(data,title,url)` <br/> Pushes a new state to the browser; `data` can be null or an object, `title` can be null or a string, `url` must be a string
+- `History.replaceState(data,title,url)` <br/> Replaces the existing state with a new state to the browser; `data` can be null or an object, `title` can be null or a string, `url` must be a string
+- `History.getState()` <br/> Gets the current state of the browser, returns an object with `data`, `title` and `url`
+- `History.getHash()` <br/> Gets the current hash of the browser
+- `History.Adapter.bind(element,event,callback)` <br/> A framework independent event binder, you may either use this or your framework's native event binder.
+- `History.Adapter.trigger(element,event)` <br/> A framework independent event trigger, you may either use this or your framework's native event trigger.
+- `History.Adapter.onDomLoad(callback)` <br/> A framework independent onDomLoad binder, you may either use this or your framework's native onDomLoad binder.
+- `History.back()` <br/> Go back once through the history (same as hitting the browser's back button)
+- `History.forward()` <br/> Go forward once through the history (same as hitting the browser's forward button)
+- `History.go(X)` <br/> If X is negative go back through history X times, if X is positive go forwards through history X times
+- `History.log(...)` <br/> Logs messages to the console, the log element, and fallbacks to alert if neither of those two exist
+- `History.debug(...)` <br/> Same as `History.log` but only runs if `History.debug.enable === true`
+
+### Events
+
+- `window.onstatechange` <br/> Fired when the state of the page changes (does not include hash changes)
+- `window.onanchorchange` <br/> Fired when the anchor of the page changes (does not include state hashes)
+
+
+## Notes on Compatibility
+
+- History.js **solves** the following browser bugs:
+	- HTML5 Browsers
+		- Chrome 8 sometimes does not contain the correct state data when traversing back to the initial state
+		- Safari 5, Safari iOS 4 and Firefox 3 and 4 do not fire the `onhashchange` event when the page is loaded with a hash
+		- Safari 5 and Safari iOS 4 do not fire the `onpopstate` event when the hash has changed unlike the other browsers
+		- Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call / [bug report](https://bugs.webkit.org/show_bug.cgi?id=56249)
+		- Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions / [bug report](https://bugs.webkit.org/show_bug.cgi?id=42940)
+		- Google Chrome 8,9,10 and Firefox 4 prior to the RC will always fire `onpopstate` once the page has loaded / [change recommendation](http://hacks.mozilla.org/2011/03/history-api-changes-in-firefox-4/)
+		- Safari iOS 4.0, 4.1, 4.2 have a working HTML5 History API - although the actual back buttons of the browsers do not work, therefore we treat them as HTML4 browsers
+		- None of the HTML5 browsers actually utilise the `title` argument to the `pushState` and `replaceState` calls
+	- HTML4 Browsers
+		- Old browsers like MSIE 6,7 and Firefox 2 do not have a `onhashchange` event
+		- MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
+		- Non-Opera HTML4 browsers sometimes do not apply the hash when the hash is not `urlencoded`
+	- All Browsers
+		- State data and titles do not persist once the site is left and then returned (includes page refreshes)
+		- State titles are never applied to the `document.title`
+- ReplaceState functionality is emulated in HTML4 browsers by discarding the replaced state, so when the discarded state is accessed it is skipped using the appropriate `History.back()` / `History.forward()` call
+- Data persistance and synchronisation works like so: Every second or so, the SUIDs and URLs of the states will synchronise between the store and the local session. When a new session opens a familiar state (via the SUID or the URL) and it is not found locally then it will attempt to load the last known stored state with that information.
+- URLs will be unescaped to the maximum, so for instance the URL `?key=a%20b%252c` will become `?key=a b c`. This is to ensure consistency between browser url encodings.
+- Changing the hash of the page causes `onpopstate` to fire (this is expected/standard functionality). To ensure correct compatibility between HTML5 and HTML4 browsers the following events have been created:
+	- `window.onstatechange`: this is the same as the `onpopstate` event except it does not fire for traditional anchors
+	- `window.onanchorchange`: this is the same as the `onhashchange` event except it does not fire for states
+- Known Issues
+	- Opera 11 fails to create history entries when under stressful loads (events fire perfectly, just the history events fail) - there is nothing we can do about this
+	- Mercury iOS fails to apply url changes (hashes and HTML5 History API states) - there is nothing we can do about this
+
+
+
+## History
+
+You can discover the history inside the [History.md](https://github.com/balupton/history.js/blob/master/History.md#files) file
+
+
+## License
+
+Licensed under the [New BSD License](http://opensource.org/licenses/BSD-3-Clause)
+<br/>Copyright &copy;  2011-2012 [Benjamin Arthur Lupton](http://balupton.com)

+ 281 - 0
js/libraries/balupton-history/buildr.coffee

@@ -0,0 +1,281 @@
+# Requires
+buildr = require 'buildr'
+util = require 'util'
+
+# Options
+options =
+	watch: false
+	compress: true
+
+# Configs
+configs =
+	standard:
+		# Options
+		name: 'standard'
+		watch: options.watch
+
+		# Paths
+		srcPath: __dirname+'/scripts/uncompressed'
+		outPath: __dirname+'/scripts/compressed'
+
+		# Checking
+		checkScripts: true
+		jshintOptions:
+			browser: true
+			laxbreak: true
+			boss: true
+			undef: true
+			onevar: true
+			strict: true
+			noarg: true
+
+		# Compression (without outPath only the generated bundle files are compressed)
+		compressScripts: options.compress # Array or true or false
+	
+	other: [
+
+		# -----------------------------
+		# JQUERY
+
+		{
+			# Options
+			name: 'html4+html5+jquery'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'json2.js'
+				'history.adapter.jquery.js'
+				'history.html4.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html4+html5/jquery.history.js'
+		}
+		{
+			# Options
+			name: 'html5+jquery'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'history.adapter.jquery.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html5/jquery.history.js'
+		}
+
+
+		# -----------------------------
+		# MOOTOOLS
+
+		{
+			# Options
+			name: 'html4+html5+mootools'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'json2.js'
+				'history.adapter.mootools.js'
+				'history.html4.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html4+html5/mootools.history.js'
+		}
+		{
+			# Options
+			name: 'html5+mootools'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'history.adapter.mootools.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html5/mootools.history.js'
+		}
+
+
+		# -----------------------------
+		# NATIVE
+
+		{
+			# Options
+			name: 'html4+html5+native'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'json2.js'
+				'history.adapter.native.js'
+				'history.html4.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html4+html5/native.history.js'
+		}
+		{
+			# Options
+			name: 'html5+native'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'history.adapter.native.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html5/native.history.js'
+		}
+
+
+		# -----------------------------
+		# RIGHT.JS
+
+		{
+			# Options
+			name: 'html4+html5+right'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'json2.js'
+				'history.adapter.right.js'
+				'history.html4.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html4+html5/right.history.js'
+		}
+		{
+			# Options
+			name: 'html5+right'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'history.adapter.right.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html5/right.history.js'
+		}
+
+
+		# -----------------------------
+		# ZEPTO
+
+		{
+			# Options
+			name: 'html4+html5+zepto'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'json2.js'
+				'history.adapter.zepto.js'
+				'history.html4.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html4+html5/zepto.history.js'
+		}
+		{
+			# Options
+			name: 'html5+zepto'
+			watch: options.watch
+
+			# Paths
+			srcPath: __dirname+'/scripts/uncompressed'
+
+			# Compression (without outPath only the generated bundle files are compressed)
+			compressScripts: options.compress # Array or true or false
+
+			# Order
+			scriptsOrder: [
+				'history.adapter.zepto.js'
+				'history.js'
+			]
+
+			# Bundling
+			bundleScriptPath: __dirname+'/scripts/bundled/html5/zepto.history.js'
+		}
+	]
+
+# Standard
+standardConfig = configs.standard
+standardConfig.successHandler = ->
+	for config in configs.other
+		buildrInstance = buildr.createInstance config
+		buildrInstance.process()
+
+# Process
+standardBuildr = buildr.createInstance configs.standard
+standardBuildr.process()

Datei-Diff unterdrückt, da er zu groß ist
+ 30 - 0
js/libraries/balupton-history/demo/bcherry-orig.html


Datei-Diff unterdrückt, da er zu groß ist
+ 30 - 0
js/libraries/balupton-history/demo/bcherry.html


+ 37 - 0
js/libraries/balupton-history/demo/chrome.html

@@ -0,0 +1,37 @@
+<html>
+<head>
+	<title>Chrome History API Data Artifact</title>
+</head>
+<body>
+	<p>This demo demonstrates an issue with Google Chrome versions 8-10 (possibly 11) where if you push a state with data, then do history.back to the initial state, the event.state will contain the pushed states data instead of being null.</p>
+	<p>Note: The issue requires a clean history list, as such this should always be opened in a new tab/window where there are no prior history items.</p>
+	<p>Reported by <a href="http://balupton.com">Benjamin Lupton</a> author of <a href="http://github.com/balupton/history.js">History.js</a></p>
+	<button id="bug">bug</button>
+	<button id="reset">reset</button>
+	<textarea id="log" style="width:100%;height:200px;margin-top:1em;"></textarea>
+	<script type="text/javascript">
+		(function(){
+
+			window.onpopstate = function(event) {
+				var message = ("onpopstate: location: " + document.location.href + ", data: " + JSON.stringify(event.state));
+				document.getElementById('log').innerHTML += message+"\n\n";
+			};
+
+			document.getElementById('bug').onclick = function(){
+				setTimeout(function(){
+					history.pushState({state:'new'},'New State','?new');
+				},1e3);
+
+				setTimeout(function(){
+					history.back();
+				},2e3);
+			};
+
+			document.getElementById('reset').onclick = function(){
+				document.location.href = document.location.href.replace(/[\#\?].*/,"");
+			};
+
+		})();
+	</script>
+</body>
+</html>

Datei-Diff unterdrückt, da er zu groß ist
+ 19 - 0
js/libraries/balupton-history/demo/index.html


+ 43 - 0
js/libraries/balupton-history/demo/native-auto.html

@@ -0,0 +1,43 @@
+<html>
+<head>
+</head>
+<body>
+	<script type="text/javascript">
+		(function(){
+
+			window.onpopstate = function(event) {
+				console.log("onpopstate: location: " + document.location.href + ", data: " + JSON.stringify(event.state));
+			};
+			window.onhashchange = function(event) {
+				console.log("onhashchange: location: " + document.location.href);
+			};
+
+			setTimeout(function(){
+				history.pushState({page: 1}, "title 1", "?page=1");
+			},1e3);
+			setTimeout(function(){
+				history.pushState({page: 2}, "title 2", "?page=2");
+			},2e3);
+			setTimeout(function(){
+				history.replaceState({page: 3}, "title 3", "?page=3");
+			},3e3);
+			setTimeout(function(){
+				document.location.hash = 'asd';
+			},4e3);
+			setTimeout(function(){
+				history.back(); // alerts "location: http://example.com/example.html?page=3#asd, state: {"page":3}"
+			},5e3);
+			setTimeout(function(){
+				history.back(); // alerts "location: http://example.com/example.html?page=1, state: {"page":1}"
+			},6e3);
+			setTimeout(function(){
+				history.back(); // alerts "location: http://example.com/example.html, state: null
+			},7e3);
+			setTimeout(function(){
+				history.go(2);  // alerts "location: http://example.com/example.html?page=3, state: {"page":3}
+			},8e3);
+
+		})();
+	</script>
+</body>
+</html>

+ 62 - 0
js/libraries/balupton-history/demo/native.html

@@ -0,0 +1,62 @@
+<html>
+<head>
+	<title>HTML5 History API Demo</title>
+</head>
+<body>
+	<textarea id="log" style="width:100%;height:400px;margin:1em;"></textarea>
+	<div id="url" style="border:1px black dotted;height:1em;margin:1em;"></div>
+	<div id="buttons" style="margin:1em;"></div>
+	<script type="text/javascript">
+			var $url = document.getElementById('url'), $log = document.getElementById('log');
+
+			window.onpopstate = function(event) {
+				var message =
+					"onpopstate: "+
+					"location: " + location.href + ", " +
+					"data: " + JSON.stringify(event.state) +
+					"\n\n"
+					;
+
+				$url.innerHTML = location.href;
+				$log.innerHTML += message;
+				console.log(message);
+			};
+
+			window.onhashchange = function(event) {
+				var message =
+					"onhashchange: "+
+					"location: " + location.href + ", "+
+					"hash: " + location.hash +
+					"\n\n"
+					;
+
+				$url.innerHTML = location.href;
+				$log.innerHTML += message;
+				console.log(message);
+			};
+
+			// Prepare Buttons
+			var
+				buttons = document.getElementById('buttons'),
+				scripts = [
+					'history.pushState({state:1}, "State 1", "?state=1");',
+					'history.pushState({state:2}, "State 2", "?state=2");',
+					'history.replaceState({state:3}, "State 3", "?state=3");',
+					'location.hash = Math.random();',
+					'history.back();',
+					'history.forward();',
+					'document.location.href = document.location.href.replace(/[\#\?].*/,"");'
+				],
+				buttonsHTML = ''
+				;
+
+			// Add Buttons
+			for ( var i=0,n=scripts.length; i<n; ++i ) {
+				var _script = scripts[i];
+				buttonsHTML +=
+					'<li><button onclick=\'javascript:'+_script+'\'>'+_script+'</button></li>';
+			}
+			buttons.innerHTML = buttonsHTML;
+	</script>
+</body>
+</html>

+ 23 - 0
js/libraries/balupton-history/demo/navigator.html

@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<title>
+		Navigator Output
+	</title>
+	<style>
+		label { font-weight:bold; }
+		label:after { content: ":"; margin-right:5px; }
+	</style>
+</head>
+<body>
+	<div id="nav"></div>
+	<script>
+		var nav = document.getElementById('nav'), i,v;
+		for ( i in navigator ) {
+			var v = navigator[i];
+			nav.innerHTML += '<div><label>'+i+'</label>'+v+'</div>';
+		}
+	</script>
+</body>
+</html>

+ 61 - 0
js/libraries/balupton-history/demo/safari.html

@@ -0,0 +1,61 @@
+<html>
+<head>
+	<title>Safari Hash ReplaceState History Traversal Bug</title>
+</head>
+<body>
+	<p>This demo demonstrates an issue with Safari 5.0.4 (6533.20.27) handing of hashes and replace state. When a hash is set, and then replaced using replaceState the history list are then broken, when traversing back the hash does not change.</p>
+	<p>Note: The issue requires a clean history list, as such this should always be opened in a new tab/window where there are no prior history items.</p>
+	<p>Reported by <a href="http://balupton.com">Benjamin Lupton</a> author of <a href="http://github.com/balupton/history.js">History.js</a></p>
+	<button id="bug">bug</button>
+	<button id="workaround">workaround</button>
+	<button id="reset">reset</button>
+	<textarea id="log" style="width:100%;height:200px;margin-top:1em;"></textarea>
+	<script type="text/javascript">
+		(function(){
+
+			window.onpopstate = function(event) {
+				var message = ("onpopstate: location: " + document.location.href);
+				document.getElementById('log').innerHTML += message+"\n\n";
+			};
+
+			window.onhashchange = function(event) {
+				var message = ("onhashchange: location: " + document.location.href);
+				document.getElementById('log').innerHTML += message+"\n\n";
+			};
+
+			document.getElementById('bug').onclick = function(){
+				setTimeout(function(){
+					document.location.hash = Math.random();
+				},1e3);
+
+				setTimeout(function(){
+					history.replaceState(null,'','?blah');
+				},2e3);
+
+				setTimeout(function(){
+					history.back(); // should take us to the initial page, it doesn't
+				},3e3);
+			};
+
+			document.getElementById('workaround').onclick = function(){
+				setTimeout(function(){
+					history.pushState(null,'','#'+Math.random());
+				},1e3);
+
+				setTimeout(function(){
+					history.replaceState(null,'','?blah');
+				},2e3);
+
+				setTimeout(function(){
+					history.back(); // will take us to the initial page
+				},3e3);
+			};
+
+			document.getElementById('reset').onclick = function(){
+				document.location.href = document.location.href.replace(/[\#\?].*/,"");
+			};
+
+		})();
+	</script>
+</body>
+</html>

Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
js/libraries/balupton-history/license.txt


+ 57 - 0
js/libraries/balupton-history/package.json

@@ -0,0 +1,57 @@
+{
+	"name": "history.js",
+	"version": "1.7.1-r2",
+	"description": "History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState. Supports jQuery, MooTools and Prototype.  For HTML5 browsers this means that you can modify the URL directly, without needing to use hashes anymore. For HTML4 browsers it will revert back to using the old onhashchange functionality.",
+	"homepage": "https://github.com/balupton/history.js",
+	"keywords": [
+		"javascript",
+		"html5 history api",
+		"hashchange",
+		"popstate",
+		"pushstate",
+		"replacestate",
+		"hashes",
+		"hashbang"
+	],
+	"author": {
+		"name": "Benjamin Lupton",
+		"email": "b@lupton.cc",
+		"web": "http://balupton.com"
+	},
+	"maintainers": [
+		{
+			"name": "Benjamin Lupton",
+			"email": "b@lupton.cc",
+			"web": "http://balupton.com"
+		}
+	],
+	"contributors": [
+		{
+			"name": "Benjamin Lupton",
+			"email": "b@lupton.cc",
+			"web": "http://balupton.com"
+		}
+	],
+	"bugs": {
+		"web": "https://github.com/balupton/history.js/issues"
+	},
+	"licenses": [
+		{
+			"type": "New-BSD",
+			"url": "http://creativecommons.org/licenses/BSD/"
+		}
+	],
+	"repository": {
+		"type": "git",
+		"url": "http://github.com/balupton/history.js.git"
+	},
+	"dependencies": {
+		"buildr": "0.8.x"
+	},
+	"engines": {
+	},
+	"directories": {
+		"out": "./scripts/compressed",
+		"src": "./scripts/uncompressed"
+	}
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html4+html5/jquery.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html4+html5/mootools.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html4+html5/native.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html4+html5/right.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html4+html5/zepto.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html5/jquery.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html5/mootools.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html5/native.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html5/right.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/bundled/html5/zepto.history.js


+ 1 - 0
js/libraries/balupton-history/scripts/compressed/history.adapter.jquery.js

@@ -0,0 +1 @@
+(function(a,b){"use strict";var c=a.History=a.History||{},d=a.jQuery;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){d(a).bind(b,c)},trigger:function(a,b,c){d(a).trigger(b,c)},extractEventData:function(a,c,d){var e=c&&c.originalEvent&&c.originalEvent[a]||d&&d[a]||b;return e},onDomLoad:function(a){d(a)}},typeof c.init!="undefined"&&c.init()})(window)

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/compressed/history.adapter.mootools.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/compressed/history.adapter.native.js


+ 1 - 0
js/libraries/balupton-history/scripts/compressed/history.adapter.right.js

@@ -0,0 +1 @@
+(function(a,b){"use strict";var c=a.History=a.History||{},d=a.document,e=a.RightJS,f=e.$;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){f(a).on(b,c)},trigger:function(a,b,c){f(a).fire(b,c)},extractEventData:function(a,c){var d=c&&c._&&c._[a]||b;return d},onDomLoad:function(a){f(d).onReady(a)}},typeof c.init!="undefined"&&c.init()})(window)

+ 1 - 0
js/libraries/balupton-history/scripts/compressed/history.adapter.zepto.js

@@ -0,0 +1 @@
+(function(a,b){"use strict";var c=a.History=a.History||{},d=a.Zepto;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){(new d(a)).bind(b,c)},trigger:function(a,b){(new d(a)).trigger(b)},extractEventData:function(a,c){var d=c&&c[a]||b;return d},onDomLoad:function(a){new d(a)}},typeof c.init!="undefined"&&c.init()})(window)

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/compressed/history.html4.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/compressed/history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/balupton-history/scripts/compressed/json2.js


+ 77 - 0
js/libraries/balupton-history/scripts/uncompressed/history.adapter.jquery.js

@@ -0,0 +1,77 @@
+/**
+ * History.js jQuery Adapter
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
+ */
+
+// Closure
+(function(window,undefined){
+	"use strict";
+
+	// Localise Globals
+	var
+		History = window.History = window.History||{},
+		jQuery = window.jQuery;
+
+	// Check Existence
+	if ( typeof History.Adapter !== 'undefined' ) {
+		throw new Error('History.js Adapter has already been loaded...');
+	}
+
+	// Add the Adapter
+	History.Adapter = {
+		/**
+		 * History.Adapter.bind(el,event,callback)
+		 * @param {Element|string} el
+		 * @param {string} event - custom and standard events
+		 * @param {function} callback
+		 * @return {void}
+		 */
+		bind: function(el,event,callback){
+			jQuery(el).bind(event,callback);
+		},
+
+		/**
+		 * History.Adapter.trigger(el,event)
+		 * @param {Element|string} el
+		 * @param {string} event - custom and standard events
+		 * @param {Object=} extra - a object of extra event data (optional)
+		 * @return {void}
+		 */
+		trigger: function(el,event,extra){
+			jQuery(el).trigger(event,extra);
+		},
+
+		/**
+		 * History.Adapter.extractEventData(key,event,extra)
+		 * @param {string} key - key for the event data to extract
+		 * @param {string} event - custom and standard events
+		 * @param {Object=} extra - a object of extra event data (optional)
+		 * @return {mixed}
+		 */
+		extractEventData: function(key,event,extra){
+			// jQuery Native then jQuery Custom
+			var result = (event && event.originalEvent && event.originalEvent[key]) || (extra && extra[key]) || undefined;
+
+			// Return
+			return result;
+		},
+
+		/**
+		 * History.Adapter.onDomLoad(callback)
+		 * @param {function} callback
+		 * @return {void}
+		 */
+		onDomLoad: function(callback) {
+			jQuery(callback);
+		}
+	};
+
+	// Try and Initialise History
+	if ( typeof History.init !== 'undefined' ) {
+		History.init();
+	}
+
+})(window);
+

+ 84 - 0
js/libraries/balupton-history/scripts/uncompressed/history.adapter.mootools.js

@@ -0,0 +1,84 @@
+/**
+ * History.js MooTools Adapter
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
+ */
+
+// Closure
+(function(window,undefined){
+	"use strict";
+
+	// Localise Globals
+	var
+		History = window.History = window.History||{},
+		MooTools = window.MooTools,
+		Element = window.Element;
+
+	// Check Existence
+	if ( typeof History.Adapter !== 'undefined' ) {
+		throw new Error('History.js Adapter has already been loaded...');
+	}
+
+	// Make MooTools aware of History.js Events
+	Object.append(Element.NativeEvents,{
+		'popstate':2,
+		'hashchange':2
+	});
+
+	// Add the Adapter
+	History.Adapter = {
+		/**
+		 * History.Adapter.bind(el,event,callback)
+		 * @param {Element|string} el
+		 * @param {string} event - custom and standard events
+		 * @param {function} callback
+		 * @return {void}
+		 */
+		bind: function(el,event,callback){
+			var El = typeof el === 'string' ? document.id(el) : el;
+			El.addEvent(event,callback);
+		},
+
+		/**
+		 * History.Adapter.trigger(el,event)
+		 * @param {Element|string} el
+		 * @param {string} event - custom and standard events
+		 * @param {Object=} extra - a object of extra event data (optional)
+		 * @return void
+		 */
+		trigger: function(el,event,extra){
+			var El = typeof el === 'string' ? document.id(el) : el;
+			El.fireEvent(event,extra);
+		},
+
+		/**
+		 * History.Adapter.extractEventData(key,event,extra)
+		 * @param {string} key - key for the event data to extract
+		 * @param {string} event - custom and standard events
+		 * @return {mixed}
+		 */
+		extractEventData: function(key,event){
+			// MooTools Native then MooTools Custom
+			var result = (event && event.event && event.event[key]) || (event && event[key]) || undefined;
+
+			// Return
+			return result;
+		},
+
+		/**
+		 * History.Adapter.onDomLoad(callback)
+		 * @param {function} callback
+		 * @return {void}
+		 */
+		onDomLoad: function(callback) {
+			window.addEvent('domready',callback);
+		}
+	};
+
+	// Try and Initialise History
+	if ( typeof History.init !== 'undefined' ) {
+		History.init();
+	}
+
+})(window);

+ 121 - 0
js/libraries/balupton-history/scripts/uncompressed/history.adapter.native.js

@@ -0,0 +1,121 @@
+/**
+ * History.js Native Adapter
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
+ */
+
+// Closure
+(function(window,undefined){
+	"use strict";
+
+	// Localise Globals
+	var History = window.History = window.History||{};
+
+	// Check Existence
+	if ( typeof History.Adapter !== 'undefined' ) {
+		throw new Error('History.js Adapter has already been loaded...');
+	}
+
+	// Add the Adapter
+	History.Adapter = {
+		/**
+		 * History.Adapter.handlers[uid][eventName] = Array
+		 */
+		handlers: {},
+
+		/**
+		 * History.Adapter._uid
+		 * The current element unique identifier
+		 */
+		_uid: 1,
+
+		/**
+		 * History.Adapter.uid(element)
+		 * @param {Element} element
+		 * @return {String} uid
+		 */
+		 uid: function(element){
+			return element._uid || (element._uid = History.Adapter._uid++);
+		 },
+
+		/**
+		 * History.Adapter.bind(el,event,callback)
+		 * @param {Element} element
+		 * @param {String} eventName - custom and standard events
+		 * @param {Function} callback
+		 * @return
+		 */
+		bind: function(element,eventName,callback){
+			// Prepare
+			var uid = History.Adapter.uid(element);
+
+			// Apply Listener
+			History.Adapter.handlers[uid] = History.Adapter.handlers[uid] || {};
+			History.Adapter.handlers[uid][eventName] = History.Adapter.handlers[uid][eventName] || [];
+			History.Adapter.handlers[uid][eventName].push(callback);
+
+			// Bind Global Listener
+			element['on'+eventName] = (function(element,eventName){
+				return function(event){
+					History.Adapter.trigger(element,eventName,event);
+				};
+			})(element,eventName);
+		},
+
+		/**
+		 * History.Adapter.trigger(el,event)
+		 * @param {Element} element
+		 * @param {String} eventName - custom and standard events
+		 * @param {Object} event - a object of event data
+		 * @return
+		 */
+		trigger: function(element,eventName,event){
+			// Prepare
+			event = event || {};
+			var uid = History.Adapter.uid(element),
+				i,n;
+
+			// Apply Listener
+			History.Adapter.handlers[uid] = History.Adapter.handlers[uid] || {};
+			History.Adapter.handlers[uid][eventName] = History.Adapter.handlers[uid][eventName] || [];
+
+			// Fire Listeners
+			for ( i=0,n=History.Adapter.handlers[uid][eventName].length; i<n; ++i ) {
+				History.Adapter.handlers[uid][eventName][i].apply(this,[event]);
+			}
+		},
+
+		/**
+		 * History.Adapter.extractEventData(key,event,extra)
+		 * @param {String} key - key for the event data to extract
+		 * @param {String} event - custom and standard events
+		 * @return {mixed}
+		 */
+		extractEventData: function(key,event){
+			var result = (event && event[key]) || undefined;
+			return result;
+		},
+
+		/**
+		 * History.Adapter.onDomLoad(callback)
+		 * @param {Function} callback
+		 * @return
+		 */
+		onDomLoad: function(callback) {
+			var timeout = window.setTimeout(function(){
+				callback();
+			},2000);
+			window.onload = function(){
+				clearTimeout(timeout);
+				callback();
+			};
+		}
+	};
+
+	// Try and Initialise History
+	if ( typeof History.init !== 'undefined' ) {
+		History.init();
+	}
+
+})(window);

+ 78 - 0
js/libraries/balupton-history/scripts/uncompressed/history.adapter.right.js

@@ -0,0 +1,78 @@
+/**
+ * History.js RightJS Adapter
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
+ */
+
+// Closure
+(function(window,undefined){
+	"use strict";
+
+	// Localise Globals
+	var
+		History = window.History = window.History||{},
+		document = window.document,
+		RightJS = window.RightJS,
+		$ = RightJS.$;
+
+	// Check Existence
+	if ( typeof History.Adapter !== 'undefined' ) {
+		throw new Error('History.js Adapter has already been loaded...');
+	}
+
+	// Add the Adapter
+	History.Adapter = {
+		/**
+		 * History.Adapter.bind(el,event,callback)
+		 * @param {Element|Selector} el
+		 * @param {String} event - custom and standard events
+		 * @param {Function} callback
+		 * @return
+		 */
+		bind: function(el,event,callback){
+			$(el).on(event,callback);
+		},
+
+		/**
+		 * History.Adapter.trigger(el,event)
+		 * @param {Element|Selector} el
+		 * @param {String} event - custom and standard events
+		 * @param {Object} extraEventData - a object of extra event data
+		 * @return
+		 */
+		trigger: function(el,event,extraEventData){
+			$(el).fire(event,extraEventData);
+		},
+
+		/**
+		 * History.Adapter.extractEventData(key,event,extra)
+		 * @param {String} key - key for the event data to extract
+		 * @param {String} event - custom and standard events
+		 * @return {mixed}
+		 */
+		extractEventData: function(key,event){
+			// Right.js Native
+			// Right.js Custom
+			var result = (event && event._ && event._[key]) || undefined;
+
+			// Return
+			return result;
+		},
+
+		/**
+		 * History.Adapter.onDomLoad(callback)
+		 * @param {Function} callback
+		 * @return
+		 */
+		onDomLoad: function(callback) {
+			$(document).onReady(callback);
+		}
+	};
+
+	// Try and Initialise History
+	if ( typeof History.init !== 'undefined' ) {
+		History.init();
+	}
+
+})(window);

+ 74 - 0
js/libraries/balupton-history/scripts/uncompressed/history.adapter.zepto.js

@@ -0,0 +1,74 @@
+/**
+ * History.js Zepto Adapter
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
+ */
+
+// Closure
+(function(window,undefined){
+	"use strict";
+
+	// Localise Globals
+	var
+		History = window.History = window.History||{},
+		Zepto = window.Zepto;
+
+	// Check Existence
+	if ( typeof History.Adapter !== 'undefined' ) {
+		throw new Error('History.js Adapter has already been loaded...');
+	}
+
+	// Add the Adapter
+	History.Adapter = {
+		/**
+		 * History.Adapter.bind(el,event,callback)
+		 * @param {Element|string} el
+		 * @param {string} event - custom and standard events
+		 * @param {function} callback
+		 * @return {void}
+		 */
+		bind: function(el,event,callback){
+			new Zepto(el).bind(event,callback);
+		},
+
+		/**
+		 * History.Adapter.trigger(el,event)
+		 * @param {Element|string} el
+		 * @param {string} event - custom and standard events
+		 * @return {void}
+		 */
+		trigger: function(el,event){
+			new Zepto(el).trigger(event);
+		},
+
+		/**
+		 * History.Adapter.extractEventData(key,event,extra)
+		 * @param {string} key - key for the event data to extract
+		 * @param {string} event - custom and standard events
+		 * @return {mixed}
+		 */
+		extractEventData: function(key,event){
+			// Zepto Native
+			var result = (event && event[key]) || undefined;
+
+			// Return
+			return result;
+		},
+
+		/**
+		 * History.Adapter.onDomLoad(callback)
+		 * @param {function} callback
+		 * @return {void}
+		 */
+		onDomLoad: function(callback) {
+			new Zepto(callback);
+		}
+	};
+
+	// Try and Initialise History
+	if ( typeof History.init !== 'undefined' ) {
+		History.init();
+	}
+
+})(window);

+ 621 - 0
js/libraries/balupton-history/scripts/uncompressed/history.html4.js

@@ -0,0 +1,621 @@
+/**
+ * History.js HTML4 Support
+ * Depends on the HTML5 Support
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
+ */
+
+(function(window,undefined){
+	"use strict";
+
+	// ========================================================================
+	// Initialise
+
+	// Localise Globals
+	var
+		document = window.document, // Make sure we are using the correct document
+		setTimeout = window.setTimeout||setTimeout,
+		clearTimeout = window.clearTimeout||clearTimeout,
+		setInterval = window.setInterval||setInterval,
+		History = window.History = window.History||{}; // Public History Object
+
+	// Check Existence
+	if ( typeof History.initHtml4 !== 'undefined' ) {
+		throw new Error('History.js HTML4 Support has already been loaded...');
+	}
+
+
+	// ========================================================================
+	// Initialise HTML4 Support
+
+	// Initialise HTML4 Support
+	History.initHtml4 = function(){
+		// Initialise
+		if ( typeof History.initHtml4.initialized !== 'undefined' ) {
+			// Already Loaded
+			return false;
+		}
+		else {
+			History.initHtml4.initialized = true;
+		}
+
+
+		// ====================================================================
+		// Properties
+
+		/**
+		 * History.enabled
+		 * Is History enabled?
+		 */
+		History.enabled = true;
+
+
+		// ====================================================================
+		// Hash Storage
+
+		/**
+		 * History.savedHashes
+		 * Store the hashes in an array
+		 */
+		History.savedHashes = [];
+
+		/**
+		 * History.isLastHash(newHash)
+		 * Checks if the hash is the last hash
+		 * @param {string} newHash
+		 * @return {boolean} true
+		 */
+		History.isLastHash = function(newHash){
+			// Prepare
+			var oldHash = History.getHashByIndex(),
+				isLast;
+
+			// Check
+			isLast = newHash === oldHash;
+
+			// Return isLast
+			return isLast;
+		};
+
+		/**
+		 * History.saveHash(newHash)
+		 * Push a Hash
+		 * @param {string} newHash
+		 * @return {boolean} true
+		 */
+		History.saveHash = function(newHash){
+			// Check Hash
+			if ( History.isLastHash(newHash) ) {
+				return false;
+			}
+
+			// Push the Hash
+			History.savedHashes.push(newHash);
+
+			// Return true
+			return true;
+		};
+
+		/**
+		 * History.getHashByIndex()
+		 * Gets a hash by the index
+		 * @param {integer} index
+		 * @return {string}
+		 */
+		History.getHashByIndex = function(index){
+			// Prepare
+			var hash = null;
+
+			// Handle
+			if ( typeof index === 'undefined' ) {
+				// Get the last inserted
+				hash = History.savedHashes[History.savedHashes.length-1];
+			}
+			else if ( index < 0 ) {
+				// Get from the end
+				hash = History.savedHashes[History.savedHashes.length+index];
+			}
+			else {
+				// Get from the beginning
+				hash = History.savedHashes[index];
+			}
+
+			// Return hash
+			return hash;
+		};
+
+
+		// ====================================================================
+		// Discarded States
+
+		/**
+		 * History.discardedHashes
+		 * A hashed array of discarded hashes
+		 */
+		History.discardedHashes = {};
+
+		/**
+		 * History.discardedStates
+		 * A hashed array of discarded states
+		 */
+		History.discardedStates = {};
+
+		/**
+		 * History.discardState(State)
+		 * Discards the state by ignoring it through History
+		 * @param {object} State
+		 * @return {true}
+		 */
+		History.discardState = function(discardedState,forwardState,backState){
+			//History.debug('History.discardState', arguments);
+			// Prepare
+			var discardedStateHash = History.getHashByState(discardedState),
+				discardObject;
+
+			// Create Discard Object
+			discardObject = {
+				'discardedState': discardedState,
+				'backState': backState,
+				'forwardState': forwardState
+			};
+
+			// Add to DiscardedStates
+			History.discardedStates[discardedStateHash] = discardObject;
+
+			// Return true
+			return true;
+		};
+
+		/**
+		 * History.discardHash(hash)
+		 * Discards the hash by ignoring it through History
+		 * @param {string} hash
+		 * @return {true}
+		 */
+		History.discardHash = function(discardedHash,forwardState,backState){
+			//History.debug('History.discardState', arguments);
+			// Create Discard Object
+			var discardObject = {
+				'discardedHash': discardedHash,
+				'backState': backState,
+				'forwardState': forwardState
+			};
+
+			// Add to discardedHash
+			History.discardedHashes[discardedHash] = discardObject;
+
+			// Return true
+			return true;
+		};
+
+		/**
+		 * History.discardState(State)
+		 * Checks to see if the state is discarded
+		 * @param {object} State
+		 * @return {bool}
+		 */
+		History.discardedState = function(State){
+			// Prepare
+			var StateHash = History.getHashByState(State),
+				discarded;
+
+			// Check
+			discarded = History.discardedStates[StateHash]||false;
+
+			// Return true
+			return discarded;
+		};
+
+		/**
+		 * History.discardedHash(hash)
+		 * Checks to see if the state is discarded
+		 * @param {string} State
+		 * @return {bool}
+		 */
+		History.discardedHash = function(hash){
+			// Check
+			var discarded = History.discardedHashes[hash]||false;
+
+			// Return true
+			return discarded;
+		};
+
+		/**
+		 * History.recycleState(State)
+		 * Allows a discarded state to be used again
+		 * @param {object} data
+		 * @param {string} title
+		 * @param {string} url
+		 * @return {true}
+		 */
+		History.recycleState = function(State){
+			//History.debug('History.recycleState', arguments);
+			// Prepare
+			var StateHash = History.getHashByState(State);
+
+			// Remove from DiscardedStates
+			if ( History.discardedState(State) ) {
+				delete History.discardedStates[StateHash];
+			}
+
+			// Return true
+			return true;
+		};
+
+
+		// ====================================================================
+		// HTML4 HashChange Support
+
+		if ( History.emulated.hashChange ) {
+			/*
+			 * We must emulate the HTML4 HashChange Support by manually checking for hash changes
+			 */
+
+			/**
+			 * History.hashChangeInit()
+			 * Init the HashChange Emulation
+			 */
+			History.hashChangeInit = function(){
+				// Define our Checker Function
+				History.checkerFunction = null;
+
+				// Define some variables that will help in our checker function
+				var lastDocumentHash = '',
+					iframeId, iframe,
+					lastIframeHash, checkerRunning;
+
+				// Handle depending on the browser
+				if ( History.isInternetExplorer() ) {
+					// IE6 and IE7
+					// We need to use an iframe to emulate the back and forward buttons
+
+					// Create iFrame
+					iframeId = 'historyjs-iframe';
+					iframe = document.createElement('iframe');
+
+					// Adjust iFarme
+					iframe.setAttribute('id', iframeId);
+					iframe.style.display = 'none';
+
+					// Append iFrame
+					document.body.appendChild(iframe);
+
+					// Create initial history entry
+					iframe.contentWindow.document.open();
+					iframe.contentWindow.document.close();
+
+					// Define some variables that will help in our checker function
+					lastIframeHash = '';
+					checkerRunning = false;
+
+					// Define the checker function
+					History.checkerFunction = function(){
+						// Check Running
+						if ( checkerRunning ) {
+							return false;
+						}
+
+						// Update Running
+						checkerRunning = true;
+
+						// Fetch
+						var documentHash = History.getHash()||'',
+							iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash)||'';
+
+						// The Document Hash has changed (application caused)
+						if ( documentHash !== lastDocumentHash ) {
+							// Equalise
+							lastDocumentHash = documentHash;
+
+							// Create a history entry in the iframe
+							if ( iframeHash !== documentHash ) {
+								//History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
+
+								// Equalise
+								lastIframeHash = iframeHash = documentHash;
+
+								// Create History Entry
+								iframe.contentWindow.document.open();
+								iframe.contentWindow.document.close();
+
+								// Update the iframe's hash
+								iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
+							}
+
+							// Trigger Hashchange Event
+							History.Adapter.trigger(window,'hashchange');
+						}
+
+						// The iFrame Hash has changed (back button caused)
+						else if ( iframeHash !== lastIframeHash ) {
+							//History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
+
+							// Equalise
+							lastIframeHash = iframeHash;
+
+							// Update the Hash
+							History.setHash(iframeHash,false);
+						}
+
+						// Reset Running
+						checkerRunning = false;
+
+						// Return true
+						return true;
+					};
+				}
+				else {
+					// We are not IE
+					// Firefox 1 or 2, Opera
+
+					// Define the checker function
+					History.checkerFunction = function(){
+						// Prepare
+						var documentHash = History.getHash();
+
+						// The Document Hash has changed (application caused)
+						if ( documentHash !== lastDocumentHash ) {
+							// Equalise
+							lastDocumentHash = documentHash;
+
+							// Trigger Hashchange Event
+							History.Adapter.trigger(window,'hashchange');
+						}
+
+						// Return true
+						return true;
+					};
+				}
+
+				// Apply the checker function
+				History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
+
+				// Done
+				return true;
+			}; // History.hashChangeInit
+
+			// Bind hashChangeInit
+			History.Adapter.onDomLoad(History.hashChangeInit);
+
+		} // History.emulated.hashChange
+
+
+		// ====================================================================
+		// HTML5 State Support
+
+		// Non-Native pushState Implementation
+		if ( History.emulated.pushState ) {
+			/*
+			 * We must emulate the HTML5 State Management by using HTML4 HashChange
+			 */
+
+			/**
+			 * History.onHashChange(event)
+			 * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
+			 */
+			History.onHashChange = function(event){
+				//History.debug('History.onHashChange', arguments);
+
+				// Prepare
+				var currentUrl = ((event && event.newURL) || document.location.href),
+					currentHash = History.getHashByUrl(currentUrl),
+					currentState = null,
+					currentStateHash = null,
+					currentStateHashExits = null,
+					discardObject;
+
+				// Check if we are the same state
+				if ( History.isLastHash(currentHash) ) {
+					// There has been no change (just the page's hash has finally propagated)
+					//History.debug('History.onHashChange: no change');
+					History.busy(false);
+					return false;
+				}
+
+				// Reset the double check
+				History.doubleCheckComplete();
+
+				// Store our location for use in detecting back/forward direction
+				History.saveHash(currentHash);
+
+				// Expand Hash
+				if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
+					//History.debug('History.onHashChange: traditional anchor', currentHash);
+					// Traditional Anchor Hash
+					History.Adapter.trigger(window,'anchorchange');
+					History.busy(false);
+					return false;
+				}
+
+				// Create State
+				currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,false),true);
+
+				// Check if we are the same state
+				if ( History.isLastSavedState(currentState) ) {
+					//History.debug('History.onHashChange: no change');
+					// There has been no change (just the page's hash has finally propagated)
+					History.busy(false);
+					return false;
+				}
+
+				// Create the state Hash
+				currentStateHash = History.getHashByState(currentState);
+
+				// Check if we are DiscardedState
+				discardObject = History.discardedState(currentState);
+				if ( discardObject ) {
+					// Ignore this state as it has been discarded and go back to the state before it
+					if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
+						// We are going backwards
+						//History.debug('History.onHashChange: go backwards');
+						History.back(false);
+					} else {
+						// We are going forwards
+						//History.debug('History.onHashChange: go forwards');
+						History.forward(false);
+					}
+					return false;
+				}
+
+				// Push the new HTML5 State
+				//History.debug('History.onHashChange: success hashchange');
+				History.pushState(currentState.data,currentState.title,currentState.url,false);
+
+				// End onHashChange closure
+				return true;
+			};
+			History.Adapter.bind(window,'hashchange',History.onHashChange);
+
+			/**
+			 * History.pushState(data,title,url)
+			 * Add a new State to the history object, become it, and trigger onpopstate
+			 * We have to trigger for HTML4 compatibility
+			 * @param {object} data
+			 * @param {string} title
+			 * @param {string} url
+			 * @return {true}
+			 */
+			History.pushState = function(data,title,url,queue){
+				//History.debug('History.pushState: called', arguments);
+
+				// Check the State
+				if ( History.getHashByUrl(url) ) {
+					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+				}
+
+				// Handle Queueing
+				if ( queue !== false && History.busy() ) {
+					// Wait + Push to Queue
+					//History.debug('History.pushState: we must wait', arguments);
+					History.pushQueue({
+						scope: History,
+						callback: History.pushState,
+						args: arguments,
+						queue: queue
+					});
+					return false;
+				}
+
+				// Make Busy
+				History.busy(true);
+
+				// Fetch the State Object
+				var newState = History.createStateObject(data,title,url),
+					newStateHash = History.getHashByState(newState),
+					oldState = History.getState(false),
+					oldStateHash = History.getHashByState(oldState),
+					html4Hash = History.getHash();
+
+				// Store the newState
+				History.storeState(newState);
+				History.expectedStateId = newState.id;
+
+				// Recycle the State
+				History.recycleState(newState);
+
+				// Force update of the title
+				History.setTitle(newState);
+
+				// Check if we are the same State
+				if ( newStateHash === oldStateHash ) {
+					//History.debug('History.pushState: no change', newStateHash);
+					History.busy(false);
+					return false;
+				}
+
+				// Update HTML4 Hash
+				if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) {
+					//History.debug('History.pushState: update hash', newStateHash, html4Hash);
+					History.setHash(newStateHash,false);
+					return false;
+				}
+
+				// Update HTML5 State
+				History.saveState(newState);
+
+				// Fire HTML5 Event
+				//History.debug('History.pushState: trigger popstate');
+				History.Adapter.trigger(window,'statechange');
+				History.busy(false);
+
+				// End pushState closure
+				return true;
+			};
+
+			/**
+			 * History.replaceState(data,title,url)
+			 * Replace the State and trigger onpopstate
+			 * We have to trigger for HTML4 compatibility
+			 * @param {object} data
+			 * @param {string} title
+			 * @param {string} url
+			 * @return {true}
+			 */
+			History.replaceState = function(data,title,url,queue){
+				//History.debug('History.replaceState: called', arguments);
+
+				// Check the State
+				if ( History.getHashByUrl(url) ) {
+					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+				}
+
+				// Handle Queueing
+				if ( queue !== false && History.busy() ) {
+					// Wait + Push to Queue
+					//History.debug('History.replaceState: we must wait', arguments);
+					History.pushQueue({
+						scope: History,
+						callback: History.replaceState,
+						args: arguments,
+						queue: queue
+					});
+					return false;
+				}
+
+				// Make Busy
+				History.busy(true);
+
+				// Fetch the State Objects
+				var newState        = History.createStateObject(data,title,url),
+					oldState        = History.getState(false),
+					previousState   = History.getStateByIndex(-2);
+
+				// Discard Old State
+				History.discardState(oldState,newState,previousState);
+
+				// Alias to PushState
+				History.pushState(newState.data,newState.title,newState.url,false);
+
+				// End replaceState closure
+				return true;
+			};
+
+		} // History.emulated.pushState
+
+
+
+		// ====================================================================
+		// Initialise
+
+		// Non-Native pushState Implementation
+		if ( History.emulated.pushState ) {
+			/**
+			 * Ensure initial state is handled correctly
+			 */
+			if ( History.getHash() && !History.emulated.hashChange ) {
+				History.Adapter.onDomLoad(function(){
+					History.Adapter.trigger(window,'hashchange');
+				});
+			}
+
+		} // History.emulated.pushState
+
+	}; // History.initHtml4
+
+	// Try and Initialise History
+	if ( typeof History.init !== 'undefined' ) {
+		History.init();
+	}
+
+})(window);

+ 1943 - 0
js/libraries/balupton-history/scripts/uncompressed/history.js

@@ -0,0 +1,1943 @@
+/**
+ * History.js Core
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
+ */
+
+(function(window,undefined){
+	"use strict";
+
+	// ========================================================================
+	// Initialise
+
+	// Localise Globals
+	var
+		console = window.console||undefined, // Prevent a JSLint complain
+		document = window.document, // Make sure we are using the correct document
+		navigator = window.navigator, // Make sure we are using the correct navigator
+		sessionStorage = window.sessionStorage||false, // sessionStorage
+		setTimeout = window.setTimeout,
+		clearTimeout = window.clearTimeout,
+		setInterval = window.setInterval,
+		clearInterval = window.clearInterval,
+		JSON = window.JSON,
+		alert = window.alert,
+		History = window.History = window.History||{}, // Public History Object
+		history = window.history; // Old History Object
+
+	// MooTools Compatibility
+	JSON.stringify = JSON.stringify||JSON.encode;
+	JSON.parse = JSON.parse||JSON.decode;
+
+	// Check Existence
+	if ( typeof History.init !== 'undefined' ) {
+		throw new Error('History.js Core has already been loaded...');
+	}
+
+	// Initialise History
+	History.init = function(){
+		// Check Load Status of Adapter
+		if ( typeof History.Adapter === 'undefined' ) {
+			return false;
+		}
+
+		// Check Load Status of Core
+		if ( typeof History.initCore !== 'undefined' ) {
+			History.initCore();
+		}
+
+		// Check Load Status of HTML4 Support
+		if ( typeof History.initHtml4 !== 'undefined' ) {
+			History.initHtml4();
+		}
+
+		// Return true
+		return true;
+	};
+
+
+	// ========================================================================
+	// Initialise Core
+
+	// Initialise Core
+	History.initCore = function(){
+		// Initialise
+		if ( typeof History.initCore.initialized !== 'undefined' ) {
+			// Already Loaded
+			return false;
+		}
+		else {
+			History.initCore.initialized = true;
+		}
+
+
+		// ====================================================================
+		// Options
+
+		/**
+		 * History.options
+		 * Configurable options
+		 */
+		History.options = History.options||{};
+
+		/**
+		 * History.options.hashChangeInterval
+		 * How long should the interval be before hashchange checks
+		 */
+		History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
+
+		/**
+		 * History.options.safariPollInterval
+		 * How long should the interval be before safari poll checks
+		 */
+		History.options.safariPollInterval = History.options.safariPollInterval || 500;
+
+		/**
+		 * History.options.doubleCheckInterval
+		 * How long should the interval be before we perform a double check
+		 */
+		History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
+
+		/**
+		 * History.options.storeInterval
+		 * How long should we wait between store calls
+		 */
+		History.options.storeInterval = History.options.storeInterval || 1000;
+
+		/**
+		 * History.options.busyDelay
+		 * How long should we wait between busy events
+		 */
+		History.options.busyDelay = History.options.busyDelay || 250;
+
+		/**
+		 * History.options.debug
+		 * If true will enable debug messages to be logged
+		 */
+		History.options.debug = History.options.debug || false;
+
+		/**
+		 * History.options.initialTitle
+		 * What is the title of the initial state
+		 */
+		History.options.initialTitle = History.options.initialTitle || document.title;
+
+
+		// ====================================================================
+		// Interval record
+
+		/**
+		 * History.intervalList
+		 * List of intervals set, to be cleared when document is unloaded.
+		 */
+		History.intervalList = [];
+
+		/**
+		 * History.clearAllIntervals
+		 * Clears all setInterval instances.
+		 */
+		History.clearAllIntervals = function(){
+			var i, il = History.intervalList;
+			if (typeof il !== "undefined" && il !== null) {
+				for (i = 0; i < il.length; i++) {
+					clearInterval(il[i]);
+				}
+				History.intervalList = null;
+			}
+		};
+
+
+		// ====================================================================
+		// Debug
+
+		/**
+		 * History.debug(message,...)
+		 * Logs the passed arguments if debug enabled
+		 */
+		History.debug = function(){
+			if ( (History.options.debug||false) ) {
+				History.log.apply(History,arguments);
+			}
+		};
+
+		/**
+		 * History.log(message,...)
+		 * Logs the passed arguments
+		 */
+		History.log = function(){
+			// Prepare
+			var
+				consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
+				textarea = document.getElementById('log'),
+				message,
+				i,n,
+				args,arg
+				;
+
+			// Write to Console
+			if ( consoleExists ) {
+				args = Array.prototype.slice.call(arguments);
+				message = args.shift();
+				if ( typeof console.debug !== 'undefined' ) {
+					console.debug.apply(console,[message,args]);
+				}
+				else {
+					console.log.apply(console,[message,args]);
+				}
+			}
+			else {
+				message = ("\n"+arguments[0]+"\n");
+			}
+
+			// Write to log
+			for ( i=1,n=arguments.length; i<n; ++i ) {
+				arg = arguments[i];
+				if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
+					try {
+						arg = JSON.stringify(arg);
+					}
+					catch ( Exception ) {
+						// Recursive Object
+					}
+				}
+				message += "\n"+arg+"\n";
+			}
+
+			// Textarea
+			if ( textarea ) {
+				textarea.value += message+"\n-----\n";
+				textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
+			}
+			// No Textarea, No Console
+			else if ( !consoleExists ) {
+				alert(message);
+			}
+
+			// Return true
+			return true;
+		};
+
+
+		// ====================================================================
+		// Emulated Status
+
+		/**
+		 * History.getInternetExplorerMajorVersion()
+		 * Get's the major version of Internet Explorer
+		 * @return {integer}
+		 * @license Public Domain
+		 * @author Benjamin Arthur Lupton <contact@balupton.com>
+		 * @author James Padolsey <https://gist.github.com/527683>
+		 */
+		History.getInternetExplorerMajorVersion = function(){
+			var result = History.getInternetExplorerMajorVersion.cached =
+					(typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
+				?	History.getInternetExplorerMajorVersion.cached
+				:	(function(){
+						var v = 3,
+								div = document.createElement('div'),
+								all = div.getElementsByTagName('i');
+						while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
+						return (v > 4) ? v : false;
+					})()
+				;
+			return result;
+		};
+
+		/**
+		 * History.isInternetExplorer()
+		 * Are we using Internet Explorer?
+		 * @return {boolean}
+		 * @license Public Domain
+		 * @author Benjamin Arthur Lupton <contact@balupton.com>
+		 */
+		History.isInternetExplorer = function(){
+			var result =
+				History.isInternetExplorer.cached =
+				(typeof History.isInternetExplorer.cached !== 'undefined')
+					?	History.isInternetExplorer.cached
+					:	Boolean(History.getInternetExplorerMajorVersion())
+				;
+			return result;
+		};
+
+		/**
+		 * History.emulated
+		 * Which features require emulating?
+		 */
+		History.emulated = {
+			pushState: !Boolean(
+				window.history && window.history.pushState && window.history.replaceState
+				&& !(
+					(/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
+					|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
+				)
+			),
+			hashChange: Boolean(
+				!(('onhashchange' in window) || ('onhashchange' in document))
+				||
+				(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
+			)
+		};
+
+		/**
+		 * History.enabled
+		 * Is History enabled?
+		 */
+		History.enabled = !History.emulated.pushState;
+
+		/**
+		 * History.bugs
+		 * Which bugs are present
+		 */
+		History.bugs = {
+			/**
+			 * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
+			 * https://bugs.webkit.org/show_bug.cgi?id=56249
+			 */
+			setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
+
+			/**
+			 * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
+			 * https://bugs.webkit.org/show_bug.cgi?id=42940
+			 */
+			safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
+
+			/**
+			 * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
+			 */
+			ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
+
+			/**
+			 * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
+			 */
+			hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
+		};
+
+		/**
+		 * History.isEmptyObject(obj)
+		 * Checks to see if the Object is Empty
+		 * @param {Object} obj
+		 * @return {boolean}
+		 */
+		History.isEmptyObject = function(obj) {
+			for ( var name in obj ) {
+				return false;
+			}
+			return true;
+		};
+
+		/**
+		 * History.cloneObject(obj)
+		 * Clones a object and eliminate all references to the original contexts
+		 * @param {Object} obj
+		 * @return {Object}
+		 */
+		History.cloneObject = function(obj) {
+			var hash,newObj;
+			if ( obj ) {
+				hash = JSON.stringify(obj);
+				newObj = JSON.parse(hash);
+			}
+			else {
+				newObj = {};
+			}
+			return newObj;
+		};
+
+
+		// ====================================================================
+		// URL Helpers
+
+		/**
+		 * History.getRootUrl()
+		 * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
+		 * @return {String} rootUrl
+		 */
+		History.getRootUrl = function(){
+			// Create
+			var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
+			if ( document.location.port||false ) {
+				rootUrl += ':'+document.location.port;
+			}
+			rootUrl += '/';
+
+			// Return
+			return rootUrl;
+		};
+
+		/**
+		 * History.getBaseHref()
+		 * Fetches the `href` attribute of the `<base href="...">` element if it exists
+		 * @return {String} baseHref
+		 */
+		History.getBaseHref = function(){
+			// Create
+			var
+				baseElements = document.getElementsByTagName('base'),
+				baseElement = null,
+				baseHref = '';
+
+			// Test for Base Element
+			if ( baseElements.length === 1 ) {
+				// Prepare for Base Element
+				baseElement = baseElements[0];
+				baseHref = baseElement.href.replace(/[^\/]+$/,'');
+			}
+
+			// Adjust trailing slash
+			baseHref = baseHref.replace(/\/+$/,'');
+			if ( baseHref ) baseHref += '/';
+
+			// Return
+			return baseHref;
+		};
+
+		/**
+		 * History.getBaseUrl()
+		 * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
+		 * @return {String} baseUrl
+		 */
+		History.getBaseUrl = function(){
+			// Create
+			var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
+
+			// Return
+			return baseUrl;
+		};
+
+		/**
+		 * History.getPageUrl()
+		 * Fetches the URL of the current page
+		 * @return {String} pageUrl
+		 */
+		History.getPageUrl = function(){
+			// Fetch
+			var
+				State = History.getState(false,false),
+				stateUrl = (State||{}).url||document.location.href,
+				pageUrl;
+
+			// Create
+			pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
+				return (/\./).test(part) ? part : part+'/';
+			});
+
+			// Return
+			return pageUrl;
+		};
+
+		/**
+		 * History.getBasePageUrl()
+		 * Fetches the Url of the directory of the current page
+		 * @return {String} basePageUrl
+		 */
+		History.getBasePageUrl = function(){
+			// Create
+			var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
+				return (/[^\/]$/).test(part) ? '' : part;
+			}).replace(/\/+$/,'')+'/';
+
+			// Return
+			return basePageUrl;
+		};
+
+		/**
+		 * History.getFullUrl(url)
+		 * Ensures that we have an absolute URL and not a relative URL
+		 * @param {string} url
+		 * @param {Boolean} allowBaseHref
+		 * @return {string} fullUrl
+		 */
+		History.getFullUrl = function(url,allowBaseHref){
+			// Prepare
+			var fullUrl = url, firstChar = url.substring(0,1);
+			allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
+
+			// Check
+			if ( /[a-z]+\:\/\//.test(url) ) {
+				// Full URL
+			}
+			else if ( firstChar === '/' ) {
+				// Root URL
+				fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
+			}
+			else if ( firstChar === '#' ) {
+				// Anchor URL
+				fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
+			}
+			else if ( firstChar === '?' ) {
+				// Query URL
+				fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
+			}
+			else {
+				// Relative URL
+				if ( allowBaseHref ) {
+					fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
+				} else {
+					fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
+				}
+				// We have an if condition above as we do not want hashes
+				// which are relative to the baseHref in our URLs
+				// as if the baseHref changes, then all our bookmarks
+				// would now point to different locations
+				// whereas the basePageUrl will always stay the same
+			}
+
+			// Return
+			return fullUrl.replace(/\#$/,'');
+		};
+
+		/**
+		 * History.getShortUrl(url)
+		 * Ensures that we have a relative URL and not a absolute URL
+		 * @param {string} url
+		 * @return {string} url
+		 */
+		History.getShortUrl = function(url){
+			// Prepare
+			var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
+
+			// Trim baseUrl
+			if ( History.emulated.pushState ) {
+				// We are in a if statement as when pushState is not emulated
+				// The actual url these short urls are relative to can change
+				// So within the same session, we the url may end up somewhere different
+				shortUrl = shortUrl.replace(baseUrl,'');
+			}
+
+			// Trim rootUrl
+			shortUrl = shortUrl.replace(rootUrl,'/');
+
+			// Ensure we can still detect it as a state
+			if ( History.isTraditionalAnchor(shortUrl) ) {
+				shortUrl = './'+shortUrl;
+			}
+
+			// Clean It
+			shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
+
+			// Return
+			return shortUrl;
+		};
+
+
+		// ====================================================================
+		// State Storage
+
+		/**
+		 * History.store
+		 * The store for all session specific data
+		 */
+		History.store = {};
+
+		/**
+		 * History.idToState
+		 * 1-1: State ID to State Object
+		 */
+		History.idToState = History.idToState||{};
+
+		/**
+		 * History.stateToId
+		 * 1-1: State String to State ID
+		 */
+		History.stateToId = History.stateToId||{};
+
+		/**
+		 * History.urlToId
+		 * 1-1: State URL to State ID
+		 */
+		History.urlToId = History.urlToId||{};
+
+		/**
+		 * History.storedStates
+		 * Store the states in an array
+		 */
+		History.storedStates = History.storedStates||[];
+
+		/**
+		 * History.savedStates
+		 * Saved the states in an array
+		 */
+		History.savedStates = History.savedStates||[];
+
+		/**
+		 * History.noramlizeStore()
+		 * Noramlize the store by adding necessary values
+		 */
+		History.normalizeStore = function(){
+			History.store.idToState = History.store.idToState||{};
+			History.store.urlToId = History.store.urlToId||{};
+			History.store.stateToId = History.store.stateToId||{};
+		};
+
+		/**
+		 * History.getState()
+		 * Get an object containing the data, title and url of the current state
+		 * @param {Boolean} friendly
+		 * @param {Boolean} create
+		 * @return {Object} State
+		 */
+		History.getState = function(friendly,create){
+			// Prepare
+			if ( typeof friendly === 'undefined' ) { friendly = true; }
+			if ( typeof create === 'undefined' ) { create = true; }
+
+			// Fetch
+			var State = History.getLastSavedState();
+
+			// Create
+			if ( !State && create ) {
+				State = History.createStateObject();
+			}
+
+			// Adjust
+			if ( friendly ) {
+				State = History.cloneObject(State);
+				State.url = State.cleanUrl||State.url;
+			}
+
+			// Return
+			return State;
+		};
+
+		/**
+		 * History.getIdByState(State)
+		 * Gets a ID for a State
+		 * @param {State} newState
+		 * @return {String} id
+		 */
+		History.getIdByState = function(newState){
+
+			// Fetch ID
+			var id = History.extractId(newState.url),
+				str;
+			
+			if ( !id ) {
+				// Find ID via State String
+				str = History.getStateString(newState);
+				if ( typeof History.stateToId[str] !== 'undefined' ) {
+					id = History.stateToId[str];
+				}
+				else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
+					id = History.store.stateToId[str];
+				}
+				else {
+					// Generate a new ID
+					while ( true ) {
+						id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
+						if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
+							break;
+						}
+					}
+
+					// Apply the new State to the ID
+					History.stateToId[str] = id;
+					History.idToState[id] = newState;
+				}
+			}
+
+			// Return ID
+			return id;
+		};
+
+		/**
+		 * History.normalizeState(State)
+		 * Expands a State Object
+		 * @param {object} State
+		 * @return {object}
+		 */
+		History.normalizeState = function(oldState){
+			// Variables
+			var newState, dataNotEmpty;
+
+			// Prepare
+			if ( !oldState || (typeof oldState !== 'object') ) {
+				oldState = {};
+			}
+
+			// Check
+			if ( typeof oldState.normalized !== 'undefined' ) {
+				return oldState;
+			}
+
+			// Adjust
+			if ( !oldState.data || (typeof oldState.data !== 'object') ) {
+				oldState.data = {};
+			}
+
+			// ----------------------------------------------------------------
+
+			// Create
+			newState = {};
+			newState.normalized = true;
+			newState.title = oldState.title||'';
+			newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
+			newState.hash = History.getShortUrl(newState.url);
+			newState.data = History.cloneObject(oldState.data);
+
+			// Fetch ID
+			newState.id = History.getIdByState(newState);
+
+			// ----------------------------------------------------------------
+
+			// Clean the URL
+			newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
+			newState.url = newState.cleanUrl;
+
+			// Check to see if we have more than just a url
+			dataNotEmpty = !History.isEmptyObject(newState.data);
+
+			// Apply
+			if ( newState.title || dataNotEmpty ) {
+				// Add ID to Hash
+				newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
+				if ( !/\?/.test(newState.hash) ) {
+					newState.hash += '?';
+				}
+				newState.hash += '&_suid='+newState.id;
+			}
+
+			// Create the Hashed URL
+			newState.hashedUrl = History.getFullUrl(newState.hash);
+
+			// ----------------------------------------------------------------
+
+			// Update the URL if we have a duplicate
+			if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
+				newState.url = newState.hashedUrl;
+			}
+
+			// ----------------------------------------------------------------
+
+			// Return
+			return newState;
+		};
+
+		/**
+		 * History.createStateObject(data,title,url)
+		 * Creates a object based on the data, title and url state params
+		 * @param {object} data
+		 * @param {string} title
+		 * @param {string} url
+		 * @return {object}
+		 */
+		History.createStateObject = function(data,title,url){
+			// Hashify
+			var State = {
+				'data': data,
+				'title': title,
+				'url': url
+			};
+
+			// Expand the State
+			State = History.normalizeState(State);
+
+			// Return object
+			return State;
+		};
+
+		/**
+		 * History.getStateById(id)
+		 * Get a state by it's UID
+		 * @param {String} id
+		 */
+		History.getStateById = function(id){
+			// Prepare
+			id = String(id);
+
+			// Retrieve
+			var State = History.idToState[id] || History.store.idToState[id] || undefined;
+
+			// Return State
+			return State;
+		};
+
+		/**
+		 * Get a State's String
+		 * @param {State} passedState
+		 */
+		History.getStateString = function(passedState){
+			// Prepare
+			var State, cleanedState, str;
+
+			// Fetch
+			State = History.normalizeState(passedState);
+
+			// Clean
+			cleanedState = {
+				data: State.data,
+				title: passedState.title,
+				url: passedState.url
+			};
+
+			// Fetch
+			str = JSON.stringify(cleanedState);
+
+			// Return
+			return str;
+		};
+
+		/**
+		 * Get a State's ID
+		 * @param {State} passedState
+		 * @return {String} id
+		 */
+		History.getStateId = function(passedState){
+			// Prepare
+			var State, id;
+			
+			// Fetch
+			State = History.normalizeState(passedState);
+
+			// Fetch
+			id = State.id;
+
+			// Return
+			return id;
+		};
+
+		/**
+		 * History.getHashByState(State)
+		 * Creates a Hash for the State Object
+		 * @param {State} passedState
+		 * @return {String} hash
+		 */
+		History.getHashByState = function(passedState){
+			// Prepare
+			var State, hash;
+			
+			// Fetch
+			State = History.normalizeState(passedState);
+
+			// Hash
+			hash = State.hash;
+
+			// Return
+			return hash;
+		};
+
+		/**
+		 * History.extractId(url_or_hash)
+		 * Get a State ID by it's URL or Hash
+		 * @param {string} url_or_hash
+		 * @return {string} id
+		 */
+		History.extractId = function ( url_or_hash ) {
+			// Prepare
+			var id,parts,url;
+
+			// Extract
+			parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
+			url = parts ? (parts[1]||url_or_hash) : url_or_hash;
+			id = parts ? String(parts[2]||'') : '';
+
+			// Return
+			return id||false;
+		};
+
+		/**
+		 * History.isTraditionalAnchor
+		 * Checks to see if the url is a traditional anchor or not
+		 * @param {String} url_or_hash
+		 * @return {Boolean}
+		 */
+		History.isTraditionalAnchor = function(url_or_hash){
+			// Check
+			var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
+
+			// Return
+			return isTraditional;
+		};
+
+		/**
+		 * History.extractState
+		 * Get a State by it's URL or Hash
+		 * @param {String} url_or_hash
+		 * @return {State|null}
+		 */
+		History.extractState = function(url_or_hash,create){
+			// Prepare
+			var State = null, id, url;
+			create = create||false;
+
+			// Fetch SUID
+			id = History.extractId(url_or_hash);
+			if ( id ) {
+				State = History.getStateById(id);
+			}
+
+			// Fetch SUID returned no State
+			if ( !State ) {
+				// Fetch URL
+				url = History.getFullUrl(url_or_hash);
+
+				// Check URL
+				id = History.getIdByUrl(url)||false;
+				if ( id ) {
+					State = History.getStateById(id);
+				}
+
+				// Create State
+				if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
+					State = History.createStateObject(null,null,url);
+				}
+			}
+
+			// Return
+			return State;
+		};
+
+		/**
+		 * History.getIdByUrl()
+		 * Get a State ID by a State URL
+		 */
+		History.getIdByUrl = function(url){
+			// Fetch
+			var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
+
+			// Return
+			return id;
+		};
+
+		/**
+		 * History.getLastSavedState()
+		 * Get an object containing the data, title and url of the current state
+		 * @return {Object} State
+		 */
+		History.getLastSavedState = function(){
+			return History.savedStates[History.savedStates.length-1]||undefined;
+		};
+
+		/**
+		 * History.getLastStoredState()
+		 * Get an object containing the data, title and url of the current state
+		 * @return {Object} State
+		 */
+		History.getLastStoredState = function(){
+			return History.storedStates[History.storedStates.length-1]||undefined;
+		};
+
+		/**
+		 * History.hasUrlDuplicate
+		 * Checks if a Url will have a url conflict
+		 * @param {Object} newState
+		 * @return {Boolean} hasDuplicate
+		 */
+		History.hasUrlDuplicate = function(newState) {
+			// Prepare
+			var hasDuplicate = false,
+				oldState;
+
+			// Fetch
+			oldState = History.extractState(newState.url);
+
+			// Check
+			hasDuplicate = oldState && oldState.id !== newState.id;
+
+			// Return
+			return hasDuplicate;
+		};
+
+		/**
+		 * History.storeState
+		 * Store a State
+		 * @param {Object} newState
+		 * @return {Object} newState
+		 */
+		History.storeState = function(newState){
+			// Store the State
+			History.urlToId[newState.url] = newState.id;
+
+			// Push the State
+			History.storedStates.push(History.cloneObject(newState));
+
+			// Return newState
+			return newState;
+		};
+
+		/**
+		 * History.isLastSavedState(newState)
+		 * Tests to see if the state is the last state
+		 * @param {Object} newState
+		 * @return {boolean} isLast
+		 */
+		History.isLastSavedState = function(newState){
+			// Prepare
+			var isLast = false,
+				newId, oldState, oldId;
+
+			// Check
+			if ( History.savedStates.length ) {
+				newId = newState.id;
+				oldState = History.getLastSavedState();
+				oldId = oldState.id;
+
+				// Check
+				isLast = (newId === oldId);
+			}
+
+			// Return
+			return isLast;
+		};
+
+		/**
+		 * History.saveState
+		 * Push a State
+		 * @param {Object} newState
+		 * @return {boolean} changed
+		 */
+		History.saveState = function(newState){
+			// Check Hash
+			if ( History.isLastSavedState(newState) ) {
+				return false;
+			}
+
+			// Push the State
+			History.savedStates.push(History.cloneObject(newState));
+
+			// Return true
+			return true;
+		};
+
+		/**
+		 * History.getStateByIndex()
+		 * Gets a state by the index
+		 * @param {integer} index
+		 * @return {Object}
+		 */
+		History.getStateByIndex = function(index){
+			// Prepare
+			var State = null;
+
+			// Handle
+			if ( typeof index === 'undefined' ) {
+				// Get the last inserted
+				State = History.savedStates[History.savedStates.length-1];
+			}
+			else if ( index < 0 ) {
+				// Get from the end
+				State = History.savedStates[History.savedStates.length+index];
+			}
+			else {
+				// Get from the beginning
+				State = History.savedStates[index];
+			}
+
+			// Return State
+			return State;
+		};
+
+
+		// ====================================================================
+		// Hash Helpers
+
+		/**
+		 * History.getHash()
+		 * Gets the current document hash
+		 * @return {string}
+		 */
+		History.getHash = function(){
+			var hash = History.unescapeHash(document.location.hash);
+			return hash;
+		};
+
+		/**
+		 * History.unescapeString()
+		 * Unescape a string
+		 * @param {String} str
+		 * @return {string}
+		 */
+		History.unescapeString = function(str){
+			// Prepare
+			var result = str,
+				tmp;
+
+			// Unescape hash
+			while ( true ) {
+				tmp = window.unescape(result);
+				if ( tmp === result ) {
+					break;
+				}
+				result = tmp;
+			}
+
+			// Return result
+			return result;
+		};
+
+		/**
+		 * History.unescapeHash()
+		 * normalize and Unescape a Hash
+		 * @param {String} hash
+		 * @return {string}
+		 */
+		History.unescapeHash = function(hash){
+			// Prepare
+			var result = History.normalizeHash(hash);
+
+			// Unescape hash
+			result = History.unescapeString(result);
+
+			// Return result
+			return result;
+		};
+
+		/**
+		 * History.normalizeHash()
+		 * normalize a hash across browsers
+		 * @return {string}
+		 */
+		History.normalizeHash = function(hash){
+			// Prepare
+			var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
+
+			// Return result
+			return result;
+		};
+
+		/**
+		 * History.setHash(hash)
+		 * Sets the document hash
+		 * @param {string} hash
+		 * @return {History}
+		 */
+		History.setHash = function(hash,queue){
+			// Prepare
+			var adjustedHash, State, pageUrl;
+
+			// Handle Queueing
+			if ( queue !== false && History.busy() ) {
+				// Wait + Push to Queue
+				//History.debug('History.setHash: we must wait', arguments);
+				History.pushQueue({
+					scope: History,
+					callback: History.setHash,
+					args: arguments,
+					queue: queue
+				});
+				return false;
+			}
+
+			// Log
+			//History.debug('History.setHash: called',hash);
+
+			// Prepare
+			adjustedHash = History.escapeHash(hash);
+
+			// Make Busy + Continue
+			History.busy(true);
+
+			// Check if hash is a state
+			State = History.extractState(hash,true);
+			if ( State && !History.emulated.pushState ) {
+				// Hash is a state so skip the setHash
+				//History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
+
+				// PushState
+				History.pushState(State.data,State.title,State.url,false);
+			}
+			else if ( document.location.hash !== adjustedHash ) {
+				// Hash is a proper hash, so apply it
+
+				// Handle browser bugs
+				if ( History.bugs.setHash ) {
+					// Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
+
+					// Fetch the base page
+					pageUrl = History.getPageUrl();
+
+					// Safari hash apply
+					History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
+				}
+				else {
+					// Normal hash apply
+					document.location.hash = adjustedHash;
+				}
+			}
+
+			// Chain
+			return History;
+		};
+
+		/**
+		 * History.escape()
+		 * normalize and Escape a Hash
+		 * @return {string}
+		 */
+		History.escapeHash = function(hash){
+			// Prepare
+			var result = History.normalizeHash(hash);
+
+			// Escape hash
+			result = window.escape(result);
+
+			// IE6 Escape Bug
+			if ( !History.bugs.hashEscape ) {
+				// Restore common parts
+				result = result
+					.replace(/\%21/g,'!')
+					.replace(/\%26/g,'&')
+					.replace(/\%3D/g,'=')
+					.replace(/\%3F/g,'?');
+			}
+
+			// Return result
+			return result;
+		};
+
+		/**
+		 * History.getHashByUrl(url)
+		 * Extracts the Hash from a URL
+		 * @param {string} url
+		 * @return {string} url
+		 */
+		History.getHashByUrl = function(url){
+			// Extract the hash
+			var hash = String(url)
+				.replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
+				;
+
+			// Unescape hash
+			hash = History.unescapeHash(hash);
+
+			// Return hash
+			return hash;
+		};
+
+		/**
+		 * History.setTitle(title)
+		 * Applies the title to the document
+		 * @param {State} newState
+		 * @return {Boolean}
+		 */
+		History.setTitle = function(newState){
+			// Prepare
+			var title = newState.title,
+				firstState;
+
+			// Initial
+			if ( !title ) {
+				firstState = History.getStateByIndex(0);
+				if ( firstState && firstState.url === newState.url ) {
+					title = firstState.title||History.options.initialTitle;
+				}
+			}
+
+			// Apply
+			try {
+				document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
+			}
+			catch ( Exception ) { }
+			document.title = title;
+
+			// Chain
+			return History;
+		};
+
+
+		// ====================================================================
+		// Queueing
+
+		/**
+		 * History.queues
+		 * The list of queues to use
+		 * First In, First Out
+		 */
+		History.queues = [];
+
+		/**
+		 * History.busy(value)
+		 * @param {boolean} value [optional]
+		 * @return {boolean} busy
+		 */
+		History.busy = function(value){
+			// Apply
+			if ( typeof value !== 'undefined' ) {
+				//History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
+				History.busy.flag = value;
+			}
+			// Default
+			else if ( typeof History.busy.flag === 'undefined' ) {
+				History.busy.flag = false;
+			}
+
+			// Queue
+			if ( !History.busy.flag ) {
+				// Execute the next item in the queue
+				clearTimeout(History.busy.timeout);
+				var fireNext = function(){
+					var i, queue, item;
+					if ( History.busy.flag ) return;
+					for ( i=History.queues.length-1; i >= 0; --i ) {
+						queue = History.queues[i];
+						if ( queue.length === 0 ) continue;
+						item = queue.shift();
+						History.fireQueueItem(item);
+						History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
+					}
+				};
+				History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
+			}
+
+			// Return
+			return History.busy.flag;
+		};
+
+		/**
+		 * History.busy.flag
+		 */
+		History.busy.flag = false;
+
+		/**
+		 * History.fireQueueItem(item)
+		 * Fire a Queue Item
+		 * @param {Object} item
+		 * @return {Mixed} result
+		 */
+		History.fireQueueItem = function(item){
+			return item.callback.apply(item.scope||History,item.args||[]);
+		};
+
+		/**
+		 * History.pushQueue(callback,args)
+		 * Add an item to the queue
+		 * @param {Object} item [scope,callback,args,queue]
+		 */
+		History.pushQueue = function(item){
+			// Prepare the queue
+			History.queues[item.queue||0] = History.queues[item.queue||0]||[];
+
+			// Add to the queue
+			History.queues[item.queue||0].push(item);
+
+			// Chain
+			return History;
+		};
+
+		/**
+		 * History.queue (item,queue), (func,queue), (func), (item)
+		 * Either firs the item now if not busy, or adds it to the queue
+		 */
+		History.queue = function(item,queue){
+			// Prepare
+			if ( typeof item === 'function' ) {
+				item = {
+					callback: item
+				};
+			}
+			if ( typeof queue !== 'undefined' ) {
+				item.queue = queue;
+			}
+
+			// Handle
+			if ( History.busy() ) {
+				History.pushQueue(item);
+			} else {
+				History.fireQueueItem(item);
+			}
+
+			// Chain
+			return History;
+		};
+
+		/**
+		 * History.clearQueue()
+		 * Clears the Queue
+		 */
+		History.clearQueue = function(){
+			History.busy.flag = false;
+			History.queues = [];
+			return History;
+		};
+
+
+		// ====================================================================
+		// IE Bug Fix
+
+		/**
+		 * History.stateChanged
+		 * States whether or not the state has changed since the last double check was initialised
+		 */
+		History.stateChanged = false;
+
+		/**
+		 * History.doubleChecker
+		 * Contains the timeout used for the double checks
+		 */
+		History.doubleChecker = false;
+
+		/**
+		 * History.doubleCheckComplete()
+		 * Complete a double check
+		 * @return {History}
+		 */
+		History.doubleCheckComplete = function(){
+			// Update
+			History.stateChanged = true;
+
+			// Clear
+			History.doubleCheckClear();
+
+			// Chain
+			return History;
+		};
+
+		/**
+		 * History.doubleCheckClear()
+		 * Clear a double check
+		 * @return {History}
+		 */
+		History.doubleCheckClear = function(){
+			// Clear
+			if ( History.doubleChecker ) {
+				clearTimeout(History.doubleChecker);
+				History.doubleChecker = false;
+			}
+
+			// Chain
+			return History;
+		};
+
+		/**
+		 * History.doubleCheck()
+		 * Create a double check
+		 * @return {History}
+		 */
+		History.doubleCheck = function(tryAgain){
+			// Reset
+			History.stateChanged = false;
+			History.doubleCheckClear();
+
+			// Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
+			// Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
+			if ( History.bugs.ieDoubleCheck ) {
+				// Apply Check
+				History.doubleChecker = setTimeout(
+					function(){
+						History.doubleCheckClear();
+						if ( !History.stateChanged ) {
+							//History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
+							// Re-Attempt
+							tryAgain();
+						}
+						return true;
+					},
+					History.options.doubleCheckInterval
+				);
+			}
+
+			// Chain
+			return History;
+		};
+
+
+		// ====================================================================
+		// Safari Bug Fix
+
+		/**
+		 * History.safariStatePoll()
+		 * Poll the current state
+		 * @return {History}
+		 */
+		History.safariStatePoll = function(){
+			// Poll the URL
+
+			// Get the Last State which has the new URL
+			var
+				urlState = History.extractState(document.location.href),
+				newState;
+
+			// Check for a difference
+			if ( !History.isLastSavedState(urlState) ) {
+				newState = urlState;
+			}
+			else {
+				return;
+			}
+
+			// Check if we have a state with that url
+			// If not create it
+			if ( !newState ) {
+				//History.debug('History.safariStatePoll: new');
+				newState = History.createStateObject();
+			}
+
+			// Apply the New State
+			//History.debug('History.safariStatePoll: trigger');
+			History.Adapter.trigger(window,'popstate');
+
+			// Chain
+			return History;
+		};
+
+
+		// ====================================================================
+		// State Aliases
+
+		/**
+		 * History.back(queue)
+		 * Send the browser history back one item
+		 * @param {Integer} queue [optional]
+		 */
+		History.back = function(queue){
+			//History.debug('History.back: called', arguments);
+
+			// Handle Queueing
+			if ( queue !== false && History.busy() ) {
+				// Wait + Push to Queue
+				//History.debug('History.back: we must wait', arguments);
+				History.pushQueue({
+					scope: History,
+					callback: History.back,
+					args: arguments,
+					queue: queue
+				});
+				return false;
+			}
+
+			// Make Busy + Continue
+			History.busy(true);
+
+			// Fix certain browser bugs that prevent the state from changing
+			History.doubleCheck(function(){
+				History.back(false);
+			});
+
+			// Go back
+			history.go(-1);
+
+			// End back closure
+			return true;
+		};
+
+		/**
+		 * History.forward(queue)
+		 * Send the browser history forward one item
+		 * @param {Integer} queue [optional]
+		 */
+		History.forward = function(queue){
+			//History.debug('History.forward: called', arguments);
+
+			// Handle Queueing
+			if ( queue !== false && History.busy() ) {
+				// Wait + Push to Queue
+				//History.debug('History.forward: we must wait', arguments);
+				History.pushQueue({
+					scope: History,
+					callback: History.forward,
+					args: arguments,
+					queue: queue
+				});
+				return false;
+			}
+
+			// Make Busy + Continue
+			History.busy(true);
+
+			// Fix certain browser bugs that prevent the state from changing
+			History.doubleCheck(function(){
+				History.forward(false);
+			});
+
+			// Go forward
+			history.go(1);
+
+			// End forward closure
+			return true;
+		};
+
+		/**
+		 * History.go(index,queue)
+		 * Send the browser history back or forward index times
+		 * @param {Integer} queue [optional]
+		 */
+		History.go = function(index,queue){
+			//History.debug('History.go: called', arguments);
+
+			// Prepare
+			var i;
+
+			// Handle
+			if ( index > 0 ) {
+				// Forward
+				for ( i=1; i<=index; ++i ) {
+					History.forward(queue);
+				}
+			}
+			else if ( index < 0 ) {
+				// Backward
+				for ( i=-1; i>=index; --i ) {
+					History.back(queue);
+				}
+			}
+			else {
+				throw new Error('History.go: History.go requires a positive or negative integer passed.');
+			}
+
+			// Chain
+			return History;
+		};
+
+
+		// ====================================================================
+		// HTML5 State Support
+
+		// Non-Native pushState Implementation
+		if ( History.emulated.pushState ) {
+			/*
+			 * Provide Skeleton for HTML4 Browsers
+			 */
+
+			// Prepare
+			var emptyFunction = function(){};
+			History.pushState = History.pushState||emptyFunction;
+			History.replaceState = History.replaceState||emptyFunction;
+		} // History.emulated.pushState
+
+		// Native pushState Implementation
+		else {
+			/*
+			 * Use native HTML5 History API Implementation
+			 */
+
+			/**
+			 * History.onPopState(event,extra)
+			 * Refresh the Current State
+			 */
+			History.onPopState = function(event,extra){
+				// Prepare
+				var stateId = false, newState = false, currentHash, currentState;
+
+				// Reset the double check
+				History.doubleCheckComplete();
+
+				// Check for a Hash, and handle apporiatly
+				currentHash	= History.getHash();
+				if ( currentHash ) {
+					// Expand Hash
+					currentState = History.extractState(currentHash||document.location.href,true);
+					if ( currentState ) {
+						// We were able to parse it, it must be a State!
+						// Let's forward to replaceState
+						//History.debug('History.onPopState: state anchor', currentHash, currentState);
+						History.replaceState(currentState.data, currentState.title, currentState.url, false);
+					}
+					else {
+						// Traditional Anchor
+						//History.debug('History.onPopState: traditional anchor', currentHash);
+						History.Adapter.trigger(window,'anchorchange');
+						History.busy(false);
+					}
+
+					// We don't care for hashes
+					History.expectedStateId = false;
+					return false;
+				}
+
+				// Ensure
+				stateId = History.Adapter.extractEventData('state',event,extra) || false;
+
+				// Fetch State
+				if ( stateId ) {
+					// Vanilla: Back/forward button was used
+					newState = History.getStateById(stateId);
+				}
+				else if ( History.expectedStateId ) {
+					// Vanilla: A new state was pushed, and popstate was called manually
+					newState = History.getStateById(History.expectedStateId);
+				}
+				else {
+					// Initial State
+					newState = History.extractState(document.location.href);
+				}
+
+				// The State did not exist in our store
+				if ( !newState ) {
+					// Regenerate the State
+					newState = History.createStateObject(null,null,document.location.href);
+				}
+
+				// Clean
+				History.expectedStateId = false;
+
+				// Check if we are the same state
+				if ( History.isLastSavedState(newState) ) {
+					// There has been no change (just the page's hash has finally propagated)
+					//History.debug('History.onPopState: no change', newState, History.savedStates);
+					History.busy(false);
+					return false;
+				}
+
+				// Store the State
+				History.storeState(newState);
+				History.saveState(newState);
+
+				// Force update of the title
+				History.setTitle(newState);
+
+				// Fire Our Event
+				History.Adapter.trigger(window,'statechange');
+				History.busy(false);
+
+				// Return true
+				return true;
+			};
+			History.Adapter.bind(window,'popstate',History.onPopState);
+
+			/**
+			 * History.pushState(data,title,url)
+			 * Add a new State to the history object, become it, and trigger onpopstate
+			 * We have to trigger for HTML4 compatibility
+			 * @param {object} data
+			 * @param {string} title
+			 * @param {string} url
+			 * @return {true}
+			 */
+			History.pushState = function(data,title,url,queue){
+				//History.debug('History.pushState: called', arguments);
+
+				// Check the State
+				if ( History.getHashByUrl(url) && History.emulated.pushState ) {
+					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+				}
+
+				// Handle Queueing
+				if ( queue !== false && History.busy() ) {
+					// Wait + Push to Queue
+					//History.debug('History.pushState: we must wait', arguments);
+					History.pushQueue({
+						scope: History,
+						callback: History.pushState,
+						args: arguments,
+						queue: queue
+					});
+					return false;
+				}
+
+				// Make Busy + Continue
+				History.busy(true);
+
+				// Create the newState
+				var newState = History.createStateObject(data,title,url);
+
+				// Check it
+				if ( History.isLastSavedState(newState) ) {
+					// Won't be a change
+					History.busy(false);
+				}
+				else {
+					// Store the newState
+					History.storeState(newState);
+					History.expectedStateId = newState.id;
+
+					// Push the newState
+					history.pushState(newState.id,newState.title,newState.url);
+
+					// Fire HTML5 Event
+					History.Adapter.trigger(window,'popstate');
+				}
+
+				// End pushState closure
+				return true;
+			};
+
+			/**
+			 * History.replaceState(data,title,url)
+			 * Replace the State and trigger onpopstate
+			 * We have to trigger for HTML4 compatibility
+			 * @param {object} data
+			 * @param {string} title
+			 * @param {string} url
+			 * @return {true}
+			 */
+			History.replaceState = function(data,title,url,queue){
+				//History.debug('History.replaceState: called', arguments);
+
+				// Check the State
+				if ( History.getHashByUrl(url) && History.emulated.pushState ) {
+					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+				}
+
+				// Handle Queueing
+				if ( queue !== false && History.busy() ) {
+					// Wait + Push to Queue
+					//History.debug('History.replaceState: we must wait', arguments);
+					History.pushQueue({
+						scope: History,
+						callback: History.replaceState,
+						args: arguments,
+						queue: queue
+					});
+					return false;
+				}
+
+				// Make Busy + Continue
+				History.busy(true);
+
+				// Create the newState
+				var newState = History.createStateObject(data,title,url);
+
+				// Check it
+				if ( History.isLastSavedState(newState) ) {
+					// Won't be a change
+					History.busy(false);
+				}
+				else {
+					// Store the newState
+					History.storeState(newState);
+					History.expectedStateId = newState.id;
+
+					// Push the newState
+					history.replaceState(newState.id,newState.title,newState.url);
+
+					// Fire HTML5 Event
+					History.Adapter.trigger(window,'popstate');
+				}
+
+				// End replaceState closure
+				return true;
+			};
+
+		} // !History.emulated.pushState
+
+
+		// ====================================================================
+		// Initialise
+
+		/**
+		 * Load the Store
+		 */
+		if ( sessionStorage ) {
+			// Fetch
+			try {
+				History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
+			}
+			catch ( err ) {
+				History.store = {};
+			}
+
+			// Normalize
+			History.normalizeStore();
+		}
+		else {
+			// Default Load
+			History.store = {};
+			History.normalizeStore();
+		}
+
+		/**
+		 * Clear Intervals on exit to prevent memory leaks
+		 */
+		History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
+		History.Adapter.bind(window,"unload",History.clearAllIntervals);
+
+		/**
+		 * Create the initial State
+		 */
+		History.saveState(History.storeState(History.extractState(document.location.href,true)));
+
+		/**
+		 * Bind for Saving Store
+		 */
+		if ( sessionStorage ) {
+			// When the page is closed
+			History.onUnload = function(){
+				// Prepare
+				var	currentStore, item;
+
+				// Fetch
+				try {
+					currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
+				}
+				catch ( err ) {
+					currentStore = {};
+				}
+
+				// Ensure
+				currentStore.idToState = currentStore.idToState || {};
+				currentStore.urlToId = currentStore.urlToId || {};
+				currentStore.stateToId = currentStore.stateToId || {};
+
+				// Sync
+				for ( item in History.idToState ) {
+					if ( !History.idToState.hasOwnProperty(item) ) {
+						continue;
+					}
+					currentStore.idToState[item] = History.idToState[item];
+				}
+				for ( item in History.urlToId ) {
+					if ( !History.urlToId.hasOwnProperty(item) ) {
+						continue;
+					}
+					currentStore.urlToId[item] = History.urlToId[item];
+				}
+				for ( item in History.stateToId ) {
+					if ( !History.stateToId.hasOwnProperty(item) ) {
+						continue;
+					}
+					currentStore.stateToId[item] = History.stateToId[item];
+				}
+
+				// Update
+				History.store = currentStore;
+				History.normalizeStore();
+
+				// Store
+				sessionStorage.setItem('History.store',JSON.stringify(currentStore));
+			};
+
+			// For Internet Explorer
+			History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
+			
+			// For Other Browsers
+			History.Adapter.bind(window,'beforeunload',History.onUnload);
+			History.Adapter.bind(window,'unload',History.onUnload);
+			
+			// Both are enabled for consistency
+		}
+
+		// Non-Native pushState Implementation
+		if ( !History.emulated.pushState ) {
+			// Be aware, the following is only for native pushState implementations
+			// If you are wanting to include something for all browsers
+			// Then include it above this if block
+
+			/**
+			 * Setup Safari Fix
+			 */
+			if ( History.bugs.safariPoll ) {
+				History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
+			}
+
+			/**
+			 * Ensure Cross Browser Compatibility
+			 */
+			if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
+				/**
+				 * Fix Safari HashChange Issue
+				 */
+
+				// Setup Alias
+				History.Adapter.bind(window,'hashchange',function(){
+					History.Adapter.trigger(window,'popstate');
+				});
+
+				// Initialise Alias
+				if ( History.getHash() ) {
+					History.Adapter.onDomLoad(function(){
+						History.Adapter.trigger(window,'hashchange');
+					});
+				}
+			}
+
+		} // !History.emulated.pushState
+
+
+	}; // History.initCore
+
+	// Try and Initialise History
+	History.init();
+
+})(window);

+ 480 - 0
js/libraries/balupton-history/scripts/uncompressed/json2.js

@@ -0,0 +1,480 @@
+/*
+    http://www.JSON.org/json2.js
+    2011-01-18
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or '&nbsp;'),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+*/
+
+/*jslint evil: true, strict: false, regexp: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!window.JSON) {
+    window.JSON = {};
+}
+
+(function () {
+    "use strict";
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                this.getUTCFullYear()     + '-' +
+                f(this.getUTCMonth() + 1) + '-' +
+                f(this.getUTCDate())      + 'T' +
+                f(this.getUTCHours())     + ':' +
+                f(this.getUTCMinutes())   + ':' +
+                f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON      =
+            Number.prototype.toJSON  =
+            Boolean.prototype.toJSON = function (key) {
+                return this.valueOf();
+            };
+    }
+
+    var JSON = window.JSON,
+        cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+            var c = meta[a];
+            return typeof c === 'string' ? c :
+                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+        }) + '"' : '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' : gap ?
+                    '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
+                    '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' : gap ?
+                '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
+                '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                    typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/
+                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());

+ 22 - 0
js/libraries/balupton-history/tests.src/_header.php

@@ -0,0 +1,22 @@
+<?php
+	# Locations
+	$dir = dirname(__FILE__);
+	$out = "$dir/../tests";
+
+	# Base URL
+	$base_url = '/';
+	$tests_url = $base_url.'tests';
+
+	# Data
+	$browsers = array(
+		'html4+html5',
+		'html5'
+	);
+	$adapters = array(
+		'jquery',
+		'mootools',
+		'native',
+		'right',
+		'zepto'
+	);
+

+ 45 - 0
js/libraries/balupton-history/tests.src/all.php

@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>History.js Test Suite</title>
+	<style type="text/css">
+		body,html,iframe {
+			padding:0;
+			margin:0;
+			outline:none;
+			border:none;
+		}
+		.browser {
+			padding-top:1em;
+		}
+		.adapter {
+			padding-top:1em;
+		}
+	</style>
+</head>
+<body>
+	<h1>History.js Test Suite</h1>
+	<p>HTML5 Browsers must pass the HTML4+HTML5 tests</p>
+	<p>HTML4 Browsers must pass the HTML4 tests and should fail the HTML5 tests</p>
+	<?php
+	foreach ( $browsers as $browser ) :
+		echo '<div class="browser">';
+		foreach ( $adapters as $adapter ) :
+			echo '<div class="adapter">';
+			# Url
+			$url = "${browser}.${adapter}.html";
+
+			# Title
+			$Browser = ucwords($browser);
+			$Adapter = ucwords($adapter);
+			$title = "History.js ${Browser} ${Adapter} Test Suite";
+
+			# Render
+			?><a href="<?=$url?>"><?=$title?></a><?php
+			echo '</div>';
+		endforeach;
+		echo '</div>';
+	endforeach;
+	?>
+</body>
+</html>

+ 51 - 0
js/libraries/balupton-history/tests.src/each.php

@@ -0,0 +1,51 @@
+<?php
+	# Url
+	$url = "${browser}.${adapter}.html";
+
+	# Titles
+	$Browser = strtoupper($browser);
+	$Adapter = ucwords($adapter);
+	$title = "History.js ${Browser} ${Adapter} Test Suite";
+?><!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title><?=$title?></title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/<?=$adapter?>.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header"><?=$title?></h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/<?=$browser?>/<?=$adapter?>.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 23 - 0
js/libraries/balupton-history/tests.src/index.php

@@ -0,0 +1,23 @@
+<?php
+	# Header
+	require_once(dirname(__FILE__).'/_header.php');
+
+	# Index
+	ob_start();
+	require($dir.'/all.php');
+	$contents = ob_get_contents();
+	ob_end_clean();
+	file_put_contents($out.'/index.html', $contents);
+
+	# Each
+	foreach ( $browsers as $browser )
+	foreach ( $adapters as $adapter ) {
+		ob_start();
+		require($dir.'/each.php');
+		$contents = ob_get_contents();
+		ob_end_clean();
+		file_put_contents($out."/${url}", $contents);
+	}
+
+	# Done
+?><html><body><a href="../tests">Tests</a></body></html>

+ 13 - 0
js/libraries/balupton-history/tests/.htaccess

@@ -0,0 +1,13 @@
+Options +FollowSymlinks
+RewriteEngine On
+
+# Clean Adapter
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ([^\.]+)$ $1.html [NC,L,QSA]
+
+# Can someone smarter than me make it so:
+# http://localhost/history.js/tests/uncompressed-html5-persistant-jquery
+# Does not redirect to:
+# http://localhost/history.js/tests/uncompressed-html5-persistant-jquery.html
+# But still accesses that url

+ 43 - 0
js/libraries/balupton-history/tests/html4+html5.jquery.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML4+HTML5 Jquery Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/jquery.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML4+HTML5 Jquery Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html4+html5/jquery.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html4+html5.mootools.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML4+HTML5 Mootools Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/mootools.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML4+HTML5 Mootools Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html4+html5/mootools.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html4+html5.native.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML4+HTML5 Native Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/native.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML4+HTML5 Native Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html4+html5/native.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html4+html5.right.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML4+HTML5 Right Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/right.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML4+HTML5 Right Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html4+html5/right.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html4+html5.zepto.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML4+HTML5 Zepto Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/zepto.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML4+HTML5 Zepto Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html4+html5/zepto.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html5.jquery.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML5 Jquery Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/jquery.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML5 Jquery Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html5/jquery.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html5.mootools.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML5 Mootools Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/mootools.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML5 Mootools Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html5/mootools.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html5.native.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML5 Native Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/native.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML5 Native Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html5/native.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html5.right.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML5 Right Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/right.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML5 Right Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html5/right.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 43 - 0
js/libraries/balupton-history/tests/html5.zepto.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html debug="true">
+<head>
+	<meta http-equiv="Expires" CONTENT="Mon, 06 Jan 1990 00:00:01 GMT" />
+	<meta http-equiv="PRAGMA" CONTENT="NO-CACHE" />
+	<meta http-equiv="CACHE-CONTROL" CONTENT="NO-CACHE" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+	<title>History.js HTML5 Zepto Test Suite</title>
+
+	<!-- Check -->
+	<script>
+		var href = window.document.location.href,
+			test_url = href.replace(/(history\.js\/tests\/[^\/\?\#]+).*/,'$1');
+		if ( test_url !== href ) {
+			window.document.location.href = test_url;
+		}
+	</script>
+
+	<!-- Framework -->
+	<script src="../vendor/zepto.js"></script>
+
+	<!-- QUnit -->
+	<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css" type="text/css" media="screen">
+	<script src="../vendor/qunit/qunit/qunit.js"></script>
+</head>
+<body>
+	<!-- Elements -->
+	<h1 id="qunit-header">History.js HTML5 Zepto Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+	<button onclick="history.back()">back</button><button onclick="history.forward()">forward</button>
+	<textarea id="log" style="width:100%;height:400px"></textarea>
+
+	<!-- History.js -->
+	<script src="../scripts/bundled/html5/zepto.history.js"></script>
+
+	<!-- Tests -->
+	<script src="tests.js"></script>
+</body>
+</html>

+ 3 - 0
js/libraries/balupton-history/tests/image.php

@@ -0,0 +1,3 @@
+<?php
+header('Content-type: image/jpeg');
+sleep(10);

Datei-Diff unterdrückt, da er zu groß ist
+ 23 - 0
js/libraries/balupton-history/tests/index.html


+ 254 - 0
js/libraries/balupton-history/tests/tests.js

@@ -0,0 +1,254 @@
+(function(){
+
+var
+	History = window.History,
+	document = window.document,
+	test = window.test,
+	same = window.same;
+
+// Check
+if ( !History.enabled ) {
+	throw new Error('History.js is disabled');
+}
+
+// Prepare
+History.options.debug = false;
+
+// Variables
+var
+	States = {
+		// Home
+		0: {
+			'url': document.location.href.replace(/#.*$/,''),
+			'title': ''
+		},
+		// One
+		1: {
+			'data': {
+				'state': 1,
+				'rand': Math.random()
+			},
+			'title': 'State 1',
+			'url': '?state=1'
+		},
+		// Two
+		2: {
+			'data': {
+				'state': 2,
+				'rand': Math.random()
+			},
+			'title': 'State 2',
+			'url': '?state=2&asd=%20asd%2520asd'
+		},
+		// Three
+		3: {
+			'url': '?state=3'
+		},
+		// Four
+		4: {
+			'data': {
+				'state': 4,
+				'trick': true,
+				'rand': Math.random()
+			},
+			'title': 'State 4',
+			'url': '?state=3'
+		},
+		// Log
+		5: {
+			'url': '?state=1#log'
+		},
+		// Six
+		6: {
+			'data': {
+				'state': 6,
+				'rand': Math.random()
+			},
+			'url': 'six.html'
+		},
+		// Seven
+		7: {
+			'url': 'seven'
+		},
+		// Eight
+		8: {
+			'url': '/eight'
+		}
+	},
+	stateOrder = [0,1,2,3,4,3,1,0,1,3,4,3,1,0,6,7,8,1,8,7,6,0],
+	currentTest = 0;
+
+// Original Title
+var title = document.title;
+
+var banner;
+
+var checkStatus = function(){
+	banner = banner || document.getElementById('qunit-banner');
+	var status = banner.className !== 'qunit-fail';
+	return status;
+};
+
+// Check State
+var checkState = function(){
+	if ( !checkStatus() ) {
+		throw new Error('A test has failed');
+	}
+
+	var
+		stateIndex = stateOrder[currentTest],
+		expectedState = History.normalizeState(States[stateIndex]),
+		actualState = History.getState(false);
+
+	++currentTest;
+
+	document.title = title+': '+actualState.url;
+
+	var
+		testName = 'Test '+currentTest,
+		stateName = 'State '+stateIndex;
+
+	test(testName,function(){
+		History.log('Completed: '+testName +' / '+ stateName);
+		same(actualState,expectedState,stateName);
+	});
+
+	// Image Load to Stress Test Safari and Opera
+	(new Image()).src = "image.php";
+};
+
+// Check the Initial State
+checkState();
+
+// State Change
+History.Adapter.bind(window,'statechange',checkState);
+
+// Log
+var addLog = function(){
+	var args = arguments;
+	History.queue(function(){
+		History.log.apply(History,args);
+	});
+};
+
+// Dom Load
+History.Adapter.onDomLoad(function(){
+	setTimeout(function(){
+
+	// ----------------------------------------------------------------------
+	// Test State Functionality: Adding
+
+	// Test 2 / State 1 (0 -> 1)
+	// Tests HTML4 -> HTML5 Graceful Upgrade
+	addLog('Test 2',History.queues.length,History.busy.flag);
+	History.setHash(History.getHashByState(States[1]));
+
+	// Test 3 / State 2 (1 -> 2)
+	addLog('Test 3',History.queues.length,History.busy.flag);
+	History.pushState(States[2].data, States[2].title, States[2].url);
+
+	// Test 3-2 / State 2 (2 -> 2) / No Change
+	addLog('Test 3-2',History.queues.length,History.busy.flag);
+	History.pushState(States[2].data, States[2].title, States[2].url);
+
+	// Test 3-3 / State 2 (2 -> 2) / No Change
+	addLog('Test 3-3',History.queues.length,History.busy.flag);
+	History.replaceState(States[2].data, States[2].title, States[2].url);
+
+	// Test 4 / State 3 (2 -> 3)
+	addLog('Test 4',History.queues.length,History.busy.flag);
+	History.replaceState(States[3].data, States[3].title, States[3].url);
+
+	// Test 5 / State 4 (3 -> 4)
+	addLog('Test 5',History.queues.length,History.busy.flag);
+	History.pushState(States[4].data, States[4].title, States[4].url);
+
+	// ----------------------------------------------------------------------
+	// Test State Functionality: Traversing
+
+	// Test 6 / State 3 (4 -> 3)
+	// Test 7 / State 1 (3 -> 2 -> 1)
+	addLog('Test 6,7',History.queues.length,History.busy.flag);
+	History.go(-2);
+
+	// Test 8 / State 0 (1 -> 0)
+	// Tests Default State
+	addLog('Test 8',History.queues.length,History.busy.flag);
+	History.back();
+
+	// Test 9 / State 1 (0 -> 1)
+	// Test 10 / State 3 (1 -> 2 -> 3)
+	addLog('Test 9,10',History.queues.length,History.busy.flag);
+	History.go(2);
+
+	// Test 11 / State 4 (3 -> 4)
+	addLog('Test 11',History.queues.length,History.busy.flag);
+	History.forward();
+
+	// Test 12 / State 3 (4 -> 3)
+	addLog('Test 12',History.queues.length,History.busy.flag);
+	History.back();
+
+	// Test 13 / State 1 (3 -> 2 -> 1)
+	addLog('Test 13',History.queues.length,History.busy.flag);
+	History.back();
+
+	// ----------------------------------------------------------------------
+	// Test State Functionality: Traditional Anchors
+
+	// Test 13-2 / State 1 (1 -> #log) / No Change
+	addLog('Test 13-2',History.queues.length,History.busy.flag);
+	History.setHash('log');
+
+	// Test 13-3 / State 1 (#log -> 1) / No Change
+	addLog('Test 13-3',History.queues.length,History.busy.flag);
+	History.back();
+
+	// Test 14 / State 0 (1 -> 0)
+	addLog('Test 14',History.queues.length,History.busy.flag);
+	History.back();
+
+	// ----------------------------------------------------------------------
+	// Test URL Handling: Adding
+
+	// Test 15 / State 6 (1 -> 6)
+	// Also tests data with no title
+	addLog('Test 15',History.queues.length,History.busy.flag);
+	History.pushState(States[6].data, States[6].title, States[6].url);
+
+	// Test 16 / State 7 (6 -> 7)
+	addLog('Test 16',History.queues.length,History.busy.flag);
+	History.pushState(States[7].data, States[7].title, States[7].url);
+
+	// Test 17 / State 7 (7 -> 8)
+	addLog('Test 17',History.queues.length,History.busy.flag);
+	History.pushState(States[8].data, States[8].title, States[8].url);
+
+	// Test 18 / State 1 (8 -> 1)
+	// Should be /eight?state=1
+	addLog('Test 18',History.queues.length,History.busy.flag);
+	History.pushState(States[1].data, States[1].title, States[1].url);
+
+	// ----------------------------------------------------------------------
+	// Test URL Handling: Traversing
+
+	// Test 19 / State 8 (1 -> 8)
+	addLog('Test 19',History.queues.length,History.busy.flag);
+	History.back();
+
+	// Test 20 / State 7 (8 -> 7)
+	addLog('Test 20',History.queues.length,History.busy.flag);
+	History.back();
+
+	// Test 21 / State 6 (7 -> 6)
+	addLog('Test 21',History.queues.length,History.busy.flag);
+	History.back();
+
+	// Test 22 / State 0 (6 -> 0)
+	addLog('Test 22',History.queues.length,History.busy.flag);
+	History.back();
+
+	},1000); // wait for test one to complete
+});
+
+})();

+ 9046 - 0
js/libraries/balupton-history/vendor/jquery.js

@@ -0,0 +1,9046 @@
+/*!
+ * jQuery JavaScript Library v1.6.4
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Sep 12 18:54:48 2011 -0400
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+	navigator = window.navigator,
+	location = window.location;
+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, rootjQuery );
+	},
+
+	// 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
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+	// Check if a string has a non-whitespace character in it
+	rnotwhite = /\S/,
+
+	// Used for trimming whitespace
+	trimLeft = /^\s+/,
+	trimRight = /\s+$/,
+
+	// 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.]+))?/,
+
+	// Matches dashed string for camelizing
+	rdashAlpha = /-([a-z]|[0-9])/ig,
+	rmsPrefix = /^-ms-/,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return ( letter + "" ).toUpperCase();
+	},
+
+	// Keep a UserAgent string for use with jQuery.browser
+	userAgent = navigator.userAgent,
+
+	// For matching the engine and version of the browser
+	browserMatch,
+
+	// The deferred used 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 = {
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		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 = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			// Are we dealing with HTML string or an ID?
+			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = 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] ) {
+					context = context instanceof jQuery ? context[0] : context;
+					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 ? jQuery.clone(ret.fragment) : 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: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return (context || rootjQuery).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( 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.6.4",
+
+	// 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[ this.length + num ] : 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 = this.constructor();
+
+		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();
+
+		// Add the callback
+		readyList.done( 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 || this.constructor(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() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		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 ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			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,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+		// Either a released hold or an DOMready/load event and not yet ready
+		if ( (wait === true && !--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
+			readyList.resolveWith( document, [ jQuery ] );
+
+			// Trigger any bound ready events
+			if ( jQuery.fn.trigger ) {
+				jQuery( document ).trigger( "ready" ).unbind( "ready" );
+			}
+		}
+	},
+
+	bindReady: function() {
+		if ( readyList ) {
+			return;
+		}
+
+		readyList = jQuery._Deferred();
+
+		// 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;
+		}
+
+		try {
+			// Not own constructor property must be Object
+			if ( obj.constructor &&
+				!hasOwn.call(obj, "constructor") &&
+				!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+				return false;
+			}
+		} catch ( e ) {
+			// IE8,9 Will throw exceptions on certain host objects #9897
+			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 );
+
+		// Attempt to parse using the native JSON parser first
+		if ( window.JSON && window.JSON.parse ) {
+			return window.JSON.parse( 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, "")) ) {
+
+			return (new Function( "return " + data ))();
+
+		}
+		jQuery.error( "Invalid JSON: " + data );
+	},
+
+	// Cross-browser xml parsing
+	parseXML: function( data ) {
+		var xml, tmp;
+		try {
+			if ( window.DOMParser ) { // Standard
+				tmp = new DOMParser();
+				xml = tmp.parseFromString( data , "text/xml" );
+			} else { // IE
+				xml = new ActiveXObject( "Microsoft.XMLDOM" );
+				xml.async = "false";
+				xml.loadXML( data );
+			}
+		} catch( e ) {
+			xml = undefined;
+		}
+		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evaluates a script in a global context
+	// Workarounds based on findings by Jim Driscoll
+	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+	globalEval: function( data ) {
+		if ( data && rnotwhite.test( data ) ) {
+			// We use execScript on Internet Explorer
+			// We use an anonymous function so that context is window
+			// rather than jQuery in Firefox
+			( window.execScript || function( data ) {
+				window[ "eval" ].call( window, data );
+			} )( data );
+		}
+	},
+
+	// Convert dashed to camelCase; used by the css and data modules
+	// Microsoft forgot to hump their vendor prefix (#9572)
+	camelCase: function( string ) {
+		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.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 ( ; i < length; ) {
+					if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		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 ) {
+			return -1;
+		}
+
+		if ( indexOf ) {
+			return indexOf.call( array, 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 value, key, ret = [],
+			i = 0,
+			length = elems.length,
+			// jquery objects are treated as arrays
+			isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+		// Go through the array, translating each of the items to their
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( key in elems ) {
+				value = callback( elems[ key ], key, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return ret.concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		if ( typeof context === "string" ) {
+			var tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		var args = slice.call( arguments, 2 ),
+			proxy = function() {
+				return fn.apply( context, args.concat( slice.call( arguments ) ) );
+			};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	// Mutifunctional method to get and set values to a collection
+	// The value/s can optionally be executed if it's 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" };
+	},
+
+	sub: function() {
+		function jQuerySub( selector, context ) {
+			return new jQuerySub.fn.init( selector, context );
+		}
+		jQuery.extend( true, jQuerySub, this );
+		jQuerySub.superclass = this;
+		jQuerySub.fn = jQuerySub.prototype = this();
+		jQuerySub.fn.constructor = jQuerySub;
+		jQuerySub.sub = this.sub;
+		jQuerySub.fn.init = function init( selector, context ) {
+			if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+				context = jQuerySub( context );
+			}
+
+			return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+		};
+		jQuerySub.fn.init.prototype = jQuerySub.fn;
+		var rootjQuerySub = jQuerySub(document);
+		return jQuerySub;
+	},
+
+	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;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.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();
+}
+
+return jQuery;
+
+})();
+
+
+var // Promise methods
+	promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
+	// Static reference to slice
+	sliceDeferred = [].slice;
+
+jQuery.extend({
+	// Create a simple deferred (one callbacks list)
+	_Deferred: function() {
+		var // callbacks list
+			callbacks = [],
+			// stored [ context , args ]
+			fired,
+			// to avoid firing when already doing so
+			firing,
+			// flag to know if the deferred has been cancelled
+			cancelled,
+			// the deferred itself
+			deferred  = {
+
+				// done( f1, f2, ...)
+				done: function() {
+					if ( !cancelled ) {
+						var args = arguments,
+							i,
+							length,
+							elem,
+							type,
+							_fired;
+						if ( fired ) {
+							_fired = fired;
+							fired = 0;
+						}
+						for ( i = 0, length = args.length; i < length; i++ ) {
+							elem = args[ i ];
+							type = jQuery.type( elem );
+							if ( type === "array" ) {
+								deferred.done.apply( deferred, elem );
+							} else if ( type === "function" ) {
+								callbacks.push( elem );
+							}
+						}
+						if ( _fired ) {
+							deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
+						}
+					}
+					return this;
+				},
+
+				// resolve with given context and args
+				resolveWith: function( context, args ) {
+					if ( !cancelled && !fired && !firing ) {
+						// make sure args are available (#8421)
+						args = args || [];
+						firing = 1;
+						try {
+							while( callbacks[ 0 ] ) {
+								callbacks.shift().apply( context, args );
+							}
+						}
+						finally {
+							fired = [ context, args ];
+							firing = 0;
+						}
+					}
+					return this;
+				},
+
+				// resolve with this as context and given arguments
+				resolve: function() {
+					deferred.resolveWith( this, arguments );
+					return this;
+				},
+
+				// Has this deferred been resolved?
+				isResolved: function() {
+					return !!( firing || fired );
+				},
+
+				// Cancel
+				cancel: function() {
+					cancelled = 1;
+					callbacks = [];
+					return this;
+				}
+			};
+
+		return deferred;
+	},
+
+	// Full fledged deferred (two callbacks list)
+	Deferred: function( func ) {
+		var deferred = jQuery._Deferred(),
+			failDeferred = jQuery._Deferred(),
+			promise;
+		// Add errorDeferred methods, then and promise
+		jQuery.extend( deferred, {
+			then: function( doneCallbacks, failCallbacks ) {
+				deferred.done( doneCallbacks ).fail( failCallbacks );
+				return this;
+			},
+			always: function() {
+				return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
+			},
+			fail: failDeferred.done,
+			rejectWith: failDeferred.resolveWith,
+			reject: failDeferred.resolve,
+			isRejected: failDeferred.isResolved,
+			pipe: function( fnDone, fnFail ) {
+				return jQuery.Deferred(function( newDefer ) {
+					jQuery.each( {
+						done: [ fnDone, "resolve" ],
+						fail: [ fnFail, "reject" ]
+					}, function( handler, data ) {
+						var fn = data[ 0 ],
+							action = data[ 1 ],
+							returned;
+						if ( jQuery.isFunction( fn ) ) {
+							deferred[ handler ](function() {
+								returned = fn.apply( this, arguments );
+								if ( returned && jQuery.isFunction( returned.promise ) ) {
+									returned.promise().then( newDefer.resolve, newDefer.reject );
+								} else {
+									newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+								}
+							});
+						} else {
+							deferred[ handler ]( newDefer[ action ] );
+						}
+					});
+				}).promise();
+			},
+			// Get a promise for this deferred
+			// If obj is provided, the promise aspect is added to the object
+			promise: function( obj ) {
+				if ( obj == null ) {
+					if ( promise ) {
+						return promise;
+					}
+					promise = obj = {};
+				}
+				var i = promiseMethods.length;
+				while( i-- ) {
+					obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
+				}
+				return obj;
+			}
+		});
+		// Make sure only one callback list will be used
+		deferred.done( failDeferred.cancel ).fail( deferred.cancel );
+		// Unexpose cancel
+		delete deferred.cancel;
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( firstParam ) {
+		var args = arguments,
+			i = 0,
+			length = args.length,
+			count = length,
+			deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+				firstParam :
+				jQuery.Deferred();
+		function resolveFunc( i ) {
+			return function( value ) {
+				args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+				if ( !( --count ) ) {
+					// Strange bug in FF4:
+					// Values changed onto the arguments object sometimes end up as undefined values
+					// outside the $.when method. Cloning the object into a fresh array solves the issue
+					deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
+				}
+			};
+		}
+		if ( length > 1 ) {
+			for( ; i < length; i++ ) {
+				if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
+					args[ i ].promise().then( resolveFunc(i), deferred.reject );
+				} else {
+					--count;
+				}
+			}
+			if ( !count ) {
+				deferred.resolveWith( deferred, args );
+			}
+		} else if ( deferred !== firstParam ) {
+			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+		}
+		return deferred.promise();
+	}
+});
+
+
+
+jQuery.support = (function() {
+
+	var div = document.createElement( "div" ),
+		documentElement = document.documentElement,
+		all,
+		a,
+		select,
+		opt,
+		input,
+		marginDiv,
+		support,
+		fragment,
+		body,
+		testElementParent,
+		testElement,
+		testElementStyle,
+		tds,
+		events,
+		eventName,
+		i,
+		isSupported;
+
+	// Preliminary tests
+	div.setAttribute("className", "t");
+	div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+
+	all = div.getElementsByTagName( "*" );
+	a = div.getElementsByTagName( "a" )[ 0 ];
+
+	// Can't get basic test support
+	if ( !all || !all.length || !a ) {
+		return {};
+	}
+
+	// First batch of supports tests
+	select = document.createElement( "select" );
+	opt = select.appendChild( document.createElement("option") );
+	input = div.getElementsByTagName( "input" )[ 0 ];
+
+	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 instead)
+		style: /top/.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: ( input.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,
+
+		// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+		getSetAttribute: div.className !== "t",
+
+		// Will be defined later
+		submitBubbles: true,
+		changeBubbles: true,
+		focusinBubbles: false,
+		deleteExpando: true,
+		noCloneEvent: true,
+		inlineBlockNeedsLayout: false,
+		shrinkWrapBlocks: false,
+		reliableMarginRight: true
+	};
+
+	// Make sure checked status is properly cloned
+	input.checked = true;
+	support.noCloneChecked = input.cloneNode( true ).checked;
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as disabled)
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Test to see if it's possible to delete an expando from an element
+	// Fails in Internet Explorer
+	try {
+		delete div.test;
+	} catch( e ) {
+		support.deleteExpando = false;
+	}
+
+	if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+		div.attachEvent( "onclick", function() {
+			// Cloning a node shouldn't copy over any
+			// bound event handlers (IE does this)
+			support.noCloneEvent = false;
+		});
+		div.cloneNode( true ).fireEvent( "onclick" );
+	}
+
+	// Check if a radio maintains it's value
+	// after being appended to the DOM
+	input = document.createElement("input");
+	input.value = "t";
+	input.setAttribute("type", "radio");
+	support.radioValue = input.value === "t";
+
+	input.setAttribute("checked", "checked");
+	div.appendChild( input );
+	fragment = document.createDocumentFragment();
+	fragment.appendChild( div.firstChild );
+
+	// WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	div.innerHTML = "";
+
+	// Figure out if the W3C box model works as expected
+	div.style.width = div.style.paddingLeft = "1px";
+
+	body = document.getElementsByTagName( "body" )[ 0 ];
+	// We use our own, invisible, body unless the body is already present
+	// in which case we use a div (#9239)
+	testElement = document.createElement( body ? "div" : "body" );
+	testElementStyle = {
+		visibility: "hidden",
+		width: 0,
+		height: 0,
+		border: 0,
+		margin: 0,
+		background: "none"
+	};
+	if ( body ) {
+		jQuery.extend( testElementStyle, {
+			position: "absolute",
+			left: "-1000px",
+			top: "-1000px"
+		});
+	}
+	for ( i in testElementStyle ) {
+		testElement.style[ i ] = testElementStyle[ i ];
+	}
+	testElement.appendChild( div );
+	testElementParent = body || documentElement;
+	testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+	// Check if a disconnected checkbox will retain its checked
+	// value of true after appended to the DOM (IE6/7)
+	support.appendChecked = input.checked;
+
+	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;
+		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>";
+		support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+	}
+
+	div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+	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)
+	isSupported = ( 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)
+	support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+	div.innerHTML = "";
+
+	// Check if div with explicit width and no margin-right incorrectly
+	// gets computed margin-right based on width of container. For more
+	// info see bug #3333
+	// Fails in WebKit before Feb 2011 nightlies
+	// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+	if ( document.defaultView && document.defaultView.getComputedStyle ) {
+		marginDiv = document.createElement( "div" );
+		marginDiv.style.width = "0";
+		marginDiv.style.marginRight = "0";
+		div.appendChild( marginDiv );
+		support.reliableMarginRight =
+			( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+	}
+
+	// Remove the body element we added
+	testElement.innerHTML = "";
+	testElementParent.removeChild( testElement );
+
+	// Technique from Juriy Zaytsev
+	// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+	// We only care about the case where non-standard event systems
+	// are used, namely in IE. Short-circuiting here helps us to
+	// avoid an eval call (in setAttribute) which can cause CSP
+	// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+	if ( div.attachEvent ) {
+		for( i in {
+			submit: 1,
+			change: 1,
+			focusin: 1
+		} ) {
+			eventName = "on" + i;
+			isSupported = ( eventName in div );
+			if ( !isSupported ) {
+				div.setAttribute( eventName, "return;" );
+				isSupported = ( typeof div[ eventName ] === "function" );
+			}
+			support[ i + "Bubbles" ] = isSupported;
+		}
+	}
+
+	// Null connected elements to avoid leaks in IE
+	testElement = fragment = select = opt = body = marginDiv = div = input = null;
+
+	return support;
+})();
+
+// Keep track of boxModel
+jQuery.boxModel = jQuery.support.boxModel;
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+	rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+	cache: {},
+
+	// Please use with caution
+	uuid: 0,
+
+	// Unique for each copy of jQuery on the page
+	// Non-digits removed to match rinlinejQuery
+	expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+	// 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
+	},
+
+	hasData: function( elem ) {
+		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+
+		return !!elem && !isEmptyDataObject( elem );
+	},
+
+	data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var thisCache, ret,
+			internalKey = jQuery.expando,
+			getByName = typeof name === "string",
+
+			// We have to handle DOM nodes and JS objects differently because IE6-7
+			// can't GC object references properly across the DOM-JS boundary
+			isNode = elem.nodeType,
+
+			// Only DOM nodes need the global jQuery cache; JS object data is
+			// attached directly to the object so GC can occur automatically
+			cache = isNode ? jQuery.cache : elem,
+
+			// Only defining an ID for JS objects if its cache already exists allows
+			// the code to shortcut on the same path as a DOM node with no cache
+			id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
+
+		// Avoid doing any more work than we need to when trying to get data on an
+		// object that has no data at all
+		if ( (!id || (pvt && id && (cache[ id ] && !cache[ id ][ internalKey ]))) && getByName && data === undefined ) {
+			return;
+		}
+
+		if ( !id ) {
+			// Only DOM nodes need a new unique ID for each element since their data
+			// ends up in the global cache
+			if ( isNode ) {
+				elem[ jQuery.expando ] = id = ++jQuery.uuid;
+			} else {
+				id = jQuery.expando;
+			}
+		}
+
+		if ( !cache[ id ] ) {
+			cache[ id ] = {};
+
+			// TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+			// metadata on plain JS objects when the object is serialized using
+			// JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+		}
+
+		// An object can be passed to jQuery.data instead of a key/value pair; this gets
+		// shallow copied over onto the existing cache
+		if ( typeof name === "object" || typeof name === "function" ) {
+			if ( pvt ) {
+				cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
+			} else {
+				cache[ id ] = jQuery.extend(cache[ id ], name);
+			}
+		}
+
+		thisCache = cache[ id ];
+
+		// Internal jQuery data is stored in a separate object inside the object's data
+		// cache in order to avoid key collisions between internal data and user-defined
+		// data
+		if ( pvt ) {
+			if ( !thisCache[ internalKey ] ) {
+				thisCache[ internalKey ] = {};
+			}
+
+			thisCache = thisCache[ internalKey ];
+		}
+
+		if ( data !== undefined ) {
+			thisCache[ jQuery.camelCase( name ) ] = data;
+		}
+
+		// TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
+		// not attempt to inspect the internal events object using jQuery.data, as this
+		// internal data object is undocumented and subject to change.
+		if ( name === "events" && !thisCache[name] ) {
+			return thisCache[ internalKey ] && thisCache[ internalKey ].events;
+		}
+
+		// Check for both converted-to-camel and non-converted data property names
+		// If a data property was specified
+		if ( getByName ) {
+
+			// First Try to find as-is property data
+			ret = thisCache[ name ];
+
+			// Test for null|undefined property data
+			if ( ret == null ) {
+
+				// Try to find the camelCased property
+				ret = thisCache[ jQuery.camelCase( name ) ];
+			}
+		} else {
+			ret = thisCache;
+		}
+
+		return ret;
+	},
+
+	removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var thisCache,
+
+			// Reference to internal data cache key
+			internalKey = jQuery.expando,
+
+			isNode = elem.nodeType,
+
+			// See jQuery.data for more information
+			cache = isNode ? jQuery.cache : elem,
+
+			// See jQuery.data for more information
+			id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+		// If there is already no cache entry for this object, there is no
+		// purpose in continuing
+		if ( !cache[ id ] ) {
+			return;
+		}
+
+		if ( name ) {
+
+			thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
+
+			if ( thisCache ) {
+
+				// Support interoperable removal of hyphenated or camelcased keys
+				if ( !thisCache[ name ] ) {
+					name = jQuery.camelCase( name );
+				}
+
+				delete thisCache[ name ];
+
+				// If there is no data left in the cache, we want to continue
+				// and let the cache object itself get destroyed
+				if ( !isEmptyDataObject(thisCache) ) {
+					return;
+				}
+			}
+		}
+
+		// See jQuery.data for more information
+		if ( pvt ) {
+			delete cache[ id ][ internalKey ];
+
+			// Don't destroy the parent cache unless the internal data object
+			// had been the only thing left in it
+			if ( !isEmptyDataObject(cache[ id ]) ) {
+				return;
+			}
+		}
+
+		var internalCache = cache[ id ][ internalKey ];
+
+		// Browsers that fail expando deletion also refuse to delete expandos on
+		// the window, but it will allow it on all other JS objects; other browsers
+		// don't care
+		// Ensure that `cache` is not a window object #10080
+		if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+			delete cache[ id ];
+		} else {
+			cache[ id ] = null;
+		}
+
+		// We destroyed the entire user cache at once because it's faster than
+		// iterating through each key, but we need to continue to persist internal
+		// data if it existed
+		if ( internalCache ) {
+			cache[ id ] = {};
+			// TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+			// metadata on plain JS objects when the object is serialized using
+			// JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+
+			cache[ id ][ internalKey ] = internalCache;
+
+		// Otherwise, we need to eliminate the expando on the node to avoid
+		// false lookups in the cache for entries that no longer exist
+		} else if ( isNode ) {
+			// IE does not allow us to delete expando properties from nodes,
+			// nor does it have a removeAttribute function on Document nodes;
+			// we must handle all of these cases
+			if ( jQuery.support.deleteExpando ) {
+				delete elem[ jQuery.expando ];
+			} else if ( elem.removeAttribute ) {
+				elem.removeAttribute( jQuery.expando );
+			} else {
+				elem[ jQuery.expando ] = null;
+			}
+		}
+	},
+
+	// For internal use only.
+	_data: function( elem, name, data ) {
+		return jQuery.data( elem, name, data, true );
+	},
+
+	// 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 ) {
+		var data = null;
+
+		if ( typeof key === "undefined" ) {
+			if ( this.length ) {
+				data = jQuery.data( this[0] );
+
+				if ( this[0].nodeType === 1 ) {
+			    var attr = this[0].attributes, name;
+					for ( var i = 0, l = attr.length; i < l; i++ ) {
+						name = attr[i].name;
+
+						if ( name.indexOf( "data-" ) === 0 ) {
+							name = jQuery.camelCase( name.substring(5) );
+
+							dataAttr( this[0], name, data[ name ] );
+						}
+					}
+				}
+			}
+
+			return data;
+
+		} 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 ) {
+			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 );
+				data = dataAttr( this[0], key, data );
+			}
+
+			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 );
+		});
+	}
+});
+
+function dataAttr( elem, key, data ) {
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+
+		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+		data = elem.getAttribute( name );
+
+		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 ) {}
+
+			// Make sure we set the data so it isn't changed later
+			jQuery.data( elem, key, data );
+
+		} else {
+			data = undefined;
+		}
+	}
+
+	return data;
+}
+
+// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON
+// property to be considered empty objects; this property always exists in
+// order to make sure JSON.stringify does not expose internal metadata
+function isEmptyDataObject( obj ) {
+	for ( var name in obj ) {
+		if ( name !== "toJSON" ) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+	var deferDataKey = type + "defer",
+		queueDataKey = type + "queue",
+		markDataKey = type + "mark",
+		defer = jQuery.data( elem, deferDataKey, undefined, true );
+	if ( defer &&
+		( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) &&
+		( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) {
+		// Give room for hard-coded callbacks to fire first
+		// and eventually mark/queue something else on the element
+		setTimeout( function() {
+			if ( !jQuery.data( elem, queueDataKey, undefined, true ) &&
+				!jQuery.data( elem, markDataKey, undefined, true ) ) {
+				jQuery.removeData( elem, deferDataKey, true );
+				defer.resolve();
+			}
+		}, 0 );
+	}
+}
+
+jQuery.extend({
+
+	_mark: function( elem, type ) {
+		if ( elem ) {
+			type = (type || "fx") + "mark";
+			jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true );
+		}
+	},
+
+	_unmark: function( force, elem, type ) {
+		if ( force !== true ) {
+			type = elem;
+			elem = force;
+			force = false;
+		}
+		if ( elem ) {
+			type = type || "fx";
+			var key = type + "mark",
+				count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 );
+			if ( count ) {
+				jQuery.data( elem, key, count, true );
+			} else {
+				jQuery.removeData( elem, key, true );
+				handleQueueMarkDefer( elem, type, "mark" );
+			}
+		}
+	},
+
+	queue: function( elem, type, data ) {
+		if ( elem ) {
+			type = (type || "fx") + "queue";
+			var q = jQuery.data( elem, type, undefined, true );
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !q || jQuery.isArray(data) ) {
+					q = jQuery.data( elem, type, jQuery.makeArray(data), true );
+				} else {
+					q.push( data );
+				}
+			}
+			return q || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			fn = queue.shift(),
+			defer;
+
+		// 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);
+			});
+		}
+
+		if ( !queue.length ) {
+			jQuery.removeData( elem, type + "queue", true );
+			handleQueueMarkDefer( elem, type, "queue" );
+		}
+	}
+});
+
+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() {
+			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", [] );
+	},
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, object ) {
+		if ( typeof type !== "string" ) {
+			object = type;
+			type = undefined;
+		}
+		type = type || "fx";
+		var defer = jQuery.Deferred(),
+			elements = this,
+			i = elements.length,
+			count = 1,
+			deferDataKey = type + "defer",
+			queueDataKey = type + "queue",
+			markDataKey = type + "mark",
+			tmp;
+		function resolve() {
+			if ( !( --count ) ) {
+				defer.resolveWith( elements, [ elements ] );
+			}
+		}
+		while( i-- ) {
+			if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+					( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+						jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+					jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {
+				count++;
+				tmp.done( resolve );
+			}
+		}
+		resolve();
+		return defer.promise();
+	}
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+	rspace = /\s+/,
+	rreturn = /\r/g,
+	rtype = /^(?:button|input)$/i,
+	rfocusable = /^(?:button|input|object|select|textarea)$/i,
+	rclickable = /^a(?:rea)?$/i,
+	rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+	nodeHook, boolHook;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, name, value, true, jQuery.attr );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	},
+	
+	prop: function( name, value ) {
+		return jQuery.access( this, name, value, true, jQuery.prop );
+	},
+	
+	removeProp: function( name ) {
+		name = jQuery.propFix[ name ] || name;
+		return this.each(function() {
+			// try/catch handles cases where IE balks (such as removing a property on window)
+			try {
+				this[ name ] = undefined;
+				delete this[ name ];
+			} catch( e ) {}
+		});
+	},
+
+	addClass: function( value ) {
+		var classNames, i, l, elem,
+			setClass, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( value && typeof value === "string" ) {
+			classNames = value.split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 ) {
+					if ( !elem.className && classNames.length === 1 ) {
+						elem.className = value;
+
+					} else {
+						setClass = " " + elem.className + " ";
+
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+								setClass += classNames[ c ] + " ";
+							}
+						}
+						elem.className = jQuery.trim( setClass );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classNames, i, l, elem, className, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( (value && typeof value === "string") || value === undefined ) {
+			classNames = (value || "").split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 && elem.className ) {
+					if ( value ) {
+						className = (" " + elem.className + " ").replace( rclass, " " );
+						for ( 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 ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, 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( rspace );
+
+				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].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		var hooks, ret,
+			elem = this[0];
+		
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ? 
+					// handle most common string cases
+					ret.replace(rreturn, "") : 
+					// handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return undefined;
+		}
+
+		var isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var self = jQuery(this), val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, self.val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map(val, function ( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				// 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;
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value,
+					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 );
+					}
+				}
+
+				// Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+				if ( one && !values.length && options.length ) {
+					return jQuery( options[ index ] ).val();
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var values = jQuery.makeArray( value );
+
+				jQuery(elem).find("option").each(function() {
+					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+				});
+
+				if ( !values.length ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	},
+
+	attrFn: {
+		val: true,
+		css: true,
+		html: true,
+		text: true,
+		data: true,
+		width: true,
+		height: true,
+		offset: true
+	},
+	
+	attrFix: {
+		// Always normalize to ensure hook usage
+		tabindex: "tabIndex"
+	},
+	
+	attr: function( elem, name, value, pass ) {
+		var nType = elem.nodeType;
+		
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return undefined;
+		}
+
+		if ( pass && name in jQuery.attrFn ) {
+			return jQuery( elem )[ name ]( value );
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( !("getAttribute" in elem) ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		var ret, hooks,
+			notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		// Normalize the name if needed
+		if ( notxml ) {
+			name = jQuery.attrFix[ name ] || name;
+
+			hooks = jQuery.attrHooks[ name ];
+
+			if ( !hooks ) {
+				// Use boolHook for boolean attributes
+				if ( rboolean.test( name ) ) {
+					hooks = boolHook;
+
+				// Use nodeHook if available( IE6/7 )
+				} else if ( nodeHook ) {
+					hooks = nodeHook;
+				}
+			}
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+				return undefined;
+
+			} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, "" + value );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+
+			ret = elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret === null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, name ) {
+		var propName;
+		if ( elem.nodeType === 1 ) {
+			name = jQuery.attrFix[ name ] || name;
+
+			jQuery.attr( elem, name, "" );
+			elem.removeAttribute( name );
+
+			// Set corresponding property to false for boolean attributes
+			if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) {
+				elem[ propName ] = false;
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+					jQuery.error( "type property can't be changed" );
+				} else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+					// Setting the type on a radio button after the value resets the value in IE6-9
+					// Reset value to it's default in case type is set after value
+					// This is for element creation
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		},
+		// Use the value property for back compat
+		// Use the nodeHook for button elements in IE6/7 (#1954)
+		value: {
+			get: function( elem, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.get( elem, name );
+				}
+				return name in elem ?
+					elem.value :
+					null;
+			},
+			set: function( elem, value, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.set( elem, value, name );
+				}
+				// Does not return so that setAttribute is also used
+				elem.value = value;
+			}
+		}
+	},
+
+	propFix: {
+		tabindex: "tabIndex",
+		readonly: "readOnly",
+		"for": "htmlFor",
+		"class": "className",
+		maxlength: "maxLength",
+		cellspacing: "cellSpacing",
+		cellpadding: "cellPadding",
+		rowspan: "rowSpan",
+		colspan: "colSpan",
+		usemap: "useMap",
+		frameborder: "frameBorder",
+		contenteditable: "contentEditable"
+	},
+	
+	prop: function( elem, name, value ) {
+		var nType = elem.nodeType;
+
+		// don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return undefined;
+		}
+
+		var ret, hooks,
+			notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				return (elem[ name ] = value);
+			}
+
+		} else {
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+				return ret;
+
+			} else {
+				return elem[ name ];
+			}
+		}
+	},
+	
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				var attributeNode = elem.getAttributeNode("tabindex");
+
+				return attributeNode && attributeNode.specified ?
+					parseInt( attributeNode.value, 10 ) :
+					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+						0 :
+						undefined;
+			}
+		}
+	}
+});
+
+// Add the tabindex propHook to attrHooks for back-compat
+jQuery.attrHooks.tabIndex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+	get: function( elem, name ) {
+		// Align boolean attributes with corresponding properties
+		// Fall back to attribute presence where some booleans are not supported
+		var attrNode;
+		return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ?
+			name.toLowerCase() :
+			undefined;
+	},
+	set: function( elem, value, name ) {
+		var propName;
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			// value is true since we know at this point it's type boolean and not false
+			// Set boolean attributes to the same name and set the DOM property
+			propName = jQuery.propFix[ name ] || name;
+			if ( propName in elem ) {
+				// Only set the IDL specifically if it already exists on the element
+				elem[ propName ] = true;
+			}
+
+			elem.setAttribute( name, name.toLowerCase() );
+		}
+		return name;
+	}
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !jQuery.support.getSetAttribute ) {
+	
+	// Use this for any attribute in IE6/7
+	// This fixes almost every IE6/7 issue
+	nodeHook = jQuery.valHooks.button = {
+		get: function( elem, name ) {
+			var ret;
+			ret = elem.getAttributeNode( name );
+			// Return undefined if nodeValue is empty string
+			return ret && ret.nodeValue !== "" ?
+				ret.nodeValue :
+				undefined;
+		},
+		set: function( elem, value, name ) {
+			// Set the existing or create a new attribute node
+			var ret = elem.getAttributeNode( name );
+			if ( !ret ) {
+				ret = document.createAttribute( name );
+				elem.setAttributeNode( ret );
+			}
+			return (ret.nodeValue = value + "");
+		}
+	};
+
+	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
+	// This is for removals
+	jQuery.each([ "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			set: function( elem, value ) {
+				if ( value === "" ) {
+					elem.setAttribute( name, "auto" );
+					return value;
+				}
+			}
+		});
+	});
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+	jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			get: function( elem ) {
+				var ret = elem.getAttribute( name, 2 );
+				return ret === null ? undefined : ret;
+			}
+		});
+	});
+}
+
+if ( !jQuery.support.style ) {
+	jQuery.attrHooks.style = {
+		get: function( elem ) {
+			// Return undefined in the case of empty string
+			// Normalize to lowercase since IE uppercases css property names
+			return elem.style.cssText.toLowerCase() || undefined;
+		},
+		set: function( elem, value ) {
+			return (elem.style.cssText = "" + value);
+		}
+	};
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+	jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+
+			if ( parent ) {
+				parent.selectedIndex;
+
+				// Make sure that it also works with optgroups, see #5701
+				if ( parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+			}
+			return null;
+		}
+	});
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+	jQuery.each([ "radio", "checkbox" ], function() {
+		jQuery.valHooks[ this ] = {
+			get: function( elem ) {
+				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+				return elem.getAttribute("value") === null ? "on" : elem.value;
+			}
+		};
+	});
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0);
+			}
+		}
+	});
+});
+
+
+
+
+var rnamespaces = /\.(.*)$/,
+	rformElems = /^(?:textarea|input|select)$/i,
+	rperiod = /\./g,
+	rspaces = / /g,
+	rescape = /[^\w\s.|`]/g,
+	fcleanup = function( nm ) {
+		return nm.replace(rescape, "\\$&");
+	};
+
+/*
+ * 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;
+		}
+
+		if ( handler === false ) {
+			handler = returnFalse;
+		} else if ( !handler ) {
+			// Fixes bug #7229. Fix recommended by jdalton
+			return;
+		}
+
+		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;
+		}
+
+		var events = elemData.events,
+			eventHandle = elemData.handle;
+
+		if ( !events ) {
+			elemData.events = events = {};
+		}
+
+		if ( !eventHandle ) {
+			elemData.handle = eventHandle = function( e ) {
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+					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 event optimization
+			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,
+			elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+			events = elemData && elemData.events;
+
+		if ( !elemData || !events ) {
+			return;
+		}
+
+		// 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 ( jQuery.isEmptyObject( elemData ) ) {
+				jQuery.removeData( elem, undefined, true );
+			}
+		}
+	},
+	
+	// Events that are safe to short-circuit if no handlers are attached.
+	// Native DOM events should not be added, they may have inline handlers.
+	customEvent: {
+		"getData": true,
+		"setData": true,
+		"changeData": true
+	},
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+		// Event object or event type
+		var type = event.type || event,
+			namespaces = [],
+			exclusive;
+
+		if ( type.indexOf("!") >= 0 ) {
+			// Exclusive events trigger only for the exact event (no namespaces)
+			type = type.slice(0, -1);
+			exclusive = true;
+		}
+
+		if ( type.indexOf(".") >= 0 ) {
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split(".");
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+
+		if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+			// No jQuery handlers for this event type, and it can't have inline handlers
+			return;
+		}
+
+		// Caller can pass in an Event, Object, or just an event type string
+		event = typeof event === "object" ?
+			// jQuery.Event object
+			event[ jQuery.expando ] ? event :
+			// Object literal
+			new jQuery.Event( type, event ) :
+			// Just the event type (string)
+			new jQuery.Event( type );
+
+		event.type = type;
+		event.exclusive = exclusive;
+		event.namespace = namespaces.join(".");
+		event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)");
+		
+		// triggerHandler() and global events don't bubble or run the default action
+		if ( onlyHandlers || !elem ) {
+			event.preventDefault();
+			event.stopPropagation();
+		}
+
+		// Handle a global trigger
+		if ( !elem ) {
+			// TODO: Stop taunting the data cache; remove global events and always attach to document
+			jQuery.each( jQuery.cache, function() {
+				// internalKey variable is just used to make it easier to find
+				// and potentially change this stuff later; currently it just
+				// points to jQuery.expando
+				var internalKey = jQuery.expando,
+					internalCache = this[ internalKey ];
+				if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
+					jQuery.event.trigger( event, data, internalCache.handle.elem );
+				}
+			});
+			return;
+		}
+
+		// Don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		event.target = elem;
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data != null ? jQuery.makeArray( data ) : [];
+		data.unshift( event );
+
+		var cur = elem,
+			// IE doesn't like method names with a colon (#3533, #8272)
+			ontype = type.indexOf(":") < 0 ? "on" + type : "";
+
+		// Fire event on the current element, then bubble up the DOM tree
+		do {
+			var handle = jQuery._data( cur, "handle" );
+
+			event.currentTarget = cur;
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+
+			// Trigger an inline bound script
+			if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) {
+				event.result = false;
+				event.preventDefault();
+			}
+
+			// Bubble up to document, then to window
+			cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window;
+		} while ( cur && !event.isPropagationStopped() );
+
+		// If nobody prevented the default action, do it now
+		if ( !event.isDefaultPrevented() ) {
+			var old,
+				special = jQuery.event.special[ type ] || {};
+
+			if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) &&
+				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Can't use an .isFunction)() check here because IE6/7 fails that test.
+				// IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch.
+				try {
+					if ( ontype && elem[ type ] ) {
+						// Don't re-trigger an onFOO event when we call its FOO() method
+						old = elem[ ontype ];
+
+						if ( old ) {
+							elem[ ontype ] = null;
+						}
+
+						jQuery.event.triggered = type;
+						elem[ type ]();
+					}
+				} catch ( ieError ) {}
+
+				if ( old ) {
+					elem[ ontype ] = old;
+				}
+
+				jQuery.event.triggered = undefined;
+			}
+		}
+		
+		return event.result;
+	},
+
+	handle: function( event ) {
+		event = jQuery.event.fix( event || window.event );
+		// Snapshot the handlers list since a called handler may add/remove events.
+		var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0),
+			run_all = !event.exclusive && !event.namespace,
+			args = Array.prototype.slice.call( arguments, 0 );
+
+		// Use the fix-ed Event rather than the (read-only) native event
+		args[0] = event;
+		event.currentTarget = this;
+
+		for ( var j = 0, l = handlers.length; j < l; j++ ) {
+			var handleObj = handlers[ j ];
+
+			// Triggered event must 1) be non-exclusive and have no namespace, or
+			// 2) have namespace(s) a subset or equal to those in the bound event.
+			if ( run_all || event.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 ) {
+			// Fixes #1925 where srcElement might not be defined either
+			event.target = event.srcElement || document;
+		}
+
+		// 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 eventDocument = event.target.ownerDocument || document,
+				doc = eventDocument.documentElement,
+				body = eventDocument.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, props ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !this.preventDefault ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
+			src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// 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 related = event.relatedTarget,
+		inside = false,
+		eventType = event.type;
+
+	event.type = event.data;
+
+	if ( related !== this ) {
+
+		if ( related ) {
+			inside = jQuery.contains( this, related );
+		}
+
+		if ( !inside ) {
+
+			jQuery.event.handle.apply( this, arguments );
+
+			event.type = eventType;
+		}
+	}
+},
+
+// 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 ( !jQuery.nodeName( this, "form" ) ) {
+				jQuery.event.add(this, "click.specialSubmit", function( e ) {
+					// Avoid triggering error on non-existent type attribute in IE VML (#7071)
+					var elem = e.target,
+						type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : "";
+
+					if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+						trigger( "submit", this, arguments );
+					}
+				});
+
+				jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+					var elem = e.target,
+						type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : "";
+
+					if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+						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 = jQuery.nodeName( elem, "input" ) ? 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 ( jQuery.nodeName( elem, "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;
+			jQuery.event.trigger( e, arguments[1], elem );
+		}
+	};
+
+	jQuery.event.special.change = {
+		filters: {
+			focusout: testChange,
+
+			beforedeactivate: testChange,
+
+			click: function( e ) {
+				var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : "";
+
+				if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) {
+					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 = jQuery.nodeName( elem, "input" ) ? elem.type : "";
+
+				if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) ||
+					(e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+					type === "select-multiple" ) {
+					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 ) {
+	// Piggyback on a donor event to simulate a different one.
+	// Fake originalEvent to avoid donor's stopPropagation, but if the
+	// simulated event prevents default then we do the same on the donor.
+	// Don't pass args or remember liveFired; they apply to the donor event.
+	var event = jQuery.extend( {}, args[ 0 ] );
+	event.type = type;
+	event.originalEvent = {};
+	event.liveFired = undefined;
+	jQuery.event.handle.call( elem, event );
+	if ( event.isDefaultPrevented() ) {
+		args[ 0 ].preventDefault();
+	}
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0;
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
+			}
+		};
+
+		function handler( donor ) {
+			// Donor event is always a native one; fix it and switch its type.
+			// Let focusin/out handler cancel the donor focus/blur event.
+			var e = jQuery.event.fix( donor );
+			e.type = fix;
+			e.originalEvent = {};
+			jQuery.event.trigger( e, null, e.target );
+			if ( e.isDefaultPrevented() ) {
+				donor.preventDefault();
+			}
+		}
+	});
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+	jQuery.fn[ name ] = function( type, data, fn ) {
+		var handler;
+
+		// Handle object literals
+		if ( typeof type === "object" ) {
+			for ( var key in type ) {
+				this[ name ](key, data, type[key], fn);
+			}
+			return this;
+		}
+
+		if ( arguments.length === 2 || data === false ) {
+			fn = data;
+			data = undefined;
+		}
+
+		if ( name === "one" ) {
+			handler = function( event ) {
+				jQuery( this ).unbind( event, handler );
+				return fn.apply( this, arguments );
+			};
+			handler.guid = fn.guid || jQuery.guid++;
+		} else {
+			handler = 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] ) {
+			return jQuery.event.trigger( type, data, this[0], true );
+		}
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments,
+			guid = fn.guid || jQuery.guid++,
+			i = 0,
+			toggler = 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;
+			};
+
+		// link all the functions, so any of them can unbind this click handler
+		toggler.guid = guid;
+		while ( i < args.length ) {
+			args[ i++ ].guid = guid;
+		}
+
+		return this.click( toggler );
+	},
+
+	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 ( name === "die" && !types &&
+					origSelector && origSelector.charAt(0) === "." ) {
+
+			context.unbind( origSelector );
+
+			return this;
+		}
+
+		if ( data === false || jQuery.isFunction( data ) ) {
+			fn = data || returnFalse;
+			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 ( liveMap[ type ] ) {
+				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, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
+		elems = [],
+		selectors = [],
+		events = jQuery._data( this, "events" );
+
+	// Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
+	if ( event.liveFired === this || !events || !events.live || event.target.disabled || 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 )) && !close.elem.disabled ) {
+				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];
+
+					// Make sure not to accidentally match a child element with the same selector
+					if ( related && jQuery.contains( elem, related ) ) {
+						related = elem;
+					}
+				}
+
+				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;
+			}
+			if ( event.isImmediatePropagationStopped() ) {
+				break;
+			}
+		}
+	}
+
+	return stop;
+}
+
+function liveConvert( type, selector ) {
+	return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&");
+}
+
+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;
+	}
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2011, 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,
+	rBackslash = /\\/g,
+	rNonWord = /\W/;
+
+// 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 m, set, checkSet, extra, ret, cur, pop, i,
+		prune = true,
+		contextXML = Sizzle.isXML( context ),
+		parts = [],
+		soFar = selector;
+	
+	// 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 match,
+			type = Expr.order[i];
+		
+		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( rBackslash, "" );
+				set = Expr.find[ type ]( match, context, isXML );
+
+				if ( set != null ) {
+					expr = expr.replace( Expr.match[ type ], "" );
+					break;
+				}
+			}
+		}
+	}
+
+	if ( !set ) {
+		set = typeof context.getElementsByTagName !== "undefined" ?
+			context.getElementsByTagName( "*" ) :
+			[];
+	}
+
+	return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+	var match, anyFound,
+		old = expr,
+		result = [],
+		curLoop = set,
+		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 found, item,
+					filter = Expr.filter[ type ],
+					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|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+		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" );
+		},
+		type: function( elem ) {
+			return elem.getAttribute( "type" );
+		}
+	},
+
+	relative: {
+		"+": function(checkSet, part){
+			var isPartStr = typeof part === "string",
+				isTag = isPartStr && !rNonWord.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 elem,
+				isPartStr = typeof part === "string",
+				i = 0,
+				l = checkSet.length;
+
+			if ( isPartStr && !rNonWord.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 nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+		},
+
+		"~": function( checkSet, part, isXML ) {
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.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 ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( match[1] );
+			}
+		}
+	},
+	preFilter: {
+		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+			match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+			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\r]/g, " ").indexOf(match) >= 0) ) {
+						if ( !inplace ) {
+							result.push( elem );
+						}
+
+					} else if ( inplace ) {
+						curLoop[i] = false;
+					}
+				}
+			}
+
+			return false;
+		},
+
+		ID: function( match ) {
+			return match[1].replace( rBackslash, "" );
+		},
+
+		TAG: function( match, curLoop ) {
+			return match[1].replace( rBackslash, "" ).toLowerCase();
+		},
+
+		CHILD: function( match ) {
+			if ( match[1] === "nth" ) {
+				if ( !match[2] ) {
+					Sizzle.error( match[0] );
+				}
+
+				match[2] = match[2].replace(/^\+|\s*/g, '');
+
+				// 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;
+			}
+			else if ( match[2] ) {
+				Sizzle.error( match[0] );
+			}
+
+			// TODO: Move to normal caching system
+			match[0] = done++;
+
+			return match;
+		},
+
+		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+			var name = match[1] = match[1].replace( rBackslash, "" );
+			
+			if ( !isXML && Expr.attrMap[name] ) {
+				match[1] = Expr.attrMap[name];
+			}
+
+			// Handle if an un-quoted value was used
+			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+			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
+			if ( elem.parentNode ) {
+				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 ) {
+			var attr = elem.getAttribute( "type" ), type = elem.type;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 
+			// use getAttribute instead to test this case
+			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+		},
+
+		radio: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+		},
+
+		checkbox: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+		},
+
+		file: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+		},
+
+		password: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+		},
+
+		submit: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "submit" === elem.type;
+		},
+
+		image: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+		},
+
+		reset: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "reset" === elem.type;
+		},
+
+		button: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && "button" === elem.type || name === "button";
+		},
+
+		input: function( elem ) {
+			return (/input|select|textarea|button/i).test( elem.nodeName );
+		},
+
+		focus: function( elem ) {
+			return elem === elem.ownerDocument.activeElement;
+		}
+	},
+	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( 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 i = 0,
+			ret = results || [];
+
+		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 ) {
+		// The nodes are identical, we can exit early
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// Fallback to using sourceIndex (in IE) if it's available on both nodes
+		} else if ( a.sourceIndex && b.sourceIndex ) {
+			return a.sourceIndex - b.sourceIndex;
+		}
+
+		var al, bl,
+			ap = [],
+			bp = [],
+			aup = a.parentNode,
+			bup = b.parentNode,
+			cur = aup;
+
+		// If the nodes are siblings (or identical) we can do a quick check
+		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(),
+		root = document.documentElement;
+
+	form.innerHTML = "<a name='" + id + "'/>";
+
+	// Inject it into the root element, check its status, and remove it quickly
+	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 );
+
+	// release memory in IE
+	root = form = null;
+})();
+
+(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 );
+		};
+	}
+
+	// release memory in IE
+	div = null;
+})();
+
+if ( document.querySelectorAll ) {
+	(function(){
+		var oldSizzle = Sizzle,
+			div = document.createElement("div"),
+			id = "__sizzle__";
+
+		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) ) {
+				// See if we find a selector to speed up
+				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+				
+				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+					// Speed-up: Sizzle("TAG")
+					if ( match[1] ) {
+						return makeArray( context.getElementsByTagName( query ), extra );
+					
+					// Speed-up: Sizzle(".CLASS")
+					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+						return makeArray( context.getElementsByClassName( match[2] ), extra );
+					}
+				}
+				
+				if ( context.nodeType === 9 ) {
+					// Speed-up: Sizzle("body")
+					// The body element only exists once, optimize finding it
+					if ( query === "body" && context.body ) {
+						return makeArray( [ context.body ], extra );
+						
+					// Speed-up: Sizzle("#ID")
+					} else if ( match && match[3] ) {
+						var elem = context.getElementById( match[3] );
+
+						// 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[3] ) {
+								return makeArray( [ elem ], extra );
+							}
+							
+						} else {
+							return makeArray( [], extra );
+						}
+					}
+					
+					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 oldContext = context,
+						old = context.getAttribute( "id" ),
+						nid = old || id,
+						hasParent = context.parentNode,
+						relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+					if ( !old ) {
+						context.setAttribute( "id", nid );
+					} else {
+						nid = nid.replace( /'/g, "\\$&" );
+					}
+					if ( relativeHierarchySelector && hasParent ) {
+						context = context.parentNode;
+					}
+
+					try {
+						if ( !relativeHierarchySelector || hasParent ) {
+							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+						}
+
+					} catch(pseudoError) {
+					} finally {
+						if ( !old ) {
+							oldContext.removeAttribute( "id" );
+						}
+					}
+				}
+			}
+		
+			return oldSizzle(query, context, extra, seed);
+		};
+
+		for ( var prop in oldSizzle ) {
+			Sizzle[ prop ] = oldSizzle[ prop ];
+		}
+
+		// release memory in IE
+		div = null;
+	})();
+}
+
+(function(){
+	var html = document.documentElement,
+		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+	if ( matches ) {
+		// Check to see if it's possible to do matchesSelector
+		// on a disconnected node (IE 9 fails this)
+		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+			pseudoWorks = false;
+
+		try {
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( document.documentElement, "[test!='']:sizzle" );
+	
+		} catch( pseudoError ) {
+			pseudoWorks = true;
+		}
+
+		Sizzle.matchesSelector = function( node, expr ) {
+			// Make sure that attribute selectors are quoted
+			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+			if ( !Sizzle.isXML( node ) ) {
+				try { 
+					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+						var ret = matches.call( node, expr );
+
+						// IE 9's matchesSelector returns false on disconnected nodes
+						if ( ret || !disconnectedMatch ||
+								// As well, disconnected nodes are said to be in a document
+								// fragment in IE 9, so check for that
+								node.document && node.document.nodeType !== 11 ) {
+							return ret;
+						}
+					}
+				} 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]);
+		}
+	};
+
+	// release memory in IE
+	div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+
+			elem = elem[dir];
+
+			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 ) {
+			var match = false;
+			
+			elem = elem[dir];
+
+			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;
+		}
+	}
+}
+
+if ( document.documentElement.contains ) {
+	Sizzle.contains = function( a, b ) {
+		return a !== b && (a.contains ? a.contains(b) : true);
+	};
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+	Sizzle.contains = function( a, b ) {
+		return !!(a.compareDocumentPosition(b) & 16);
+	};
+
+} else {
+	Sizzle.contains = function() {
+		return false;
+	};
+}
+
+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 match,
+		tmpSet = [],
+		later = "",
+		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,
+	// methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var self = this,
+			i, l;
+
+		if ( typeof selector !== "string" ) {
+			return jQuery( selector ).filter(function() {
+				for ( i = 0, l = self.length; i < l; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			});
+		}
+
+		var ret = this.pushStack( "", "find", selector ),
+			length, n, r;
+
+		for ( 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 ( n = length; n < ret.length; n++ ) {
+					for ( 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 && ( typeof selector === "string" ?
+			jQuery.filter( selector, this ).length > 0 :
+			this.filter( selector ).length > 0 );
+	},
+
+	closest: function( selectors, context ) {
+		var ret = [], i, l, cur = this[0];
+		
+		// Array
+		if ( jQuery.isArray( selectors ) ) {
+			var match, selector,
+				matches = {},
+				level = 1;
+
+			if ( cur && selectors.length ) {
+				for ( i = 0, l = selectors.length; i < l; i++ ) {
+					selector = selectors[i];
+
+					if ( !matches[ selector ] ) {
+						matches[ selector ] = 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;
+		}
+
+		// String
+		var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		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 || cur.nodeType === 11 ) {
+						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 ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+		}
+
+		// index in selector
+		if ( typeof elem === "string" ) {
+			return jQuery.inArray( this[0], jQuery( elem ) );
+		}
+
+		// 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 ) :
+				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : 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 ),
+			// The variable 'args' was introduced in
+			// https://github.com/jquery/jquery/commit/52a0238
+			// to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
+			// http://code.google.com/p/v8/issues/detail?id=1050
+			args = slice.call(arguments);
+
+		if ( !runtil.test( name ) ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			ret = jQuery.filter( selector, ret );
+		}
+
+		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+		if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+			ret = ret.reverse();
+		}
+
+		return this.pushStack( ret, name, args.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 ) {
+
+	// Can't pass null or undefined to indexOf in Firefox 4
+	// Set to 0 to skip string check
+	qualifier = qualifier || 0;
+
+	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,
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /\/(java|ecma)script/i,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+	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( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		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.length ?
+				this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+				this;
+		}
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, table, callback ) {
+		var results, first, fragment, parent,
+			value = args[0],
+			scripts = [];
+
+		// 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, lastIndex = l - 1; i < l; i++ ) {
+					callback.call(
+						table ?
+							root(this[i], first) :
+							this[i],
+						// Make sure that we do not leak memory by inadvertently discarding
+						// the original fragment (which might have attached data) instead of
+						// using it; in addition, use the original fragment object for the last
+						// item instead of first because it can end up being emptied incorrectly
+						// in certain situations (Bug #8070).
+						// Fragments from the fragment cache must always be cloned and never used
+						// in place.
+						results.cacheable || (l > 1 && i < lastIndex) ?
+							jQuery.clone( fragment, true, 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( src, dest ) {
+
+	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+		return;
+	}
+
+	var internalKey = jQuery.expando,
+		oldData = jQuery.data( src ),
+		curData = jQuery.data( dest, oldData );
+
+	// Switch to use the internal data object, if it exists, for the next
+	// stage of data copying
+	if ( (oldData = oldData[ internalKey ]) ) {
+		var events = oldData.events;
+				curData = curData[ internalKey ] = jQuery.extend({}, oldData);
+
+		if ( events ) {
+			delete curData.handle;
+			curData.events = {};
+
+			for ( var type in events ) {
+				for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
+					jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
+				}
+			}
+		}
+	}
+}
+
+function cloneFixAttributes( src, dest ) {
+	var nodeName;
+
+	// We do not need to do anything for non-Elements
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// clearAttributes removes the attributes, which we don't want,
+	// but also removes the attachEvent events, which we *do* want
+	if ( dest.clearAttributes ) {
+		dest.clearAttributes();
+	}
+
+	// mergeAttributes, in contrast, only merges back on the
+	// original attributes, not the events
+	if ( dest.mergeAttributes ) {
+		dest.mergeAttributes( src );
+	}
+
+	nodeName = dest.nodeName.toLowerCase();
+
+	// IE6-8 fail to clone children inside object elements that use
+	// the proprietary classid attribute value (rather than the type
+	// attribute) to identify the type of content to display
+	if ( nodeName === "object" ) {
+		dest.outerHTML = src.outerHTML;
+
+	} else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+		// IE6-8 fails to persist the checked state of a cloned checkbox
+		// or radio button. Worse, IE6-7 fail to give the cloned element
+		// a checked appearance if the defaultChecked value isn't also set
+		if ( src.checked ) {
+			dest.defaultChecked = dest.checked = src.checked;
+		}
+
+		// IE6-7 get confused and end up setting the value of a cloned
+		// checkbox/radio button to an empty string instead of "on"
+		if ( dest.value !== src.value ) {
+			dest.value = src.value;
+		}
+
+	// IE6-8 fails to return the selected option to the default selected
+	// state when cloning options
+	} else if ( nodeName === "option" ) {
+		dest.selected = src.defaultSelected;
+
+	// IE6-8 fails to set the defaultValue to the correct value when
+	// cloning other types of input fields
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+
+	// Event data gets referenced instead of copied if the expando
+	// gets copied too
+	dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+	var fragment, cacheable, cacheresults, doc;
+
+  // nodes may contain either an explicit document object,
+  // a jQuery collection or context object.
+  // If nodes[0] contains a valid object to assign to doc
+  if ( nodes && nodes[0] ) {
+    doc = nodes[0].ownerDocument || nodes[0];
+  }
+
+  // Ensure that an attr object doesn't incorrectly stand in as a document object
+	// Chrome and Firefox seem to allow this to occur and will throw exception
+	// Fixes #8950
+	if ( !doc.createDocumentFragment ) {
+		doc = document;
+	}
+
+	// Only cache "small" (1/2 KB) HTML 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 &&
+		args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+		cacheable = true;
+
+		cacheresults = jQuery.fragments[ args[0] ];
+		if ( cacheresults && 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 );
+		}
+	};
+});
+
+function getAll( elem ) {
+	if ( "getElementsByTagName" in elem ) {
+		return elem.getElementsByTagName( "*" );
+
+	} else if ( "querySelectorAll" in elem ) {
+		return elem.querySelectorAll( "*" );
+
+	} else {
+		return [];
+	}
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+	if ( elem.type === "checkbox" || elem.type === "radio" ) {
+		elem.defaultChecked = elem.checked;
+	}
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+	if ( jQuery.nodeName( elem, "input" ) ) {
+		fixDefaultChecked( elem );
+	} else if ( "getElementsByTagName" in elem ) {
+		jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+	}
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var clone = elem.cloneNode(true),
+				srcElements,
+				destElements,
+				i;
+
+		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+			// IE copies events bound via attachEvent when using cloneNode.
+			// Calling detachEvent on the clone will also remove the events
+			// from the original. In order to get around this, we use some
+			// proprietary methods to clear the events. Thanks to MooTools
+			// guys for this hotness.
+
+			cloneFixAttributes( elem, clone );
+
+			// Using Sizzle here is crazy slow, so we use getElementsByTagName
+			// instead
+			srcElements = getAll( elem );
+			destElements = getAll( clone );
+
+			// Weird iteration because IE will replace the length property
+			// with an element if you are cloning the body and one of the
+			// elements on the page has a name or id of "length"
+			for ( i = 0; srcElements[i]; ++i ) {
+				// Ensure that the destination node is not null; Fixes #9587
+				if ( destElements[i] ) {
+					cloneFixAttributes( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			cloneCopyEvent( elem, clone );
+
+			if ( deepDataAndEvents ) {
+				srcElements = getAll( elem );
+				destElements = getAll( clone );
+
+				for ( i = 0; srcElements[i]; ++i ) {
+					cloneCopyEvent( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		srcElements = destElements = null;
+
+		// Return the cloned set
+		return clone;
+	},
+
+	clean: function( elems, context, fragment, scripts ) {
+		var checkScriptType;
+
+		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 = [], j;
+
+		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" ) {
+				if ( !rhtml.test( elem ) ) {
+					elem = context.createTextNode( elem );
+				} else {
+					// 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 ( 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;
+				}
+			}
+
+			// Resets defaultChecked for any radios and checkboxes
+			// about to be appended to the DOM in IE 6/7 (#8060)
+			var len;
+			if ( !jQuery.support.appendChecked ) {
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
+					for ( j = 0; j < len; j++ ) {
+						findInputs( elem[j] );
+					}
+				} else {
+					findInputs( elem );
+				}
+			}
+
+			if ( elem.nodeType ) {
+				ret.push( elem );
+			} else {
+				ret = jQuery.merge( ret, elem );
+			}
+		}
+
+		if ( fragment ) {
+			checkScriptType = function( elem ) {
+				return !elem.type || rscriptType.test( elem.type );
+			};
+			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 ) {
+						var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
+
+						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+					}
+					fragment.appendChild( ret[i] );
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	cleanData: function( elems ) {
+		var data, id, cache = jQuery.cache, internalKey = jQuery.expando, 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 ] && cache[ id ][ internalKey ];
+
+				if ( data && data.events ) {
+					for ( var type in data.events ) {
+						if ( special[ type ] ) {
+							jQuery.event.remove( elem, type );
+
+						// This is a shortcut to avoid jQuery.event.remove's overhead
+						} else {
+							jQuery.removeEvent( elem, type, data.handle );
+						}
+					}
+
+					// Null the DOM reference to avoid IE6/7/8 leak (#7054)
+					if ( data.handle ) {
+						data.handle.elem = null;
+					}
+				}
+
+				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 || "" ).replace( rcleanScript, "/*$0*/" ) );
+	}
+
+	if ( elem.parentNode ) {
+		elem.parentNode.removeChild( elem );
+	}
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity=([^)]*)/,
+	// fixed for IE9, see #8346
+	rupper = /([A-Z]|^ms)/g,
+	rnumpx = /^-?\d+(?:px)?$/i,
+	rnum = /^-?\d/,
+	rrelNum = /^([\-+])=([\-+.\de]+)/,
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssWidth = [ "Left", "Right" ],
+	cssHeight = [ "Top", "Bottom" ],
+	curCSS,
+
+	getComputedStyle,
+	currentStyle;
+
+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: {
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// 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, type, 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 ) {
+			type = typeof value;
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( value == null || type === "number" && isNaN( value ) ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "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 ) {
+		var ret, hooks;
+
+		// Make sure that we're working with the right name
+		name = jQuery.camelCase( name );
+		hooks = jQuery.cssHooks[ name ];
+		name = jQuery.cssProps[ name ] || name;
+
+		// cssFloat needs a special treatment
+		if ( name === "cssFloat" ) {
+			name = "float";
+		}
+
+		// 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 );
+		}
+	},
+
+	// 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 ];
+		}
+	}
+});
+
+// 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 ) {
+					return getWH( elem, name, extra );
+				} else {
+					jQuery.swap( elem, cssShow, function() {
+						val = getWH( elem, name, extra );
+					});
+				}
+
+				return val;
+			}
+		},
+
+		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,
+				currentStyle = elem.currentStyle,
+				opacity = jQuery.isNaN( value ) ? "" : "alpha(opacity=" + value * 100 + ")",
+				filter = currentStyle && currentStyle.filter || style.filter || "";
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+			if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+				// if "filter:" is present at all, clearType is disabled, we want to avoid this
+				// style.removeAttribute is IE Only, but so apparently is this code path...
+				style.removeAttribute( "filter" );
+
+				// if there there is no filter style applied in a css rule, we are done
+				if ( currentStyle && !currentStyle.filter ) {
+					return;
+				}
+			}
+
+			// otherwise, set new filter values
+			style.filter = ralpha.test( filter ) ?
+				filter.replace( ralpha, opacity ) :
+				filter + " " + opacity;
+		}
+	};
+}
+
+jQuery(function() {
+	// This hook cannot be added until DOM ready because the support test
+	// for it is not run until after DOM ready
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// Work around by temporarily setting element display to inline-block
+				var ret;
+				jQuery.swap( elem, { "display": "inline-block" }, function() {
+					if ( computed ) {
+						ret = curCSS( elem, "margin-right", "marginRight" );
+					} else {
+						ret = elem.style.marginRight;
+					}
+				});
+				return ret;
+			}
+		};
+	}
+});
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+	getComputedStyle = function( elem, 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;
+	};
+}
+
+if ( document.documentElement.currentStyle ) {
+	currentStyle = function( elem, name ) {
+		var left,
+			ret = elem.currentStyle && elem.currentStyle[ name ],
+			rsLeft = elem.runtimeStyle && elem.runtimeStyle[ 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;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : (ret || 0);
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+
+	// Start with offset property
+	var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		which = name === "width" ? cssWidth : cssHeight;
+
+	if ( val > 0 ) {
+		if ( extra !== "border" ) {
+			jQuery.each( which, function() {
+				if ( !extra ) {
+					val -= parseFloat( jQuery.css( elem, "padding" + this ) ) || 0;
+				}
+				if ( extra === "margin" ) {
+					val += parseFloat( jQuery.css( elem, extra + this ) ) || 0;
+				} else {
+					val -= parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0;
+				}
+			});
+		}
+
+		return val + "px";
+	}
+
+	// Fall back to computed then uncomputed css if necessary
+	val = curCSS( elem, name, name );
+	if ( val < 0 || val == null ) {
+		val = elem.style[ name ] || 0;
+	}
+	// Normalize "", auto, and prepare for extra
+	val = parseFloat( val ) || 0;
+
+	// Add padding, border, margin
+	if ( extra ) {
+		jQuery.each( which, function() {
+			val += parseFloat( jQuery.css( elem, "padding" + this ) ) || 0;
+			if ( extra !== "padding" ) {
+				val += parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0;
+			}
+			if ( extra === "margin" ) {
+				val += parseFloat( jQuery.css( elem, extra + this ) ) || 0;
+			}
+		});
+	}
+
+	return val + "px";
+}
+
+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 r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rhash = /#.*$/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+	rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rquery = /\?/,
+	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+	rselectTextarea = /^(?:select|textarea)/i,
+	rspacesAjax = /\s+/,
+	rts = /([?&])_=[^&]*/,
+	rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Document location
+	ajaxLocation,
+
+	// Document location segments
+	ajaxLocParts,
+	
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		if ( jQuery.isFunction( func ) ) {
+			var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+				i = 0,
+				length = dataTypes.length,
+				dataType,
+				list,
+				placeBefore;
+
+			// For each dataType in the dataTypeExpression
+			for(; i < length; i++ ) {
+				dataType = dataTypes[ i ];
+				// We control if we're asked to add before
+				// any existing element
+				placeBefore = /^\+/.test( dataType );
+				if ( placeBefore ) {
+					dataType = dataType.substr( 1 ) || "*";
+				}
+				list = structure[ dataType ] = structure[ dataType ] || [];
+				// then we add to the structure accordingly
+				list[ placeBefore ? "unshift" : "push" ]( func );
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+		dataType /* internal */, inspected /* internal */ ) {
+
+	dataType = dataType || options.dataTypes[ 0 ];
+	inspected = inspected || {};
+
+	inspected[ dataType ] = true;
+
+	var list = structure[ dataType ],
+		i = 0,
+		length = list ? list.length : 0,
+		executeOnly = ( structure === prefilters ),
+		selection;
+
+	for(; i < length && ( executeOnly || !selection ); i++ ) {
+		selection = list[ i ]( options, originalOptions, jqXHR );
+		// If we got redirected to another dataType
+		// we try there if executing only and not done already
+		if ( typeof selection === "string" ) {
+			if ( !executeOnly || inspected[ selection ] ) {
+				selection = undefined;
+			} else {
+				options.dataTypes.unshift( selection );
+				selection = inspectPrefiltersOrTransports(
+						structure, options, originalOptions, jqXHR, selection, inspected );
+			}
+		}
+	}
+	// If we're only executing or nothing was selected
+	// we try the catchall dataType if not done already
+	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+		selection = inspectPrefiltersOrTransports(
+				structure, options, originalOptions, jqXHR, "*", inspected );
+	}
+	// unnecessary when only executing (prefilters)
+	// but it'll be ignored by the caller in that case
+	return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+	for( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+}
+
+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 = undefined;
+
+			// 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 callback (responseText is used internally)
+			complete: function( jqXHR, status, responseText ) {
+				// Store the response as specified by the jqXHR object
+				responseText = jqXHR.responseText;
+				// If successful, inject the HTML into all the matched elements
+				if ( jqXHR.isResolved() ) {
+					// #4825: Get the actual response in case
+					// a dataFilter is present in ajaxSettings
+					jqXHR.done(function( r ) {
+						responseText = r;
+					});
+					// 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(responseText.replace(rscript, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						responseText );
+				}
+
+				if ( callback ) {
+					self.each( callback, [ responseText, status, jqXHR ] );
+				}
+			}
+		});
+
+		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.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).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.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			type: method,
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	};
+});
+
+jQuery.extend({
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		if ( settings ) {
+			// Building a settings object
+			ajaxExtend( target, jQuery.ajaxSettings );
+		} else {
+			// Extending ajaxSettings
+			settings = target;
+			target = jQuery.ajaxSettings;
+		}
+		ajaxExtend( target, settings );
+		return target;
+	},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			text: "text/plain",
+			json: "application/json, text/javascript",
+			"*": allTypes
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText"
+		},
+
+		// List of data converters
+		// 1) key format is "source_type destination_type" (a single space in-between)
+		// 2) the catchall symbol "*" can be used for source_type
+		converters: {
+
+			// Convert anything to text
+			"* text": window.String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			context: true,
+			url: true
+		}
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events
+			// It's the callbackContext if one was provided in the options
+			// and if it's a DOM node or a jQuery collection
+			globalEventContext = callbackContext !== s &&
+				( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+						jQuery( callbackContext ) : jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery._Deferred(),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// ifModified key
+			ifModifiedKey,
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// transport
+			transport,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// The jqXHR state
+			state = 0,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Fake xhr
+			jqXHR = {
+
+				readyState: 0,
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( !state ) {
+						var lname = name.toLowerCase();
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match === undefined ? null : match;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					statusText = statusText || "abort";
+					if ( transport ) {
+						transport.abort( statusText );
+					}
+					done( 0, statusText );
+					return this;
+				}
+			};
+
+		// Callback for when everything is done
+		// It is defined here because jslint complains if it is declared
+		// at the end of the function (which would be more logical and readable)
+		function done( status, nativeStatusText, responses, headers ) {
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			var isSuccess,
+				success,
+				error,
+				statusText = nativeStatusText,
+				response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+				lastModified,
+				etag;
+
+			// If successful, handle type chaining
+			if ( status >= 200 && status < 300 || status === 304 ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+
+					if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+						jQuery.lastModified[ ifModifiedKey ] = lastModified;
+					}
+					if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+						jQuery.etag[ ifModifiedKey ] = etag;
+					}
+				}
+
+				// If not modified
+				if ( status === 304 ) {
+
+					statusText = "notmodified";
+					isSuccess = true;
+
+				// If we have data
+				} else {
+
+					try {
+						success = ajaxConvert( s, response );
+						statusText = "success";
+						isSuccess = true;
+					} catch(e) {
+						// We have a parsererror
+						statusText = "parsererror";
+						error = e;
+					}
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if( !statusText || status ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+						[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		// Attach deferreds
+		deferred.promise( jqXHR );
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+		jqXHR.complete = completeDeferred.done;
+
+		// Status-dependent callbacks
+		jqXHR.statusCode = function( map ) {
+			if ( map ) {
+				var tmp;
+				if ( state < 2 ) {
+					for( tmp in map ) {
+						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+					}
+				} else {
+					tmp = map[ jqXHR.status ];
+					jqXHR.then( tmp, tmp );
+				}
+			}
+			return this;
+		};
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+		// Determine if a cross-domain request is in order
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefiler, stop there
+		if ( state === 2 ) {
+			return false;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Get ifModifiedKey before adding the anti-cache parameter
+			ifModifiedKey = s.url;
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+
+				var ts = jQuery.now(),
+					// try replacing _= if it is there
+					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 : "" );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			ifModifiedKey = ifModifiedKey || s.url;
+			if ( jQuery.lastModified[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+			}
+			if ( jQuery.etag[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+			}
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+				// Abort if not done already
+				jqXHR.abort();
+				return false;
+
+		}
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout( function(){
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch (e) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					jQuery.error( e );
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	// 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 && !jQuery.isPlainObject( a ) ) ) {
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			});
+
+		} else {
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( 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 ) ) {
+		// 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" ) {
+		// Serialize object item.
+		for ( var name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], 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: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var contents = s.contents,
+		dataTypes = s.dataTypes,
+		responseFields = s.responseFields,
+		ct,
+		type,
+		finalDataType,
+		firstDataType;
+
+	// Fill responseXXX fields
+	for( type in responseFields ) {
+		if ( type in responses ) {
+			jqXHR[ responseFields[type] ] = responses[ type ];
+		}
+	}
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+	// Apply the dataFilter if provided
+	if ( s.dataFilter ) {
+		response = s.dataFilter( response, s.dataType );
+	}
+
+	var dataTypes = s.dataTypes,
+		converters = {},
+		i,
+		key,
+		length = dataTypes.length,
+		tmp,
+		// Current and previous dataTypes
+		current = dataTypes[ 0 ],
+		prev,
+		// Conversion expression
+		conversion,
+		// Conversion function
+		conv,
+		// Conversion functions (transitive conversion)
+		conv1,
+		conv2;
+
+	// For each dataType in the chain
+	for( i = 1; i < length; i++ ) {
+
+		// Create converters map
+		// with lowercased keys
+		if ( i === 1 ) {
+			for( key in s.converters ) {
+				if( typeof key === "string" ) {
+					converters[ key.toLowerCase() ] = s.converters[ key ];
+				}
+			}
+		}
+
+		// Get the dataTypes
+		prev = current;
+		current = dataTypes[ i ];
+
+		// If current is auto dataType, update it to prev
+		if( current === "*" ) {
+			current = prev;
+		// If no auto and dataTypes are actually different
+		} else if ( prev !== "*" && prev !== current ) {
+
+			// Get the converter
+			conversion = prev + " " + current;
+			conv = converters[ conversion ] || converters[ "* " + current ];
+
+			// If there is no direct converter, search transitively
+			if ( !conv ) {
+				conv2 = undefined;
+				for( conv1 in converters ) {
+					tmp = conv1.split( " " );
+					if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+						conv2 = converters[ tmp[1] + " " + current ];
+						if ( conv2 ) {
+							conv1 = converters[ conv1 ];
+							if ( conv1 === true ) {
+								conv = conv2;
+							} else if ( conv2 === true ) {
+								conv = conv1;
+							}
+							break;
+						}
+					}
+				}
+			}
+			// If we found no converter, dispatch an error
+			if ( !( conv || conv2 ) ) {
+				jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+			}
+			// If found converter is not an equivalence
+			if ( conv !== true ) {
+				// Convert with 1 or 2 converters accordingly
+				response = conv ? conv( response ) : conv2( conv1(response) );
+			}
+		}
+	}
+	return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+	jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		return jQuery.expando + "_" + ( jsc++ );
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
+		( typeof s.data === "string" );
+
+	if ( s.dataTypes[ 0 ] === "jsonp" ||
+		s.jsonp !== false && ( jsre.test( s.url ) ||
+				inspectData && jsre.test( s.data ) ) ) {
+
+		var responseContainer,
+			jsonpCallback = s.jsonpCallback =
+				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+			previous = window[ jsonpCallback ],
+			url = s.url,
+			data = s.data,
+			replace = "$1" + jsonpCallback + "$2";
+
+		if ( s.jsonp !== false ) {
+			url = url.replace( jsre, replace );
+			if ( s.url === url ) {
+				if ( inspectData ) {
+					data = data.replace( jsre, replace );
+				}
+				if ( s.data === data ) {
+					// Add callback manually
+					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+				}
+			}
+		}
+
+		s.url = url;
+		s.data = data;
+
+		// Install callback
+		window[ jsonpCallback ] = function( response ) {
+			responseContainer = [ response ];
+		};
+
+		// Clean-up function
+		jqXHR.always(function() {
+			// Set callback back to previous value
+			window[ jsonpCallback ] = previous;
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( previous ) ) {
+				window[ jsonpCallback ]( responseContainer[ 0 ] );
+			}
+		});
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( jsonpCallback + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Delegate to script
+		return "script";
+	}
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /javascript|ecmascript/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement( "script" );
+
+				script.async = "async";
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( head && script.parentNode ) {
+							head.removeChild( script );
+						}
+
+						// Dereference the script
+						script = undefined;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+				// 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 );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( 0, 1 );
+				}
+			}
+		};
+	}
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+	xhrOnUnloadAbort = window.ActiveXObject ? function() {
+		// Abort all pending requests
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( 0, 1 );
+		}
+	} : false,
+	xhrId = 0,
+	xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+	} catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* 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.
+	 */
+	function() {
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+	jQuery.extend( jQuery.support, {
+		ajax: !!xhr,
+		cors: !!xhr && ( "withCredentials" in xhr )
+	});
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// Get a new xhr
+					var xhr = s.xhr(),
+						handle,
+						i;
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
+					} catch( _ ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+
+						var status,
+							statusText,
+							responseHeaders,
+							responses,
+							xml;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occured
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									if ( xhrOnUnloadAbort ) {
+										delete xhrCallbacks[ handle ];
+									}
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+									responses = {};
+									xml = xhr.responseXML;
+
+									// Construct response list
+									if ( xml && xml.documentElement /* #4958 */ ) {
+										responses.xml = xml;
+									}
+									responses.text = xhr.responseText;
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
+							}
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
+							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
+						}
+					};
+
+					// if we're in sync mode or it's in cache
+					// and has been retrieved directly (IE6 & IE7)
+					// we need to manually fire the callback
+					if ( !s.async || xhr.readyState === 4 ) {
+						callback();
+					} else {
+						handle = ++xhrId;
+						if ( xhrOnUnloadAbort ) {
+							// Create the active xhrs callbacks list if needed
+							// and attach the unload handler
+							if ( !xhrCallbacks ) {
+								xhrCallbacks = {};
+								jQuery( window ).unload( xhrOnUnloadAbort );
+							}
+							// Add to list of active xhrs callbacks
+							xhrCallbacks[ handle ] = callback;
+						}
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback(0,1);
+					}
+				}
+			};
+		}
+	});
+}
+
+
+
+
+var elemdisplay = {},
+	iframe, iframeDoc,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+	timerId,
+	fxAttrs = [
+		// height animations
+		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+		// width animations
+		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+		// opacity animations
+		[ "opacity" ]
+	],
+	fxNow;
+
+jQuery.fn.extend({
+	show: function( speed, easing, callback ) {
+		var elem, display;
+
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("show", 3), speed, easing, callback);
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				elem = this[i];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					// Reset the inline display of this element to learn if it is
+					// being hidden by cascaded rules or not
+					if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+						display = elem.style.display = "";
+					}
+
+					// Set elements which have been overridden with display: none
+					// in a stylesheet to whatever the default browser style is
+					// for such an element
+					if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
+						jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
+					}
+				}
+			}
+
+			// Set the display of most of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				elem = this[i];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					if ( display === "" || display === "none" ) {
+						elem.style.display = jQuery._data(elem, "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++ ) {
+				if ( this[i].style ) {
+					var display = jQuery.css( this[i], "display" );
+
+					if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
+						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++ ) {
+				if ( this[i].style ) {
+					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, [ false ] );
+		}
+
+		// Do not change referenced properties as per-property easing will be lost
+		prop = jQuery.extend( {}, prop );
+
+		return this[ optall.queue === false ? "each" : "queue" ](function() {
+			// XXX 'this' does not always have a nodeName when running the
+			// test suite
+
+			if ( optall.queue === false ) {
+				jQuery._mark( this );
+			}
+
+			var opt = jQuery.extend( {}, optall ),
+				isElement = this.nodeType === 1,
+				hidden = isElement && jQuery(this).is(":hidden"),
+				name, val, p,
+				display, e,
+				parts, start, end, unit;
+
+			// will store per property easing and be used to determine when an animation is complete
+			opt.animatedProperties = {};
+
+			for ( p in prop ) {
+
+				// property name normalization
+				name = jQuery.camelCase( p );
+				if ( p !== name ) {
+					prop[ name ] = prop[ p ];
+					delete prop[ p ];
+				}
+
+				val = prop[ name ];
+
+				// easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+				if ( jQuery.isArray( val ) ) {
+					opt.animatedProperties[ name ] = val[ 1 ];
+					val = prop[ name ] = val[ 0 ];
+				} else {
+					opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+				}
+
+				if ( val === "hide" && hidden || val === "show" && !hidden ) {
+					return opt.complete.call( this );
+				}
+
+				if ( isElement && ( name === "height" || name === "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 {
+							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 ( opt.overflow != null ) {
+				this.style.overflow = "hidden";
+			}
+
+			for ( p in prop ) {
+				e = new jQuery.fx( this, opt, p );
+				val = prop[ p ];
+
+				if ( rfxtypes.test(val) ) {
+					e[ val === "toggle" ? hidden ? "show" : "hide" : val ]();
+
+				} else {
+					parts = rfxnum.exec( val );
+					start = e.cur();
+
+					if ( parts ) {
+						end = parseFloat( parts[2] );
+						unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+						// We need to compute starting value
+						if ( unit !== "px" ) {
+							jQuery.style( this, p, (end || 1) + unit);
+							start = ((end || 1) / e.cur()) * start;
+							jQuery.style( this, p, 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 ) {
+		if ( clearQueue ) {
+			this.queue([]);
+		}
+
+		this.each(function() {
+			var timers = jQuery.timers,
+				i = timers.length;
+			// clear marker counters if we know they won't be
+			if ( !gotoEnd ) {
+				jQuery._unmark( true, this );
+			}
+			while ( 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;
+	}
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout( clearFxNow, 0 );
+	return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+	fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+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" },
+	fadeToggle: { opacity: "toggle" }
+}, 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( noUnmark ) {
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+
+			if ( opt.queue !== false ) {
+				jQuery.dequeue( this );
+			} else if ( noUnmark !== false ) {
+				jQuery._unmark( 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;
+
+		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 parsed,
+			r = jQuery.css( this.elem, this.prop );
+		// Empty strings, null, undefined and "auto" are converted to 0,
+		// complex values such as "rotate(1rad)" are returned as is,
+		// simple values such as "10px" are parsed to Float.
+		return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+	},
+
+	// Start an animation from one number to another
+	custom: function( from, to, unit ) {
+		var self = this,
+			fx = jQuery.fx;
+
+		this.startTime = fxNow || createFxNow();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+		this.now = this.start;
+		this.pos = this.state = 0;
+
+		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 = fxNow || createFxNow(),
+			done = true,
+			elem = this.elem,
+			options = this.options,
+			i, n;
+
+		if ( gotoEnd || t >= options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			options.animatedProperties[ this.prop ] = true;
+
+			for ( i in options.animatedProperties ) {
+				if ( options.animatedProperties[i] !== true ) {
+					done = false;
+				}
+			}
+
+			if ( done ) {
+				// Reset the overflow
+				if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+					jQuery.each( [ "", "X", "Y" ], function (index, value) {
+						elem.style[ "overflow" + value ] = options.overflow[index];
+					});
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( options.hide ) {
+					jQuery(elem).hide();
+				}
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( options.hide || options.show ) {
+					for ( var p in options.animatedProperties ) {
+						jQuery.style( elem, p, options.orig[p] );
+					}
+				}
+
+				// Execute the complete function
+				options.complete.call( elem );
+			}
+
+			return false;
+
+		} else {
+			// classical easing cannot be used with an Infinity duration
+			if ( options.duration == Infinity ) {
+				this.now = t;
+			} else {
+				n = t - this.startTime;
+				this.state = n / options.duration;
+
+				// Perform the easing function, defaults to swing
+				this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, 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() {
+		for ( var timers = jQuery.timers, 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;
+	};
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+	if ( !elemdisplay[ nodeName ] ) {
+
+		var body = document.body,
+			elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+			display = elem.css( "display" );
+
+		elem.remove();
+
+		// If the simple way fails,
+		// get element's real default display by attaching it to a temp iframe
+		if ( display === "none" || display === "" ) {
+			// No iframe to use yet, so create it
+			if ( !iframe ) {
+				iframe = document.createElement( "iframe" );
+				iframe.frameBorder = iframe.width = iframe.height = 0;
+			}
+
+			body.appendChild( iframe );
+
+			// Create a cacheable copy of the iframe document on first call.
+			// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+			// document to it; WebKit & Firefox won't allow reusing the iframe document.
+			if ( !iframeDoc || !iframe.createElement ) {
+				iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+				iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
+				iframeDoc.close();
+			}
+
+			elem = iframeDoc.createElement( nodeName );
+
+			iframeDoc.body.appendChild( elem );
+
+			display = jQuery.css( elem, "display" );
+
+			body.removeChild( iframe );
+		}
+
+		// Store the correct default display
+		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: box.top, left: box.left } : { 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 computedStyle,
+			offsetParent = elem.offsetParent,
+			prevOffsetParent = elem,
+			doc = elem.ownerDocument,
+			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 );
+		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" || position === "fixed") && 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 either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			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, win;
+
+		if ( val === undefined ) {
+			elem = this[ 0 ];
+
+			if ( !elem ) {
+				return null;
+			}
+
+			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 ];
+		}
+
+		// 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;
+			}
+		});
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+
+
+
+
+// Create width, height, 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() {
+		var elem = this[0];
+		return elem && elem.style ?
+			parseFloat( jQuery.css( elem, type, "padding" ) ) :
+			null;
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn[ "outer" + name ] = function( margin ) {
+		var elem = this[0];
+		return elem && elem.style ?
+			parseFloat( jQuery.css( elem, 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 ]() ) );
+			});
+		}
+
+		if ( jQuery.isWindow( elem ) ) {
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			// 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+			var docElemProp = elem.document.documentElement[ "client" + name ],
+				body = elem.document.body;
+			return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+				body && body[ "client" + name ] || docElemProp;
+
+		// Get document width or height
+		} else if ( elem.nodeType === 9 ) {
+			// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+			return 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
+		} else if ( size === undefined ) {
+			var orig = jQuery.css( elem, type ),
+				ret = parseFloat( orig );
+
+			return jQuery.isNaN( ret ) ? orig : ret;
+
+		// Set the width or height on the element (default to pixels if value is unitless)
+		} else {
+			return this.css( type, typeof size === "string" ? size : size + "px" );
+		}
+	};
+
+});
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+})(window);

+ 5815 - 0
js/libraries/balupton-history/vendor/mootools.js

@@ -0,0 +1,5815 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/76bf47062d6c1983d66ce47ad66aa0e0
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff
+
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2010 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+  - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+  - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+	version: '1.4.0',
+	build: 'a15e35b4dbd12e8d86d9b50aa67a27e8e0071ea3'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+	if (item == null) return 'null';
+	if (item.$family) return item.$family();
+
+	if (item.nodeName){
+		if (item.nodeType == 1) return 'element';
+		if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+	} else if (typeof item.length == 'number'){
+		if (item.callee) return 'arguments';
+		if ('item' in item) return 'collection';
+	}
+
+	return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+	if (item == null) return false;
+	var constructor = item.$constructor || item.constructor;
+	while (constructor){
+		if (constructor === object) return true;
+		constructor = constructor.parent;
+	}
+	return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+	var self = this;
+	return function(a, b){
+		if (a == null) return this;
+		if (usePlural || typeof a != 'string'){
+			for (var k in a) self.call(this, k, a[k]);
+			if (enumerables) for (var i = enumerables.length; i--;){
+				k = enumerables[i];
+				if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+			}
+		} else {
+			self.call(this, a, b);
+		}
+		return this;
+	};
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+	var self = this;
+	return function(a){
+		var args, result;
+		if (usePlural || typeof a != 'string') args = a;
+		else if (arguments.length > 1) args = arguments;
+		if (args){
+			result = {};
+			for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+		} else {
+			result = self.call(this, a);
+		}
+		return result;
+	};
+};
+
+Function.prototype.extend = function(key, value){
+	this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+	this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+	return (typeOf(item) == 'function') ? item : function(){
+		return item;
+	};
+};
+
+Array.from = function(item){
+	if (item == null) return [];
+	return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+	var number = parseFloat(item);
+	return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+	return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+	hide: function(){
+		this.$hidden = true;
+		return this;
+	},
+
+	protect: function(){
+		this.$protected = true;
+		return this;
+	}
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+	if (name){
+		var lower = name.toLowerCase();
+		var typeCheck = function(item){
+			return (typeOf(item) == lower);
+		};
+
+		Type['is' + name] = typeCheck;
+		if (object != null){
+			object.prototype.$family = (function(){
+				return lower;
+			}).hide();
+			
+		}
+	}
+
+	if (object == null) return null;
+
+	object.extend(this);
+	object.$constructor = Type;
+	object.prototype.$constructor = object;
+
+	return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+	return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+	var type = typeOf(object.prototype);
+	return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+	if (method && method.$hidden) return;
+
+	var hooks = hooksOf(this);
+
+	for (var i = 0; i < hooks.length; i++){
+		var hook = hooks[i];
+		if (typeOf(hook) == 'type') implement.call(hook, name, method);
+		else hook.call(this, name, method);
+	}
+
+	var previous = this.prototype[name];
+	if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+	if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+		return method.apply(item, slice.call(arguments, 1));
+	});
+};
+
+var extend = function(name, method){
+	if (method && method.$hidden) return;
+	var previous = this[name];
+	if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+	implement: implement.overloadSetter(),
+
+	extend: extend.overloadSetter(),
+
+	alias: function(name, existing){
+		implement.call(this, name, this.prototype[existing]);
+	}.overloadSetter(),
+
+	mirror: function(hook){
+		hooksOf(this).push(hook);
+		return this;
+	}
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+	var isType = (object != Object),
+		prototype = object.prototype;
+
+	if (isType) object = new Type(name, object);
+
+	for (var i = 0, l = methods.length; i < l; i++){
+		var key = methods[i],
+			generic = object[key],
+			proto = prototype[key];
+
+		if (generic) generic.protect();
+
+		if (isType && proto){
+			delete prototype[key];
+			prototype[key] = proto.protect();
+		}
+	}
+
+	if (isType) object.implement(prototype);
+
+	return force;
+};
+
+force('String', String, [
+	'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+	'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+	'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+	'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+	'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+	'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+	'exec', 'test'
+])('Object', Object, [
+	'create', 'defineProperty', 'defineProperties', 'keys',
+	'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+	'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+	return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+	return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+	return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+	for (var key in object){
+		if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+	}
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+	forEach: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (i in this) fn.call(bind, this[i], i, this);
+		}
+	},
+
+	each: function(fn, bind){
+		Array.forEach(this, fn, bind);
+		return this;
+	}
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+	switch (typeOf(item)){
+		case 'array': return item.clone();
+		case 'object': return Object.clone(item);
+		default: return item;
+	}
+};
+
+Array.implement('clone', function(){
+	var i = this.length, clone = new Array(i);
+	while (i--) clone[i] = cloneOf(this[i]);
+	return clone;
+});
+
+var mergeOne = function(source, key, current){
+	switch (typeOf(current)){
+		case 'object':
+			if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+			else source[key] = Object.clone(current);
+		break;
+		case 'array': source[key] = current.clone(); break;
+		default: source[key] = current;
+	}
+	return source;
+};
+
+Object.extend({
+
+	merge: function(source, k, v){
+		if (typeOf(k) == 'string') return mergeOne(source, k, v);
+		for (var i = 1, l = arguments.length; i < l; i++){
+			var object = arguments[i];
+			for (var key in object) mergeOne(source, key, object[key]);
+		}
+		return source;
+	},
+
+	clone: function(object){
+		var clone = {};
+		for (var key in object) clone[key] = cloneOf(object[key]);
+		return clone;
+	},
+
+	append: function(original){
+		for (var i = 1, l = arguments.length; i < l; i++){
+			var extended = arguments[i] || {};
+			for (var key in extended) original[key] = extended[key];
+		}
+		return original;
+	}
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+	new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+	return (UID++).toString(36);
+});
+
+
+
+})();
+
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+	/*<!ES5>*/
+	every: function(fn, bind){
+		for (var i = 0, l = this.length >>> 0; i < l; i++){
+			if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+		}
+		return true;
+	},
+
+	filter: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length >>> 0; i < l; i++){
+			if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]);
+		}
+		return results;
+	},
+
+	indexOf: function(item, from){
+		var length = this.length >>> 0;
+		for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+			if (this[i] === item) return i;
+		}
+		return -1;
+	},
+
+	map: function(fn, bind){
+		var length = this.length >>> 0, results = Array(length);
+		for (var i = 0; i < length; i++){
+			if (i in this) results[i] = fn.call(bind, this[i], i, this);
+		}
+		return results;
+	},
+
+	some: function(fn, bind){
+		for (var i = 0, l = this.length >>> 0; i < l; i++){
+			if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+		}
+		return false;
+	},
+	/*</!ES5>*/
+
+	clean: function(){
+		return this.filter(function(item){
+			return item != null;
+		});
+	},
+
+	invoke: function(methodName){
+		var args = Array.slice(arguments, 1);
+		return this.map(function(item){
+			return item[methodName].apply(item, args);
+		});
+	},
+
+	associate: function(keys){
+		var obj = {}, length = Math.min(this.length, keys.length);
+		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+		return obj;
+	},
+
+	link: function(object){
+		var result = {};
+		for (var i = 0, l = this.length; i < l; i++){
+			for (var key in object){
+				if (object[key](this[i])){
+					result[key] = this[i];
+					delete object[key];
+					break;
+				}
+			}
+		}
+		return result;
+	},
+
+	contains: function(item, from){
+		return this.indexOf(item, from) != -1;
+	},
+
+	append: function(array){
+		this.push.apply(this, array);
+		return this;
+	},
+
+	getLast: function(){
+		return (this.length) ? this[this.length - 1] : null;
+	},
+
+	getRandom: function(){
+		return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+	},
+
+	include: function(item){
+		if (!this.contains(item)) this.push(item);
+		return this;
+	},
+
+	combine: function(array){
+		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+		return this;
+	},
+
+	erase: function(item){
+		for (var i = this.length; i--;){
+			if (this[i] === item) this.splice(i, 1);
+		}
+		return this;
+	},
+
+	empty: function(){
+		this.length = 0;
+		return this;
+	},
+
+	flatten: function(){
+		var array = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			var type = typeOf(this[i]);
+			if (type == 'null') continue;
+			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+		}
+		return array;
+	},
+
+	pick: function(){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (this[i] != null) return this[i];
+		}
+		return null;
+	},
+
+	hexToRgb: function(array){
+		if (this.length != 3) return null;
+		var rgb = this.map(function(value){
+			if (value.length == 1) value += value;
+			return value.toInt(16);
+		});
+		return (array) ? rgb : 'rgb(' + rgb + ')';
+	},
+
+	rgbToHex: function(array){
+		if (this.length < 3) return null;
+		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+		var hex = [];
+		for (var i = 0; i < 3; i++){
+			var bit = (this[i] - 0).toString(16);
+			hex.push((bit.length == 1) ? '0' + bit : bit);
+		}
+		return (array) ? hex : '#' + hex.join('');
+	}
+
+});
+
+
+
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+	test: function(regex, params){
+		return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+	},
+
+	contains: function(string, separator){
+		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : String(this).indexOf(string) > -1;
+	},
+
+	trim: function(){
+		return String(this).replace(/^\s+|\s+$/g, '');
+	},
+
+	clean: function(){
+		return String(this).replace(/\s+/g, ' ').trim();
+	},
+
+	camelCase: function(){
+		return String(this).replace(/-\D/g, function(match){
+			return match.charAt(1).toUpperCase();
+		});
+	},
+
+	hyphenate: function(){
+		return String(this).replace(/[A-Z]/g, function(match){
+			return ('-' + match.charAt(0).toLowerCase());
+		});
+	},
+
+	capitalize: function(){
+		return String(this).replace(/\b[a-z]/g, function(match){
+			return match.toUpperCase();
+		});
+	},
+
+	escapeRegExp: function(){
+		return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	hexToRgb: function(array){
+		var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+		return (hex) ? hex.slice(1).hexToRgb(array) : null;
+	},
+
+	rgbToHex: function(array){
+		var rgb = String(this).match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHex(array) : null;
+	},
+
+	substitute: function(object, regexp){
+		return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+			if (match.charAt(0) == '\\') return match.slice(1);
+			return (object[name] != null) ? object[name] : '';
+		});
+	}
+
+});
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+	limit: function(min, max){
+		return Math.min(max, Math.max(min, this));
+	},
+
+	round: function(precision){
+		precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+		return Math.round(this * precision) / precision;
+	},
+
+	times: function(fn, bind){
+		for (var i = 0; i < this; i++) fn.call(bind, i, this);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	}
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+	var methods = {};
+	math.each(function(name){
+		if (!Number[name]) methods[name] = function(){
+			return Math[name].apply(null, [this].concat(Array.from(arguments)));
+		};
+	});
+	Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+	attempt: function(){
+		for (var i = 0, l = arguments.length; i < l; i++){
+			try {
+				return arguments[i]();
+			} catch (e){}
+		}
+		return null;
+	}
+
+});
+
+Function.implement({
+
+	attempt: function(args, bind){
+		try {
+			return this.apply(bind, Array.from(args));
+		} catch (e){}
+
+		return null;
+	},
+
+	/*<!ES5-bind>*/
+	bind: function(that){
+		var self = this,
+			args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+			F = function(){};
+
+		var bound = function(){
+			var context = that, length = arguments.length;
+			if (this instanceof bound){
+				F.prototype = self.prototype;
+				context = new F;
+			}
+			var result = (!args && !length)
+				? self.call(context)
+				: self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+			return context == that ? result : context;
+		};
+		return bound;
+	},
+	/*</!ES5-bind>*/
+
+	pass: function(args, bind){
+		var self = this;
+		if (args != null) args = Array.from(args);
+		return function(){
+			return self.apply(bind, args || arguments);
+		};
+	},
+
+	delay: function(delay, bind, args){
+		return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+	},
+
+	periodical: function(periodical, bind, args){
+		return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+	}
+
+});
+
+
+
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+	subset: function(object, keys){
+		var results = {};
+		for (var i = 0, l = keys.length; i < l; i++){
+			var k = keys[i];
+			if (k in object) results[k] = object[k];
+		}
+		return results;
+	},
+
+	map: function(object, fn, bind){
+		var results = {};
+		for (var key in object){
+			if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+		}
+		return results;
+	},
+
+	filter: function(object, fn, bind){
+		var results = {};
+		for (var key in object){
+			var value = object[key];
+			if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+		}
+		return results;
+	},
+
+	every: function(object, fn, bind){
+		for (var key in object){
+			if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+		}
+		return true;
+	},
+
+	some: function(object, fn, bind){
+		for (var key in object){
+			if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+		}
+		return false;
+	},
+
+	keys: function(object){
+		var keys = [];
+		for (var key in object){
+			if (hasOwnProperty.call(object, key)) keys.push(key);
+		}
+		return keys;
+	},
+
+	values: function(object){
+		var values = [];
+		for (var key in object){
+			if (hasOwnProperty.call(object, key)) values.push(object[key]);
+		}
+		return values;
+	},
+
+	getLength: function(object){
+		return Object.keys(object).length;
+	},
+
+	keyOf: function(object, value){
+		for (var key in object){
+			if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+		}
+		return null;
+	},
+
+	contains: function(object, value){
+		return Object.keyOf(object, value) != null;
+	},
+
+	toQueryString: function(object, base){
+		var queryString = [];
+
+		Object.each(object, function(value, key){
+			if (base) key = base + '[' + key + ']';
+			var result;
+			switch (typeOf(value)){
+				case 'object': result = Object.toQueryString(value, key); break;
+				case 'array':
+					var qs = {};
+					value.each(function(val, i){
+						qs[i] = val;
+					});
+					result = Object.toQueryString(qs, key);
+				break;
+				default: result = key + '=' + encodeURIComponent(value);
+			}
+			if (value != null) queryString.push(result);
+		});
+
+		return queryString.join('&');
+	}
+
+});
+
+})();
+
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var UID = 1;
+
+this.$uid = (window.ActiveXObject) ? function(item){
+	return (item.uid || (item.uid = [UID++]))[0];
+} : function(item){
+	return item.uid || (item.uid = UID++);
+};
+
+$uid(window);
+$uid(document);
+
+var ua = navigator.userAgent.toLowerCase(),
+	platform = navigator.platform.toLowerCase(),
+	UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0],
+	mode = UA[1] == 'ie' && document.documentMode;
+
+var Browser = this.Browser = {
+
+	extend: Function.prototype.extend,
+
+	name: (UA[1] == 'version') ? UA[3] : UA[1],
+
+	version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+
+	Platform: {
+		name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]
+	},
+
+	Features: {
+		xpath: !!(document.evaluate),
+		air: !!(window.runtime),
+		query: !!(document.querySelector),
+		json: !!(window.JSON)
+	},
+
+	Plugins: {}
+
+};
+
+Browser[Browser.name] = true;
+Browser[Browser.name + parseInt(Browser.version, 10)] = true;
+Browser.Platform[Browser.Platform.name] = true;
+
+// Request
+
+Browser.Request = (function(){
+
+	var XMLHTTP = function(){
+		return new XMLHttpRequest();
+	};
+
+	var MSXML2 = function(){
+		return new ActiveXObject('MSXML2.XMLHTTP');
+	};
+
+	var MSXML = function(){
+		return new ActiveXObject('Microsoft.XMLHTTP');
+	};
+
+	return Function.attempt(function(){
+		XMLHTTP();
+		return XMLHTTP;
+	}, function(){
+		MSXML2();
+		return MSXML2;
+	}, function(){
+		MSXML();
+		return MSXML;
+	});
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+// Flash detection
+
+var version = (Function.attempt(function(){
+	return navigator.plugins['Shockwave Flash'].description;
+}, function(){
+	return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+}) || '0 r0').match(/\d+/g);
+
+Browser.Plugins.Flash = {
+	version: Number(version[0] || '0.' + version[1]) || 0,
+	build: Number(version[2]) || 0
+};
+
+// String scripts
+
+Browser.exec = function(text){
+	if (!text) return text;
+	if (window.execScript){
+		window.execScript(text);
+	} else {
+		var script = document.createElement('script');
+		script.setAttribute('type', 'text/javascript');
+		script.text = text;
+		document.head.appendChild(script);
+		document.head.removeChild(script);
+	}
+	return text;
+};
+
+String.implement('stripScripts', function(exec){
+	var scripts = '';
+	var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+		scripts += code + '\n';
+		return '';
+	});
+	if (exec === true) Browser.exec(scripts);
+	else if (typeOf(exec) == 'function') exec(scripts, text);
+	return text;
+});
+
+// Window, Document
+
+Browser.extend({
+	Document: this.Document,
+	Window: this.Window,
+	Element: this.Element,
+	Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+	window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+	document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+	document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+	var unloadEvent = function(){
+		this.detachEvent('onunload', unloadEvent);
+		document.head = document.html = document.window = null;
+	};
+	this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+	arrayFrom(document.html.childNodes);
+} catch(e){
+	Array.from = function(item){
+		if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+			var i = item.length, array = new Array(i);
+			while (i--) array[i] = item[i];
+			return array;
+		}
+		return arrayFrom(item);
+	};
+
+	var prototype = Array.prototype,
+		slice = prototype.slice;
+	['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+		var method = prototype[name];
+		Array[name] = function(item){
+			return method.apply(Array.from(item), slice.call(arguments, 1));
+		};
+	});
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function() {
+
+var _keys = {};
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+	if (!win) win = window;
+	event = event || win.event;
+	if (event.$extended) return event;
+	this.event = event;
+	this.$extended = true;
+	this.shift = event.shiftKey;
+	this.control = event.ctrlKey;
+	this.alt = event.altKey;
+	this.meta = event.metaKey;
+	var type = this.type = event.type;
+	var target = event.target || event.srcElement;
+	while (target && target.nodeType == 3) target = target.parentNode;
+	this.target = document.id(target);
+
+	if (type.indexOf('key') == 0){
+		var code = this.code = (event.which || event.keyCode);
+		this.key = _keys[code];
+		if (type == 'keydown'){
+			if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+			else if (code > 95 && code < 106) this.key = code - 96;
+		}
+		if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+	} else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type.indexOf('mouse') == 0){
+		var doc = win.document;
+		doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+		this.page = {
+			x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+			y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+		};
+		this.client = {
+			x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+			y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+		};
+		if (type == 'DOMMouseScroll' || type == 'mousewheel')
+			this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+		this.rightClick = (event.which == 3 || event.button == 2);
+		if (type == 'mouseover' || type == 'mouseout'){
+			var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+			while (related && related.nodeType == 3) related = related.parentNode;
+			this.relatedTarget = document.id(related);
+		}
+	} else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+		this.rotation = event.rotation;
+		this.scale = event.scale;
+		this.targetTouches = event.targetTouches;
+		this.changedTouches = event.changedTouches;
+		var touches = this.touches = event.touches;
+		if (touches && touches[0]){
+			var touch = touches[0];
+			this.page = {x: touch.pageX, y: touch.pageY};
+			this.client = {x: touch.clientX, y: touch.clientY};
+		}
+	}
+
+	if (!this.client) this.client = {};
+	if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+	stop: function(){
+		return this.preventDefault().stopPropagation();
+	},
+
+	stopPropagation: function(){
+		if (this.event.stopPropagation) this.event.stopPropagation();
+		else this.event.cancelBubble = true;
+		return this;
+	},
+
+	preventDefault: function(){
+		if (this.event.preventDefault) this.event.preventDefault();
+		else this.event.returnValue = false;
+		return this;
+	}
+
+});
+
+DOMEvent.defineKey = function(code, key){
+	_keys[code] = key;
+	return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+	'38': 'up', '40': 'down', '37': 'left', '39': 'right',
+	'27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+	'46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+	if (instanceOf(params, Function)) params = {initialize: params};
+
+	var newClass = function(){
+		reset(this);
+		if (newClass.$prototyping) return this;
+		this.$caller = null;
+		var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+		this.$caller = this.caller = null;
+		return value;
+	}.extend(this).implement(params);
+
+	newClass.$constructor = Class;
+	newClass.prototype.$constructor = newClass;
+	newClass.prototype.parent = parent;
+
+	return newClass;
+});
+
+var parent = function(){
+	if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+	var name = this.$caller.$name,
+		parent = this.$caller.$owner.parent,
+		previous = (parent) ? parent.prototype[name] : null;
+	if (!previous) throw new Error('The method "' + name + '" has no parent.');
+	return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+	for (var key in object){
+		var value = object[key];
+		switch (typeOf(value)){
+			case 'object':
+				var F = function(){};
+				F.prototype = value;
+				object[key] = reset(new F);
+			break;
+			case 'array': object[key] = value.clone(); break;
+		}
+	}
+	return object;
+};
+
+var wrap = function(self, key, method){
+	if (method.$origin) method = method.$origin;
+	var wrapper = function(){
+		if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+		var caller = this.caller, current = this.$caller;
+		this.caller = current; this.$caller = wrapper;
+		var result = method.apply(this, arguments);
+		this.$caller = current; this.caller = caller;
+		return result;
+	}.extend({$owner: self, $origin: method, $name: key});
+	return wrapper;
+};
+
+var implement = function(key, value, retain){
+	if (Class.Mutators.hasOwnProperty(key)){
+		value = Class.Mutators[key].call(this, value);
+		if (value == null) return this;
+	}
+
+	if (typeOf(value) == 'function'){
+		if (value.$hidden) return this;
+		this.prototype[key] = (retain) ? value : wrap(this, key, value);
+	} else {
+		Object.merge(this.prototype, key, value);
+	}
+
+	return this;
+};
+
+var getInstance = function(klass){
+	klass.$prototyping = true;
+	var proto = new klass;
+	delete klass.$prototyping;
+	return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+	Extends: function(parent){
+		this.parent = parent;
+		this.prototype = getInstance(parent);
+	},
+
+	Implements: function(items){
+		Array.from(items).each(function(item){
+			var instance = new item;
+			for (var key in instance) implement.call(this, key, instance[key], true);
+		}, this);
+	}
+};
+
+})();
+
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+	$chain: [],
+
+	chain: function(){
+		this.$chain.append(Array.flatten(arguments));
+		return this;
+	},
+
+	callChain: function(){
+		return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+	},
+
+	clearChain: function(){
+		this.$chain.empty();
+		return this;
+	}
+
+});
+
+var removeOn = function(string){
+	return string.replace(/^on([A-Z])/, function(full, first){
+		return first.toLowerCase();
+	});
+};
+
+this.Events = new Class({
+
+	$events: {},
+
+	addEvent: function(type, fn, internal){
+		type = removeOn(type);
+
+		
+
+		this.$events[type] = (this.$events[type] || []).include(fn);
+		if (internal) fn.internal = true;
+		return this;
+	},
+
+	addEvents: function(events){
+		for (var type in events) this.addEvent(type, events[type]);
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		type = removeOn(type);
+		var events = this.$events[type];
+		if (!events) return this;
+		args = Array.from(args);
+		events.each(function(fn){
+			if (delay) fn.delay(delay, this, args);
+			else fn.apply(this, args);
+		}, this);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		type = removeOn(type);
+		var events = this.$events[type];
+		if (events && !fn.internal){
+			var index =  events.indexOf(fn);
+			if (index != -1) delete events[index];
+		}
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if (typeOf(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		if (events) events = removeOn(events);
+		for (type in this.$events){
+			if (events && events != type) continue;
+			var fns = this.$events[type];
+			for (var i = fns.length; i--;) if (i in fns){
+				this.removeEvent(type, fns[i]);
+			}
+		}
+		return this;
+	}
+
+});
+
+this.Options = new Class({
+
+	setOptions: function(){
+		var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+		if (this.addEvent) for (var option in options){
+			if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+			this.addEvent(option, options[option]);
+			delete options[option];
+		}
+		return this;
+	}
+
+});
+
+})();
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+	separatorIndex,
+	combinatorIndex,
+	reversed,
+	cache = {},
+	reverseCache = {},
+	reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+	if (expression == null) return null;
+	if (expression.Slick === true) return expression;
+	expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+	reversed = !!isReversed;
+	var currentCache = (reversed) ? reverseCache : cache;
+	if (currentCache[expression]) return currentCache[expression];
+	parsed = {
+		Slick: true,
+		expressions: [],
+		raw: expression,
+		reverse: function(){
+			return parse(this.raw, true);
+		}
+	};
+	separatorIndex = -1;
+	while (expression != (expression = expression.replace(regexp, parser)));
+	parsed.length = parsed.expressions.length;
+	return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+	if (combinator === '!') return ' ';
+	else if (combinator === ' ') return '!';
+	else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+	else return '!' + combinator;
+};
+
+var reverse = function(expression){
+	var expressions = expression.expressions;
+	for (var i = 0; i < expressions.length; i++){
+		var exp = expressions[i];
+		var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+		for (var j = 0; j < exp.length; j++){
+			var cexp = exp[j];
+			if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+			cexp.combinator = cexp.reverseCombinator;
+			delete cexp.reverseCombinator;
+		}
+
+		exp.reverse().push(last);
+	}
+	return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+	return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+		return '\\' + match;
+	});
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+	"(?x)^(?:\
+	  \\s* ( , ) \\s*               # Separator          \n\
+	| \\s* ( <combinator>+ ) \\s*   # Combinator         \n\
+	|      ( \\s+ )                 # CombinatorChildren \n\
+	|      ( <unicode>+ | \\* )     # Tag                \n\
+	| \\#  ( <unicode>+       )     # ID                 \n\
+	| \\.  ( <unicode>+       )     # ClassName          \n\
+	|                               # Attribute          \n\
+	\\[  \
+		\\s* (<unicode1>+)  (?:  \
+			\\s* ([*^$!~|]?=)  (?:  \
+				\\s* (?:\
+					([\"']?)(.*?)\\9 \
+				)\
+			)  \
+		)?  \\s*  \
+	\\](?!\\]) \n\
+	|   :+ ( <unicode>+ )(?:\
+	\\( (?:\
+		(?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+	) \\)\
+	)?\
+	)"
+*/
+	"^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+	.replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+	.replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+	.replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+	rawMatch,
+
+	separator,
+	combinator,
+	combinatorChildren,
+
+	tagName,
+	id,
+	className,
+
+	attributeKey,
+	attributeOperator,
+	attributeQuote,
+	attributeValue,
+
+	pseudoMarker,
+	pseudoClass,
+	pseudoQuote,
+	pseudoClassQuotedValue,
+	pseudoClassValue
+){
+	if (separator || separatorIndex === -1){
+		parsed.expressions[++separatorIndex] = [];
+		combinatorIndex = -1;
+		if (separator) return '';
+	}
+
+	if (combinator || combinatorChildren || combinatorIndex === -1){
+		combinator = combinator || ' ';
+		var currentSeparator = parsed.expressions[separatorIndex];
+		if (reversed && currentSeparator[combinatorIndex])
+			currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+		currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+	}
+
+	var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+	if (tagName){
+		currentParsed.tag = tagName.replace(reUnescape, '');
+
+	} else if (id){
+		currentParsed.id = id.replace(reUnescape, '');
+
+	} else if (className){
+		className = className.replace(reUnescape, '');
+
+		if (!currentParsed.classList) currentParsed.classList = [];
+		if (!currentParsed.classes) currentParsed.classes = [];
+		currentParsed.classList.push(className);
+		currentParsed.classes.push({
+			value: className,
+			regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+		});
+
+	} else if (pseudoClass){
+		pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+		pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+		if (!currentParsed.pseudos) currentParsed.pseudos = [];
+		currentParsed.pseudos.push({
+			key: pseudoClass.replace(reUnescape, ''),
+			value: pseudoClassValue,
+			type: pseudoMarker.length == 1 ? 'class' : 'element'
+		});
+
+	} else if (attributeKey){
+		attributeKey = attributeKey.replace(reUnescape, '');
+		attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+		var test, regexp;
+
+		switch (attributeOperator){
+			case '^=' : regexp = new RegExp(       '^'+ escapeRegExp(attributeValue)            ); break;
+			case '$=' : regexp = new RegExp(            escapeRegExp(attributeValue) +'$'       ); break;
+			case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+			case '|=' : regexp = new RegExp(       '^'+ escapeRegExp(attributeValue) +'(-|$)'   ); break;
+			case  '=' : test = function(value){
+				return attributeValue == value;
+			}; break;
+			case '*=' : test = function(value){
+				return value && value.indexOf(attributeValue) > -1;
+			}; break;
+			case '!=' : test = function(value){
+				return attributeValue != value;
+			}; break;
+			default   : test = function(value){
+				return !!value;
+			};
+		}
+
+		if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+			return false;
+		};
+
+		if (!test) test = function(value){
+			return value && regexp.test(value);
+		};
+
+		if (!currentParsed.attributes) currentParsed.attributes = [];
+		currentParsed.attributes.push({
+			key: attributeKey,
+			operator: attributeOperator,
+			value: attributeValue,
+			test: test
+		});
+
+	}
+
+	return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+	return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+	featuresCache = {},
+	toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+	return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+	return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+	(document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+	// convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+	var nodeType = document.nodeType;
+	if (nodeType == 9); // document
+	else if (nodeType) document = document.ownerDocument; // node
+	else if (document.navigator) document = document.document; // window
+	else return;
+
+	// check if it's the old document
+
+	if (this.document === document) return;
+	this.document = document;
+
+	// check if we have done feature detection on this document before
+
+	var root = document.documentElement,
+		rootUid = this.getUIDXML(root),
+		features = featuresCache[rootUid],
+		feature;
+
+	if (features){
+		for (feature in features){
+			this[feature] = features[feature];
+		}
+		return;
+	}
+
+	features = featuresCache[rootUid] = {};
+
+	features.root = root;
+	features.isXMLDocument = this.isXML(document);
+
+	features.brokenStarGEBTN
+	= features.starSelectsClosedQSA
+	= features.idGetsName
+	= features.brokenMixedCaseQSA
+	= features.brokenGEBCN
+	= features.brokenCheckedQSA
+	= features.brokenEmptyAttributeQSA
+	= features.isHTMLDocument
+	= features.nativeMatchesSelector
+	= false;
+
+	var starSelectsClosed, starSelectsComments,
+		brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+		brokenFormAttributeGetter;
+
+	var selected, id = 'slick_uniqueid';
+	var testNode = document.createElement('div');
+
+	var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+	testRoot.appendChild(testNode);
+
+	// on non-HTML documents innerHTML and getElementsById doesnt work properly
+	try {
+		testNode.innerHTML = '<a id="'+id+'"></a>';
+		features.isHTMLDocument = !!document.getElementById(id);
+	} catch(e){};
+
+	if (features.isHTMLDocument){
+
+		testNode.style.display = 'none';
+
+		// IE returns comment nodes for getElementsByTagName('*') for some documents
+		testNode.appendChild(document.createComment(''));
+		starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+		// IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+		try {
+			testNode.innerHTML = 'foo</foo>';
+			selected = testNode.getElementsByTagName('*');
+			starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+		} catch(e){};
+
+		features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+		// IE returns elements with the name instead of just id for getElementsById for some documents
+		try {
+			testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+			features.idGetsName = document.getElementById(id) === testNode.firstChild;
+		} catch(e){};
+
+		if (testNode.getElementsByClassName){
+
+			// Safari 3.2 getElementsByClassName caches results
+			try {
+				testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+				testNode.getElementsByClassName('b').length;
+				testNode.firstChild.className = 'b';
+				cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+			} catch(e){};
+
+			// Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+			try {
+				testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+				brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+			} catch(e){};
+
+			features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+		}
+
+		if (testNode.querySelectorAll){
+			// IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+			try {
+				testNode.innerHTML = 'foo</foo>';
+				selected = testNode.querySelectorAll('*');
+				features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+			} catch(e){};
+
+			// Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+			try {
+				testNode.innerHTML = '<a class="MiX"></a>';
+				features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+			} catch(e){};
+
+			// Webkit and Opera dont return selected options on querySelectorAll
+			try {
+				testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+				features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+			} catch(e){};
+
+			// IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+			try {
+				testNode.innerHTML = '<a class=""></a>';
+				features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+			} catch(e){};
+
+		}
+
+		// IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+		try {
+			testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+			brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+		} catch(e){};
+
+		// native matchesSelector function
+
+		features.nativeMatchesSelector = root.matchesSelector || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+		if (features.nativeMatchesSelector) try {
+			// if matchesSelector trows errors on incorrect sintaxes we can use it
+			features.nativeMatchesSelector.call(root, ':slick');
+			features.nativeMatchesSelector = null;
+		} catch(e){};
+
+	}
+
+	try {
+		root.slick_expando = 1;
+		delete root.slick_expando;
+		features.getUID = this.getUIDHTML;
+	} catch(e) {
+		features.getUID = this.getUIDXML;
+	}
+
+	testRoot.removeChild(testNode);
+	testNode = selected = testRoot = null;
+
+	// getAttribute
+
+	features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+		var method = this.attributeGetters[name];
+		if (method) return method.call(node);
+		var attributeNode = node.getAttributeNode(name);
+		return (attributeNode) ? attributeNode.nodeValue : null;
+	} : function(node, name){
+		var method = this.attributeGetters[name];
+		return (method) ? method.call(node) : node.getAttribute(name);
+	};
+
+	// hasAttribute
+
+	features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) {
+		return node.hasAttribute(attribute);
+	} : function(node, attribute) {
+		node = node.getAttributeNode(attribute);
+		return !!(node && (node.specified || node.nodeValue));
+	};
+
+	// contains
+	// FIXME: Add specs: local.contains should be different for xml and html documents?
+	features.contains = (root && this.isNativeCode(root.contains)) ? function(context, node){
+		return context.contains(node);
+	} : (root && root.compareDocumentPosition) ? function(context, node){
+		return context === node || !!(context.compareDocumentPosition(node) & 16);
+	} : function(context, node){
+		if (node) do {
+			if (node === context) return true;
+		} while ((node = node.parentNode));
+		return false;
+	};
+
+	// document order sorting
+	// credits to Sizzle (http://sizzlejs.com/)
+
+	features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+		if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+		return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+	} : ('sourceIndex' in root) ? function(a, b){
+		if (!a.sourceIndex || !b.sourceIndex) return 0;
+		return a.sourceIndex - b.sourceIndex;
+	} : (document.createRange) ? function(a, b){
+		if (!a.ownerDocument || !b.ownerDocument) return 0;
+		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+		aRange.setStart(a, 0);
+		aRange.setEnd(a, 0);
+		bRange.setStart(b, 0);
+		bRange.setEnd(b, 0);
+		return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+	} : null ;
+
+	root = null;
+
+	for (feature in features){
+		this[feature] = features[feature];
+	}
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+	reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+	qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+	var found = this.found = (first) ? null : (append || []);
+
+	if (!context) return found;
+	else if (context.navigator) context = context.document; // Convert the node from a window to a document
+	else if (!context.nodeType) return found;
+
+	// setup
+
+	var parsed, i,
+		uniques = this.uniques = {},
+		hasOthers = !!(append && append.length),
+		contextIsDocument = (context.nodeType == 9);
+
+	if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+	// avoid duplicating items already in the append array
+	if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+	// expression checks
+
+	if (typeof expression == 'string'){ // expression is a string
+
+		/*<simple-selectors-override>*/
+		var simpleSelector = expression.match(reSimpleSelector);
+		simpleSelectors: if (simpleSelector) {
+
+			var symbol = simpleSelector[1],
+				name = simpleSelector[2],
+				node, nodes;
+
+			if (!symbol){
+
+				if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+				nodes = context.getElementsByTagName(name);
+				if (first) return nodes[0] || null;
+				for (i = 0; node = nodes[i++];){
+					if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+				}
+
+			} else if (symbol == '#'){
+
+				if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+				node = context.getElementById(name);
+				if (!node) return found;
+				if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+				if (first) return node || null;
+				if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+			} else if (symbol == '.'){
+
+				if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+				if (context.getElementsByClassName && !this.brokenGEBCN){
+					nodes = context.getElementsByClassName(name);
+					if (first) return nodes[0] || null;
+					for (i = 0; node = nodes[i++];){
+						if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+					}
+				} else {
+					var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+					nodes = context.getElementsByTagName('*');
+					for (i = 0; node = nodes[i++];){
+						className = node.className;
+						if (!(className && matchClass.test(className))) continue;
+						if (first) return node;
+						if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+					}
+				}
+
+			}
+
+			if (hasOthers) this.sort(found);
+			return (first) ? null : found;
+
+		}
+		/*</simple-selectors-override>*/
+
+		/*<query-selector-override>*/
+		querySelector: if (context.querySelectorAll) {
+
+			if (!this.isHTMLDocument
+				|| qsaFailExpCache[expression]
+				//TODO: only skip when expression is actually mixed case
+				|| this.brokenMixedCaseQSA
+				|| (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+				|| (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+				|| (!contextIsDocument //Abort when !contextIsDocument and...
+					//  there are multiple expressions in the selector
+					//  since we currently only fix non-document rooted QSA for single expression selectors
+					&& expression.indexOf(',') > -1
+				)
+				|| Slick.disableQSA
+			) break querySelector;
+
+			var _expression = expression, _context = context;
+			if (!contextIsDocument){
+				// non-document rooted QSA
+				// credits to Andrew Dupont
+				var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+				_context.setAttribute('id', slickid);
+				_expression = '#' + slickid + ' ' + _expression;
+				context = _context.parentNode;
+			}
+
+			try {
+				if (first) return context.querySelector(_expression) || null;
+				else nodes = context.querySelectorAll(_expression);
+			} catch(e) {
+				qsaFailExpCache[expression] = 1;
+				break querySelector;
+			} finally {
+				if (!contextIsDocument){
+					if (currentId) _context.setAttribute('id', currentId);
+					else _context.removeAttribute('id');
+					context = _context;
+				}
+			}
+
+			if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+				if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+			} else for (i = 0; node = nodes[i++];){
+				if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+			}
+
+			if (hasOthers) this.sort(found);
+			return found;
+
+		}
+		/*</query-selector-override>*/
+
+		parsed = this.Slick.parse(expression);
+		if (!parsed.length) return found;
+	} else if (expression == null){ // there is no expression
+		return found;
+	} else if (expression.Slick){ // expression is a parsed Slick object
+		parsed = expression;
+	} else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+		(found) ? found.push(expression) : found = expression;
+		return found;
+	} else { // other junk
+		return found;
+	}
+
+	/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+	// cache elements for the nth selectors
+
+	this.posNTH = {};
+	this.posNTHLast = {};
+	this.posNTHType = {};
+	this.posNTHTypeLast = {};
+
+	/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+	// if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+	this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+	if (found == null) found = [];
+
+	// default engine
+
+	var j, m, n;
+	var combinator, tag, id, classList, classes, attributes, pseudos;
+	var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+	search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+		combinator = 'combinator:' + currentBit.combinator;
+		if (!this[combinator]) continue search;
+
+		tag        = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+		id         = currentBit.id;
+		classList  = currentBit.classList;
+		classes    = currentBit.classes;
+		attributes = currentBit.attributes;
+		pseudos    = currentBit.pseudos;
+		lastBit    = (j === (currentExpression.length - 1));
+
+		this.bitUniques = {};
+
+		if (lastBit){
+			this.uniques = uniques;
+			this.found = found;
+		} else {
+			this.uniques = {};
+			this.found = [];
+		}
+
+		if (j === 0){
+			this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+			if (first && lastBit && found.length) break search;
+		} else {
+			if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+				this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+				if (found.length) break search;
+			} else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+		}
+
+		currentItems = this.found;
+	}
+
+	// should sort if there are nodes in append and if you pass multiple expressions.
+	if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+	return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+	var uid = node.getAttribute(this.uidk);
+	if (!uid){
+		uid = this.uidx++;
+		node.setAttribute(this.uidk, uid);
+	}
+	return uid;
+};
+
+local.getUIDHTML = function(node){
+	return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+	if (!this.documentSorter) return results;
+	results.sort(this.documentSorter);
+	return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+	var parsed = argument.match(this.matchNTH);
+	if (!parsed) return false;
+	var special = parsed[2] || false;
+	var a = parsed[1] || 1;
+	if (a == '-') a = -1;
+	var b = +parsed[3] || 0;
+	parsed =
+		(special == 'n')	? {a: a, b: b} :
+		(special == 'odd')	? {a: 2, b: 1} :
+		(special == 'even')	? {a: 2, b: 0} : {a: 0, b: a};
+
+	return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+	return function(node, argument){
+		var uid = this.getUID(node);
+		if (!this[positions][uid]){
+			var parent = node.parentNode;
+			if (!parent) return false;
+			var el = parent[child], count = 1;
+			if (ofType){
+				var nodeName = node.nodeName;
+				do {
+					if (el.nodeName != nodeName) continue;
+					this[positions][this.getUID(el)] = count++;
+				} while ((el = el[sibling]));
+			} else {
+				do {
+					if (el.nodeType != 1) continue;
+					this[positions][this.getUID(el)] = count++;
+				} while ((el = el[sibling]));
+			}
+		}
+		argument = argument || 'n';
+		var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+		if (!parsed) return false;
+		var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+		if (a == 0) return b == pos;
+		if (a > 0){
+			if (pos < b) return false;
+		} else {
+			if (b < pos) return false;
+		}
+		return ((pos - b) % a) == 0;
+	};
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+	if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+	var uid = this.getUID(node);
+	if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+		this.uniques[uid] = true;
+		this.found.push(node);
+	}
+};
+
+local.matchNode = function(node, selector){
+	if (this.isHTMLDocument && this.nativeMatchesSelector){
+		try {
+			return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+		} catch(matchError) {}
+	}
+
+	var parsed = this.Slick.parse(selector);
+	if (!parsed) return true;
+
+	// simple (single) selectors
+	var expressions = parsed.expressions, simpleExpCounter = 0, i;
+	for (i = 0; (currentExpression = expressions[i]); i++){
+		if (currentExpression.length == 1){
+			var exp = currentExpression[0];
+			if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+			simpleExpCounter++;
+		}
+	}
+
+	if (simpleExpCounter == parsed.length) return false;
+
+	var nodes = this.search(this.document, parsed), item;
+	for (i = 0; item = nodes[i++];){
+		if (item === node) return true;
+	}
+	return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+	var pseudoName = 'pseudo:' + name;
+	if (this[pseudoName]) return this[pseudoName](node, argument);
+	var attribute = this.getAttribute(node, name);
+	return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+	if (tag){
+		var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+		if (tag == '*'){
+			if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+		} else {
+			if (nodeName != tag) return false;
+		}
+	}
+
+	if (id && node.getAttribute('id') != id) return false;
+
+	var i, part, cls;
+	if (classes) for (i = classes.length; i--;){
+		cls = node.getAttribute('class') || node.className;
+		if (!(cls && classes[i].regexp.test(cls))) return false;
+	}
+	if (attributes) for (i = attributes.length; i--;){
+		part = attributes[i];
+		if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+	}
+	if (pseudos) for (i = pseudos.length; i--;){
+		part = pseudos[i];
+		if (!this.matchPseudo(node, part.key, part.value)) return false;
+	}
+	return true;
+};
+
+var combinators = {
+
+	' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+		var i, item, children;
+
+		if (this.isHTMLDocument){
+			getById: if (id){
+				item = this.document.getElementById(id);
+				if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+					// all[id] returns all the elements with that name or id inside node
+					// if theres just one it will return the element, else it will be a collection
+					children = node.all[id];
+					if (!children) return;
+					if (!children[0]) children = [children];
+					for (i = 0; item = children[i++];){
+						var idNode = item.getAttributeNode('id');
+						if (idNode && idNode.nodeValue == id){
+							this.push(item, tag, null, classes, attributes, pseudos);
+							break;
+						}
+					}
+					return;
+				}
+				if (!item){
+					// if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+					if (this.contains(this.root, node)) return;
+					else break getById;
+				} else if (this.document !== node && !this.contains(node, item)) return;
+				this.push(item, tag, null, classes, attributes, pseudos);
+				return;
+			}
+			getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+				children = node.getElementsByClassName(classList.join(' '));
+				if (!(children && children.length)) break getByClass;
+				for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+				return;
+			}
+		}
+		getByTag: {
+			children = node.getElementsByTagName(tag);
+			if (!(children && children.length)) break getByTag;
+			if (!this.brokenStarGEBTN) tag = null;
+			for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+		}
+	},
+
+	'>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+		if ((node = node.firstChild)) do {
+			if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+		} while ((node = node.nextSibling));
+	},
+
+	'+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+		while ((node = node.nextSibling)) if (node.nodeType == 1){
+			this.push(node, tag, id, classes, attributes, pseudos);
+			break;
+		}
+	},
+
+	'^': function(node, tag, id, classes, attributes, pseudos){ // first child
+		node = node.firstChild;
+		if (node){
+			if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+			else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+		}
+	},
+
+	'~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+		while ((node = node.nextSibling)){
+			if (node.nodeType != 1) continue;
+			var uid = this.getUID(node);
+			if (this.bitUniques[uid]) break;
+			this.bitUniques[uid] = true;
+			this.push(node, tag, id, classes, attributes, pseudos);
+		}
+	},
+
+	'++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+		this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+		this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+	},
+
+	'~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+		this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+		this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+	},
+
+	'!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+		while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+	},
+
+	'!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+		node = node.parentNode;
+		if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+	},
+
+	'!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+		while ((node = node.previousSibling)) if (node.nodeType == 1){
+			this.push(node, tag, id, classes, attributes, pseudos);
+			break;
+		}
+	},
+
+	'!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+		node = node.lastChild;
+		if (node){
+			if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+			else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+		}
+	},
+
+	'!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+		while ((node = node.previousSibling)){
+			if (node.nodeType != 1) continue;
+			var uid = this.getUID(node);
+			if (this.bitUniques[uid]) break;
+			this.bitUniques[uid] = true;
+			this.push(node, tag, id, classes, attributes, pseudos);
+		}
+	}
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+	/*<pseudo-selectors>*/
+
+	'empty': function(node){
+		var child = node.firstChild;
+		return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+	},
+
+	'not': function(node, expression){
+		return !this.matchNode(node, expression);
+	},
+
+	'contains': function(node, text){
+		return (node.innerText || node.textContent || '').indexOf(text) > -1;
+	},
+
+	'first-child': function(node){
+		while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+		return true;
+	},
+
+	'last-child': function(node){
+		while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+		return true;
+	},
+
+	'only-child': function(node){
+		var prev = node;
+		while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+		var next = node;
+		while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+		return true;
+	},
+
+	/*<nth-pseudo-selectors>*/
+
+	'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+	'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+	'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+	'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+	'index': function(node, index){
+		return this['pseudo:nth-child'](node, '' + index + 1);
+	},
+
+	'even': function(node){
+		return this['pseudo:nth-child'](node, '2n');
+	},
+
+	'odd': function(node){
+		return this['pseudo:nth-child'](node, '2n+1');
+	},
+
+	/*</nth-pseudo-selectors>*/
+
+	/*<of-type-pseudo-selectors>*/
+
+	'first-of-type': function(node){
+		var nodeName = node.nodeName;
+		while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+		return true;
+	},
+
+	'last-of-type': function(node){
+		var nodeName = node.nodeName;
+		while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+		return true;
+	},
+
+	'only-of-type': function(node){
+		var prev = node, nodeName = node.nodeName;
+		while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+		var next = node;
+		while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+		return true;
+	},
+
+	/*</of-type-pseudo-selectors>*/
+
+	// custom pseudos
+
+	'enabled': function(node){
+		return !node.disabled;
+	},
+
+	'disabled': function(node){
+		return node.disabled;
+	},
+
+	'checked': function(node){
+		return node.checked || node.selected;
+	},
+
+	'focus': function(node){
+		return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+	},
+
+	'root': function(node){
+		return (node === this.root);
+	},
+
+	'selected': function(node){
+		return node.selected;
+	}
+
+	/*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+	'class': function(){
+		return this.getAttribute('class') || this.className;
+	},
+
+	'for': function(){
+		return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+	},
+
+	'href': function(){
+		return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+	},
+
+	'style': function(){
+		return (this.style) ? this.style.cssText : this.getAttribute('style');
+	},
+
+	'tabindex': function(){
+		var attributeNode = this.getAttributeNode('tabindex');
+		return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+	},
+
+	'type': function(){
+		return this.getAttribute('type');
+	},
+
+	'maxlength': function(){
+		var attributeNode = this.getAttributeNode('maxLength');
+		return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+	}
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.6';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+	return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+	return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+	local.setDocument(container);
+	return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+	local.setDocument(node);
+	return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+	local.setDocument(node);
+	return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+	if (!(node && selector)) return false;
+	if (!selector || selector === node) return true;
+	local.setDocument(node);
+	return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+	local.attributeGetters[name] = fn;
+	return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+	return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+	local['pseudo:' + name] = function(node, argument){
+		return fn.call(node, argument);
+	};
+	return this;
+};
+
+Slick.lookupPseudo = function(name){
+	var pseudo = local['pseudo:' + name];
+	if (pseudo) return function(argument){
+		return pseudo.call(this, argument);
+	};
+	return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+	local.override(regexp, fn);
+	return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+	return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, Iframe, Selectors]
+
+...
+*/
+
+var Element = function(tag, props){
+	var konstructor = Element.Constructors[tag];
+	if (konstructor) return konstructor(props);
+	if (typeof tag != 'string') return document.id(tag).set(props);
+
+	if (!props) props = {};
+
+	if (!(/^[\w-]+$/).test(tag)){
+		var parsed = Slick.parse(tag).expressions[0][0];
+		tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+		if (parsed.id && props.id == null) props.id = parsed.id;
+
+		var attributes = parsed.attributes;
+		if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+			attr = attributes[i];
+			if (props[attr.key] != null) continue;
+
+			if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+			else if (!attr.value && !attr.operator) props[attr.key] = true;
+		}
+
+		if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+	}
+
+	return document.newElement(tag, props);
+};
+
+if (Browser.Element) Element.prototype = Browser.Element.prototype;
+
+new Type('Element', Element).mirror(function(name){
+	if (Array.prototype[name]) return;
+
+	var obj = {};
+	obj[name] = function(){
+		var results = [], args = arguments, elements = true;
+		for (var i = 0, l = this.length; i < l; i++){
+			var element = this[i], result = results[i] = element[name].apply(element, args);
+			elements = (elements && typeOf(result) == 'element');
+		}
+		return (elements) ? new Elements(results) : results;
+	};
+
+	Elements.implement(obj);
+});
+
+if (!Browser.Element){
+	Element.parent = Object;
+
+	Element.Prototype = {'$family': Function.from('element').hide()};
+
+	Element.mirror(function(name, method){
+		Element.Prototype[name] = method;
+	});
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+	var params = Array.link(arguments, {
+		properties: Type.isObject,
+		iframe: function(obj){
+			return (obj != null);
+		}
+	});
+
+	var props = params.properties || {}, iframe;
+	if (params.iframe) iframe = document.id(params.iframe);
+	var onload = props.onload || function(){};
+	delete props.onload;
+	props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+	iframe = new Element(iframe || 'iframe', props);
+
+	var onLoad = function(){
+		onload.call(iframe.contentWindow);
+	};
+
+	if (window.frames[props.id]) onLoad();
+	else iframe.addListener('load', onLoad);
+	return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+	if (nodes && nodes.length){
+		var uniques = {}, node;
+		for (var i = 0; node = nodes[i++];){
+			var uid = Slick.uidOf(node);
+			if (!uniques[uid]){
+				uniques[uid] = true;
+				this.push(node);
+			}
+		}
+	}
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+	filter: function(filter, bind){
+		if (!filter) return this;
+		return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+			return item.match(filter);
+		} : filter, bind));
+	}.protect(),
+
+	push: function(){
+		var length = this.length;
+		for (var i = 0, l = arguments.length; i < l; i++){
+			var item = document.id(arguments[i]);
+			if (item) this[length++] = item;
+		}
+		return (this.length = length);
+	}.protect(),
+
+	unshift: function(){
+		var items = [];
+		for (var i = 0, l = arguments.length; i < l; i++){
+			var item = document.id(arguments[i]);
+			if (item) items.push(item);
+		}
+		return Array.prototype.unshift.apply(this, items);
+	}.protect(),
+
+	concat: function(){
+		var newElements = new Elements(this);
+		for (var i = 0, l = arguments.length; i < l; i++){
+			var item = arguments[i];
+			if (Type.isEnumerable(item)) newElements.append(item);
+			else newElements.push(item);
+		}
+		return newElements;
+	}.protect(),
+
+	append: function(collection){
+		for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+		return this;
+	}.protect(),
+
+	empty: function(){
+		while (this.length) delete this[--this.length];
+		return this;
+	}.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+	var length = this.length;
+	var result = splice.apply(this, arguments);
+	while (length >= this.length) delete this[length--];
+	return result;
+}.protect());
+
+Elements.implement(Array.prototype);
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+	var x = document.createElement('<input name=x>');
+	createElementAcceptsHTML = (x.name == 'x');
+} catch(e){}
+
+var escapeQuotes = function(html){
+	return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+Document.implement({
+
+	newElement: function(tag, props){
+		if (props && props.checked != null) props.defaultChecked = props.checked;
+		/*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+		if (createElementAcceptsHTML && props){
+			tag = '<' + tag;
+			if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+			if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+			tag += '>';
+			delete props.name;
+			delete props.type;
+		}
+		/*</ltIE8>*/
+		return this.id(this.createElement(tag)).set(props);
+	}
+
+});
+
+})();
+
+Document.implement({
+
+	newTextNode: function(text){
+		return this.createTextNode(text);
+	},
+
+	getDocument: function(){
+		return this;
+	},
+
+	getWindow: function(){
+		return this.window;
+	},
+
+	id: (function(){
+
+		var types = {
+
+			string: function(id, nocash, doc){
+				id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+				return (id) ? types.element(id, nocash) : null;
+			},
+
+			element: function(el, nocash){
+				$uid(el);
+				if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+					Object.append(el, Element.Prototype);
+				}
+				return el;
+			},
+
+			object: function(obj, nocash, doc){
+				if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+				return null;
+			}
+
+		};
+
+		types.textnode = types.whitespace = types.window = types.document = function(zero){
+			return zero;
+		};
+
+		return function(el, nocash, doc){
+			if (el && el.$family && el.uid) return el;
+			var type = typeOf(el);
+			return (types[type]) ? types[type](el, nocash, doc || document) : null;
+		};
+
+	})()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+	return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+	getDocument: function(){
+		return this.document;
+	},
+
+	getWindow: function(){
+		return this;
+	}
+
+});
+
+[Document, Element].invoke('implement', {
+
+	getElements: function(expression){
+		return Slick.search(this, expression, new Elements);
+	},
+
+	getElement: function(expression){
+		return document.id(Slick.find(this, expression));
+	}
+
+});
+
+var contains = {contains: function(element){
+	return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+	if (!expression) return combinator;
+
+	expression = Object.clone(Slick.parse(expression));
+
+	var expressions = expression.expressions;
+	for (var i = expressions.length; i--;)
+		expressions[i][0].combinator = combinator;
+
+	return expression;
+};
+
+Object.forEach({
+	getNext: '~',
+	getPrevious: '!~',
+	getParent: '!'
+}, function(combinator, method){
+	Element.implement(method, function(expression){
+		return this.getElement(injectCombinator(expression, combinator));
+	});
+});
+
+Object.forEach({
+	getAllNext: '~',
+	getAllPrevious: '!~',
+	getSiblings: '~~',
+	getChildren: '>',
+	getParents: '!'
+}, function(combinator, method){
+	Element.implement(method, function(expression){
+		return this.getElements(injectCombinator(expression, combinator));
+	});
+});
+
+Element.implement({
+
+	getFirst: function(expression){
+		return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+	},
+
+	getLast: function(expression){
+		return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+	},
+
+	getWindow: function(){
+		return this.ownerDocument.window;
+	},
+
+	getDocument: function(){
+		return this.ownerDocument;
+	},
+
+	getElementById: function(id){
+		return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+	},
+
+	match: function(expression){
+		return !expression || Slick.match(this, expression);
+	}
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+	if (arguments.length == 1){
+		if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+		else if (Type.isEnumerable(selector)) return new Elements(selector);
+	}
+	return new Elements(arguments);
+});
+
+(function(){
+
+// Inserters
+
+var inserters = {
+
+	before: function(context, element){
+		var parent = element.parentNode;
+		if (parent) parent.insertBefore(context, element);
+	},
+
+	after: function(context, element){
+		var parent = element.parentNode;
+		if (parent) parent.insertBefore(context, element.nextSibling);
+	},
+
+	bottom: function(context, element){
+		element.appendChild(context);
+	},
+
+	top: function(context, element){
+		element.insertBefore(context, element.firstChild);
+	}
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+	'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+	'frameBorder', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+	properties[property.toLowerCase()] = property;
+});
+
+Object.append(properties, {
+	'html': 'innerHTML',
+	'text': (function(){
+		var temp = document.createElement('div');
+		return (temp.innerText == null) ? 'textContent' : 'innerText';
+	})()
+});
+
+Object.forEach(properties, function(real, key){
+	propertySetters[key] = function(node, value){
+		node[real] = value;
+	};
+	propertyGetters[key] = function(node){
+		return node[real];
+	};
+});
+
+// Booleans
+
+var bools = [
+	'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+	'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+	'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+	'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+	var lower = bool.toLowerCase();
+	booleans[lower] = bool;
+	propertySetters[lower] = function(node, value){
+		node[bool] = !!value;
+	};
+	propertyGetters[lower] = function(node){
+		return !!node[bool];
+	};
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+	'class': function(node, value){
+		('className' in node) ? node.className = value : node.setAttribute('class', value);
+	},
+
+	'for': function(node, value){
+		('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+	},
+
+	'style': function(node, value){
+		(node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+	}
+
+});
+
+/* getProperty, setProperty */
+
+Element.implement({
+
+	setProperty: function(name, value){
+		var setter = propertySetters[name.toLowerCase()];
+		if (setter) setter(this, value);
+		else this.setAttribute(name, value);
+		return this;
+	},
+
+	setProperties: function(attributes){
+		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+		return this;
+	},
+
+	getProperty: function(name){
+		var getter = propertyGetters[name.toLowerCase()];
+		if (getter) return getter(this);
+		var result = Slick.getAttribute(this, name);
+		return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+	},
+
+	getProperties: function(){
+		var args = Array.from(arguments);
+		return args.map(this.getProperty, this).associate(args);
+	},
+
+	removeProperty: function(name){
+		name = name.toLowerCase();
+		if (booleans[name]) this.setProperty(name, false);
+		this.removeAttribute(name);
+		return this;
+	},
+
+	removeProperties: function(){
+		Array.each(arguments, this.removeProperty, this);
+		return this;
+	},
+
+	set: function(prop, value){
+		var property = Element.Properties[prop];
+		(property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+	}.overloadSetter(),
+
+	get: function(prop){
+		var property = Element.Properties[prop];
+		return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+	}.overloadGetter(),
+
+	erase: function(prop){
+		var property = Element.Properties[prop];
+		(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+		return this;
+	},
+
+	hasClass: function(className){
+		return this.className.clean().contains(className, ' ');
+	},
+
+	addClass: function(className){
+		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
+		return this;
+	},
+
+	removeClass: function(className){
+		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
+		return this;
+	},
+
+	toggleClass: function(className, force){
+		if (force == null) force = !this.hasClass(className);
+		return (force) ? this.addClass(className) : this.removeClass(className);
+	},
+
+	adopt: function(){
+		var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+		if (length > 1) parent = fragment = document.createDocumentFragment();
+
+		for (var i = 0; i < length; i++){
+			var element = document.id(elements[i], true);
+			if (element) parent.appendChild(element);
+		}
+
+		if (fragment) this.appendChild(fragment);
+
+		return this;
+	},
+
+	appendText: function(text, where){
+		return this.grab(this.getDocument().newTextNode(text), where);
+	},
+
+	grab: function(el, where){
+		inserters[where || 'bottom'](document.id(el, true), this);
+		return this;
+	},
+
+	inject: function(el, where){
+		inserters[where || 'bottom'](this, document.id(el, true));
+		return this;
+	},
+
+	replaces: function(el){
+		el = document.id(el, true);
+		el.parentNode.replaceChild(this, el);
+		return this;
+	},
+
+	wraps: function(el, where){
+		el = document.id(el, true);
+		return this.replaces(el).grab(el, where);
+	},
+
+	getSelected: function(){
+		this.selectedIndex; // Safari 3.2.1
+		return new Elements(Array.from(this.options).filter(function(option){
+			return option.selected;
+		}));
+	},
+
+	toQueryString: function(){
+		var queryString = [];
+		this.getElements('input, select, textarea').each(function(el){
+			var type = el.type;
+			if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+			var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+				// IE
+				return document.id(opt).get('value');
+			}) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+			Array.from(value).each(function(val){
+				if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+			});
+		});
+		return queryString.join('&');
+	}
+
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+	return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+	var uid = item.uid;
+	if (item.removeEvents) item.removeEvents();
+	if (item.clearAttributes) item.clearAttributes();
+	if (uid != null){
+		delete collected[uid];
+		delete storage[uid];
+	}
+	return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+	destroy: function(){
+		var children = clean(this).getElementsByTagName('*');
+		Array.each(children, clean);
+		Element.dispose(this);
+		return null;
+	},
+
+	empty: function(){
+		Array.from(this.childNodes).each(Element.dispose);
+		return this;
+	},
+
+	dispose: function(){
+		return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+	},
+
+	clone: function(contents, keepid){
+		contents = contents !== false;
+		var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+		if (contents){
+			ce.append(Array.from(clone.getElementsByTagName('*')));
+			te.append(Array.from(this.getElementsByTagName('*')));
+		}
+
+		for (i = ce.length; i--;){
+			var node = ce[i], element = te[i];
+			if (!keepid) node.removeAttribute('id');
+			/*<ltIE9>*/
+			if (node.clearAttributes){
+				node.clearAttributes();
+				node.mergeAttributes(element);
+				node.removeAttribute('uid');
+				if (node.options){
+					var no = node.options, eo = element.options;
+					for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+				}
+			}
+			/*</ltIE9>*/
+			var prop = formProps[element.tagName.toLowerCase()];
+			if (prop && element[prop]) node[prop] = element[prop];
+		}
+
+		/*<ltIE9>*/
+		if (Browser.ie){
+			var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+			for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+		}
+		/*</ltIE9>*/
+		return document.id(clone);
+	}
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+	addListener: function(type, fn){
+		if (type == 'unload'){
+			var old = fn, self = this;
+			fn = function(){
+				self.removeListener('unload', fn);
+				old();
+			};
+		} else {
+			collected[$uid(this)] = this;
+		}
+		if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+		else this.attachEvent('on' + type, fn);
+		return this;
+	},
+
+	removeListener: function(type, fn){
+		if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+		else this.detachEvent('on' + type, fn);
+		return this;
+	},
+
+	retrieve: function(property, dflt){
+		var storage = get($uid(this)), prop = storage[property];
+		if (dflt != null && prop == null) prop = storage[property] = dflt;
+		return prop != null ? prop : null;
+	},
+
+	store: function(property, value){
+		var storage = get($uid(this));
+		storage[property] = value;
+		return this;
+	},
+
+	eliminate: function(property){
+		var storage = get($uid(this));
+		delete storage[property];
+		return this;
+	}
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener) window.addListener('unload', function(){
+	Object.each(collected, clean);
+	if (window.CollectGarbage) CollectGarbage();
+});
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+	set: function(style){
+		this.style.cssText = style;
+	},
+
+	get: function(){
+		return this.style.cssText;
+	},
+
+	erase: function(){
+		this.style.cssText = '';
+	}
+
+};
+
+Element.Properties.tag = {
+
+	get: function(){
+		return this.tagName.toLowerCase();
+	}
+
+};
+
+/*<!webkit>*/
+Element.Properties.html = (function(){
+
+	var tableTest = Function.attempt(function(){
+		var table = document.createElement('table');
+		table.innerHTML = '<tr><td></td></tr>';
+	});
+
+	var wrapper = document.createElement('div');
+
+	var translations = {
+		table: [1, '<table>', '</table>'],
+		select: [1, '<select>', '</select>'],
+		tbody: [2, '<table><tbody>', '</tbody></table>'],
+		tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+	};
+	translations.thead = translations.tfoot = translations.tbody;
+
+	/*<ltIE9>*/
+	// technique by jdbarlett - http://jdbartlett.com/innershiv/
+	wrapper.innerHTML = '<nav></nav>';
+	var HTML5Test = wrapper.childNodes.length == 1;
+	if (!HTML5Test){
+		var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+			fragment = document.createDocumentFragment(), l = tags.length;
+		while (l--) fragment.createElement(tags[l]);
+		fragment.appendChild(wrapper);
+	}
+	/*</ltIE9>*/
+
+	var html = {
+		set: function(html){
+			if (typeOf(html) == 'array') html = html.join('');
+
+			var wrap = (!tableTest && translations[this.get('tag')]);
+			/*<ltIE9>*/
+			if (!wrap && !HTML5Test) wrap = [0, '', ''];
+			/*</ltIE9>*/
+			if (wrap){
+				var first = wrapper;
+				first.innerHTML = wrap[1] + html + wrap[2];
+				for (var i = wrap[0]; i--;) first = first.firstChild;
+				this.empty().adopt(first.childNodes);
+			} else {
+				this.innerHTML = html;
+			}
+		}
+	};
+
+	html.erase = html.set;
+
+	return html;
+})();
+/*</!webkit>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+	set: function(value){
+		var tag = this.get('tag');
+		if (tag != 'select') return this.setProperty('value', value);
+		var options = this.getElements('option');
+		for (var i = 0; i < options.length; i++){
+			var option = options[i],
+				attr = option.getAttributeNode('value'),
+				optionValue = (attr && attr.specified) ? option.value : option.get('text');
+			if (optionValue == value) return option.selected = true;
+		}
+	},
+
+	get: function(){
+		var option = this, tag = option.get('tag');
+
+		if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+		if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+		var attr = option.getAttributeNode('value');
+		return (attr && attr.specified) ? option.value : option.get('text');
+	}
+
+};
+/*</ltIE9>*/
+
+})();
+
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html;
+
+Element.Properties.styles = {set: function(styles){
+	this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+	hasFilter = (html.style.filter != null),
+	reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+	element.store('$opacity', opacity);
+	element.style.visibility = opacity > 0 ? 'visible' : 'hidden';
+};
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+	element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+	if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+	opacity = (opacity * 100).limit(0, 100).round();
+	opacity = (opacity == 100) ? '' : 'alpha(opacity=' + opacity + ')';
+	var filter = element.style.filter || element.getComputedStyle('filter') || '';
+	element.style.filter = reAlpha.test(filter) ? filter.replace(reAlpha, opacity) : filter + opacity;
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+	var opacity = element.style.opacity || element.getComputedStyle('opacity');
+	return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+	var filter = (element.style.filter || element.getComputedStyle('filter')),
+		opacity;
+	if (filter) opacity = filter.match(reAlpha);
+	return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+	var opacity = element.retrieve('$opacity');
+	if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+	return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat';
+
+Element.implement({
+
+	getComputedStyle: function(property){
+		if (this.currentStyle) return this.currentStyle[property.camelCase()];
+		var defaultView = Element.getDocument(this).defaultView,
+			computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+		return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : null;
+	},
+
+	setStyle: function(property, value){
+		if (property == 'opacity'){
+			setOpacity(this, parseFloat(value));
+			return this;
+		}
+		property = (property == 'float' ? floatName : property).camelCase();
+		if (typeOf(value) != 'string'){
+			var map = (Element.Styles[property] || '@').split(' ');
+			value = Array.from(value).map(function(val, i){
+				if (!map[i]) return '';
+				return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+			}).join(' ');
+		} else if (value == String(Number(value))){
+			value = Math.round(value);
+		}
+		this.style[property] = value;
+		return this;
+	},
+
+	getStyle: function(property){
+		if (property == 'opacity') return getOpacity(this);
+		property = (property == 'float' ? floatName : property).camelCase();
+		var result = this.style[property];
+		if (!result || property == 'zIndex'){
+			result = [];
+			for (var style in Element.ShortStyles){
+				if (property != style) continue;
+				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
+				return result.join(' ');
+			}
+			result = this.getComputedStyle(property);
+		}
+		if (result){
+			result = String(result);
+			var color = result.match(/rgba?\([\d\s,]+\)/);
+			if (color) result = result.replace(color[0], color[0].rgbToHex());
+		}
+		if (Browser.opera || (Browser.ie && isNaN(parseFloat(result)))){
+			if ((/^(height|width)$/).test(property)){
+				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+				values.each(function(value){
+					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+				}, this);
+				return this['offset' + property.capitalize()] - size + 'px';
+			}
+			if (Browser.opera && String(result).indexOf('px') != -1) return result;
+			if ((/^border(.+)Width|margin|padding/).test(property)) return '0px';
+		}
+		return result;
+	},
+
+	setStyles: function(styles){
+		for (var style in styles) this.setStyle(style, styles[style]);
+		return this;
+	},
+
+	getStyles: function(){
+		var result = {};
+		Array.flatten(arguments).each(function(key){
+			result[key] = this.getStyle(key);
+		}, this);
+		return result;
+	}
+
+});
+
+Element.Styles = {
+	left: '@px', top: '@px', bottom: '@px', right: '@px',
+	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+	var Short = Element.ShortStyles;
+	var All = Element.Styles;
+	['margin', 'padding'].each(function(style){
+		var sd = style + direction;
+		Short[style][sd] = All[sd] = '@px';
+	});
+	var bd = 'border' + direction;
+	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+	Short[bd] = {};
+	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+	this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+	addEvent: function(type, fn){
+		var events = this.retrieve('events', {});
+		if (!events[type]) events[type] = {keys: [], values: []};
+		if (events[type].keys.contains(fn)) return this;
+		events[type].keys.push(fn);
+		var realType = type,
+			custom = Element.Events[type],
+			condition = fn,
+			self = this;
+		if (custom){
+			if (custom.onAdd) custom.onAdd.call(this, fn, type);
+			if (custom.condition){
+				condition = function(event){
+					if (custom.condition.call(this, event, type)) return fn.call(this, event);
+					return true;
+				};
+			}
+			if (custom.base) realType = Function.from(custom.base).call(this, type);
+		}
+		var defn = function(){
+			return fn.call(self);
+		};
+		var nativeEvent = Element.NativeEvents[realType];
+		if (nativeEvent){
+			if (nativeEvent == 2){
+				defn = function(event){
+					event = new DOMEvent(event, self.getWindow());
+					if (condition.call(self, event) === false) event.stop();
+				};
+			}
+			this.addListener(realType, defn, arguments[2]);
+		}
+		events[type].values.push(defn);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		var list = events[type];
+		var index = list.keys.indexOf(fn);
+		if (index == -1) return this;
+		var value = list.values[index];
+		delete list.keys[index];
+		delete list.values[index];
+		var custom = Element.Events[type];
+		if (custom){
+			if (custom.onRemove) custom.onRemove.call(this, fn, type);
+			if (custom.base) type = Function.from(custom.base).call(this, type);
+		}
+		return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+	},
+
+	addEvents: function(events){
+		for (var event in events) this.addEvent(event, events[event]);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if (typeOf(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		var attached = this.retrieve('events');
+		if (!attached) return this;
+		if (!events){
+			for (type in attached) this.removeEvents(type);
+			this.eliminate('events');
+		} else if (attached[events]){
+			attached[events].keys.each(function(fn){
+				this.removeEvent(events, fn);
+			}, this);
+			delete attached[events];
+		}
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		args = Array.from(args);
+
+		events[type].keys.each(function(fn){
+			if (delay) fn.delay(delay, this, args);
+			else fn.apply(this, args);
+		}, this);
+		return this;
+	},
+
+	cloneEvents: function(from, type){
+		from = document.id(from);
+		var events = from.retrieve('events');
+		if (!events) return this;
+		if (!type){
+			for (var eventType in events) this.cloneEvents(from, eventType);
+		} else if (events[type]){
+			events[type].keys.each(function(fn){
+				this.addEvent(type, fn);
+			}, this);
+		}
+		return this;
+	}
+
+});
+
+Element.NativeEvents = {
+	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+	keydown: 2, keypress: 2, keyup: 2, //keyboard
+	orientationchange: 2, // mobile
+	touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+	gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, oninput: 2, //form elements
+	load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+	error: 1, abort: 1, scroll: 1 //misc
+};
+
+var check = function(event){
+	var related = event.relatedTarget;
+	if (related == null) return true;
+	if (!related) return false;
+	return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+Element.Events = {
+
+	mouseenter: {
+		base: 'mouseover',
+		condition: check
+	},
+
+	mouseleave: {
+		base: 'mouseout',
+		condition: check
+	},
+
+	mousewheel: {
+		base: (Browser.firefox) ? 'DOMMouseScroll' : 'mousewheel'
+	}
+
+};
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+	Element.NativeEvents.propertychange = 2;
+	Element.Events.change = {
+		base: function(){
+			var type = this.type;
+			return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'
+		},
+		condition: function(event){
+			return !!(this.type != 'radio' || this.checked);
+		}
+	}
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event){
+	var target = event.target;
+	while (target && target != self){
+		if (match(target, event)) return fn.call(target, event, target);
+		target = document.id(target.parentNode);
+	}
+};
+
+var map = {
+	mouseenter: {
+		base: 'mouseover'
+	},
+	mouseleave: {
+		base: 'mouseout'
+	},
+	focus: {
+		base: 'focus' + (eventListenerSupport ? '' : 'in'),
+		capture: true
+	},
+	blur: {
+		base: eventListenerSupport ? 'blur' : 'focusout',
+		capture: true
+	}
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+	return {
+
+		base: 'focusin',
+
+		remove: function(self, uid){
+			var list = self.retrieve(_key + type + 'listeners', {})[uid];
+			if (list && list.forms) for (var i = list.forms.length; i--;){
+				list.forms[i].removeEvent(type, list.fns[i]);
+			}
+		},
+
+		listen: function(self, match, fn, event, uid){
+			var target = event.target,
+				form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+			if (!form) return;
+
+			var listeners = self.retrieve(_key + type + 'listeners', {}),
+				listener = listeners[uid] || {forms: [], fns: []},
+				forms = listener.forms, fns = listener.fns;
+
+			if (forms.indexOf(form) != -1) return;
+			forms.push(form);
+
+			var _fn = function(event){
+				bubbleUp(self, match, fn, event);
+			};
+			form.addEvent(type, _fn);
+			fns.push(_fn);
+
+			listeners[uid] = listener;
+			self.store(_key + type + 'listeners', listeners);
+		}
+	};
+};
+
+var inputObserver = function(type){
+	return {
+		base: 'focusin',
+		listen: function(self, match, fn, event){
+			var events = {blur: function(){
+				this.removeEvents(events);
+			}};
+			events[type] = function(event){
+				bubbleUp(self, match, fn, event);
+			};
+			event.target.addEvents(events);
+		}
+	};
+};
+
+if (!eventListenerSupport) Object.append(map, {
+	submit: formObserver('submit'),
+	reset: formObserver('reset'),
+	change: inputObserver('change'),
+	select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+	addEvent = proto.addEvent,
+	removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+	return function(type, fn, useCapture){
+		if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+		var parsed = Slick.parse(type).expressions[0][0];
+		if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+		var newType = parsed.tag;
+		parsed.pseudos.slice(1).each(function(pseudo){
+			newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+		});
+		return method.call(this, newType, parsed.pseudos[0].value, fn);
+	};
+};
+
+var delegation = {
+
+	addEvent: function(type, match, fn){
+		var storage = this.retrieve('$delegates', {}), stored = storage[type];
+		if (stored) for (var _uid in stored){
+			if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+		}
+
+		var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+		type = _map.base || _type;
+
+		match = function(target){
+			return Slick.match(target, _match);
+		};
+
+		var elementEvent = Element.Events[_type];
+		if (elementEvent && elementEvent.condition){
+			var __match = match, condition = elementEvent.condition;
+			match = function(target, event){
+				return __match(target, event) && condition.call(target, event, type);
+			};
+		}
+
+		var self = this, uid = String.uniqueID();
+		var delegator = _map.listen ? function(event){
+			_map.listen(self, match, fn, event, uid);
+		} : function(event){
+			bubbleUp(self, match, fn, event);
+		};
+
+		if (!stored) stored = {};
+		stored[uid] = {
+			match: _match,
+			fn: _fn,
+			delegator: delegator
+		};
+		storage[_type] = stored;
+		return addEvent.call(this, type, delegator, _map.capture);
+	},
+
+	removeEvent: function(type, match, fn, _uid){
+		var storage = this.retrieve('$delegates', {}), stored = storage[type];
+		if (!stored) return this;
+
+		if (_uid){
+			var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+			type = _map.base || _type;
+			if (_map.remove) _map.remove(this, _uid);
+			delete stored[_uid];
+			storage[_type] = stored;
+			return removeEvent.call(this, type, delegator);
+		}
+
+		var __uid, s;
+		if (fn) for (__uid in stored){
+			s = stored[__uid];
+			if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+		} else for (__uid in stored){
+			s = stored[__uid];
+			if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+		}
+		return this;
+	}
+
+};
+
+[Element, Window, Document].invoke('implement', {
+	addEvent: relay(addEvent, delegation.addEvent),
+	removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+  - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+  - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+	child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var isOffset = function(el){
+	return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+	return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+	scrollTo: function(x, y){
+		if (isBody(this)){
+			this.getWindow().scrollTo(x, y);
+		} else {
+			this.scrollLeft = x;
+			this.scrollTop = y;
+		}
+		return this;
+	},
+
+	getSize: function(){
+		if (isBody(this)) return this.getWindow().getSize();
+		return {x: this.offsetWidth, y: this.offsetHeight};
+	},
+
+	getScrollSize: function(){
+		if (isBody(this)) return this.getWindow().getScrollSize();
+		return {x: this.scrollWidth, y: this.scrollHeight};
+	},
+
+	getScroll: function(){
+		if (isBody(this)) return this.getWindow().getScroll();
+		return {x: this.scrollLeft, y: this.scrollTop};
+	},
+
+	getScrolls: function(){
+		var element = this.parentNode, position = {x: 0, y: 0};
+		while (element && !isBody(element)){
+			position.x += element.scrollLeft;
+			position.y += element.scrollTop;
+			element = element.parentNode;
+		}
+		return position;
+	},
+
+	getOffsetParent: brokenOffsetParent ? function(){
+		var element = this;
+		if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+		var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+		while ((element = element.parentNode)){
+			if (isOffsetCheck(element)) return element;
+		}
+		return null;
+	} : function(){
+		var element = this;
+		if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+		try {
+			return element.offsetParent;
+		} catch(e) {}
+		return null;
+	},
+
+	getOffsets: function(){
+		if (this.getBoundingClientRect && !Browser.Platform.ios){
+			var bound = this.getBoundingClientRect(),
+				html = document.id(this.getDocument().documentElement),
+				htmlScroll = html.getScroll(),
+				elemScrolls = this.getScrolls(),
+				isFixed = (styleString(this, 'position') == 'fixed');
+
+			return {
+				x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+				y: bound.top.toInt()  + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+			};
+		}
+
+		var element = this, position = {x: 0, y: 0};
+		if (isBody(this)) return position;
+
+		while (element && !isBody(element)){
+			position.x += element.offsetLeft;
+			position.y += element.offsetTop;
+
+			if (Browser.firefox){
+				if (!borderBox(element)){
+					position.x += leftBorder(element);
+					position.y += topBorder(element);
+				}
+				var parent = element.parentNode;
+				if (parent && styleString(parent, 'overflow') != 'visible'){
+					position.x += leftBorder(parent);
+					position.y += topBorder(parent);
+				}
+			} else if (element != this && Browser.safari){
+				position.x += leftBorder(element);
+				position.y += topBorder(element);
+			}
+
+			element = element.offsetParent;
+		}
+		if (Browser.firefox && !borderBox(this)){
+			position.x -= leftBorder(this);
+			position.y -= topBorder(this);
+		}
+		return position;
+	},
+
+	getPosition: function(relative){
+		var offset = this.getOffsets(),
+			scroll = this.getScrolls();
+		var position = {
+			x: offset.x - scroll.x,
+			y: offset.y - scroll.y
+		};
+
+		if (relative && (relative = document.id(relative))){
+			var relativePosition = relative.getPosition();
+			return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+		}
+		return position;
+	},
+
+	getCoordinates: function(element){
+		if (isBody(this)) return this.getWindow().getCoordinates();
+		var position = this.getPosition(element),
+			size = this.getSize();
+		var obj = {
+			left: position.x,
+			top: position.y,
+			width: size.x,
+			height: size.y
+		};
+		obj.right = obj.left + obj.width;
+		obj.bottom = obj.top + obj.height;
+		return obj;
+	},
+
+	computePosition: function(obj){
+		return {
+			left: obj.x - styleNumber(this, 'margin-left'),
+			top: obj.y - styleNumber(this, 'margin-top')
+		};
+	},
+
+	setPosition: function(obj){
+		return this.setStyles(this.computePosition(obj));
+	}
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+	getSize: function(){
+		var doc = getCompatElement(this);
+		return {x: doc.clientWidth, y: doc.clientHeight};
+	},
+
+	getScroll: function(){
+		var win = this.getWindow(), doc = getCompatElement(this);
+		return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+	},
+
+	getScrollSize: function(){
+		var doc = getCompatElement(this),
+			min = this.getSize(),
+			body = this.getDocument().body;
+
+		return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+	},
+
+	getPosition: function(){
+		return {x: 0, y: 0};
+	},
+
+	getCoordinates: function(){
+		var size = this.getSize();
+		return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+	}
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+	return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+	return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+	return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+	return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+	return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+	var doc = element.getDocument();
+	return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+	getHeight: function(){
+		return this.getSize().y;
+	},
+
+	getWidth: function(){
+		return this.getSize().x;
+	},
+
+	getScrollTop: function(){
+		return this.getScroll().y;
+	},
+
+	getScrollLeft: function(){
+		return this.getScroll().x;
+	},
+
+	getScrollHeight: function(){
+		return this.getScrollSize().y;
+	},
+
+	getScrollWidth: function(){
+		return this.getScrollSize().x;
+	},
+
+	getTop: function(){
+		return this.getPosition().y;
+	},
+
+	getLeft: function(){
+		return this.getPosition().x;
+	}
+
+});
+
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {
+		/*
+		onStart: nil,
+		onCancel: nil,
+		onComplete: nil,
+		*/
+		fps: 60,
+		unit: false,
+		duration: 500,
+		frames: null,
+		frameSkip: true,
+		link: 'ignore'
+	},
+
+	initialize: function(options){
+		this.subject = this.subject || this;
+		this.setOptions(options);
+	},
+
+	getTransition: function(){
+		return function(p){
+			return -(Math.cos(Math.PI * p) - 1) / 2;
+		};
+	},
+
+	step: function(now){
+		if (this.options.frameSkip){
+			var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+			this.time = now;
+			this.frame += frames;
+		} else {
+			this.frame++;
+		}
+
+		if (this.frame < this.frames){
+			var delta = this.transition(this.frame / this.frames);
+			this.set(this.compute(this.from, this.to, delta));
+		} else {
+			this.frame = this.frames;
+			this.set(this.compute(this.from, this.to, 1));
+			this.stop();
+		}
+	},
+
+	set: function(now){
+		return now;
+	},
+
+	compute: function(from, to, delta){
+		return Fx.compute(from, to, delta);
+	},
+
+	check: function(){
+		if (!this.isRunning()) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+		}
+		return false;
+	},
+
+	start: function(from, to){
+		if (!this.check(from, to)) return this;
+		this.from = from;
+		this.to = to;
+		this.frame = (this.options.frameSkip) ? 0 : -1;
+		this.time = null;
+		this.transition = this.getTransition();
+		var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+		this.duration = Fx.Durations[duration] || duration.toInt();
+		this.frameInterval = 1000 / fps;
+		this.frames = frames || Math.round(this.duration / this.frameInterval);
+		this.fireEvent('start', this.subject);
+		pushInstance.call(this, fps);
+		return this;
+	},
+
+	stop: function(){
+		if (this.isRunning()){
+			this.time = null;
+			pullInstance.call(this, this.options.fps);
+			if (this.frames == this.frame){
+				this.fireEvent('complete', this.subject);
+				if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+			} else {
+				this.fireEvent('stop', this.subject);
+			}
+		}
+		return this;
+	},
+
+	cancel: function(){
+		if (this.isRunning()){
+			this.time = null;
+			pullInstance.call(this, this.options.fps);
+			this.frame = this.frames;
+			this.fireEvent('cancel', this.subject).clearChain();
+		}
+		return this;
+	},
+
+	pause: function(){
+		if (this.isRunning()){
+			this.time = null;
+			pullInstance.call(this, this.options.fps);
+		}
+		return this;
+	},
+
+	resume: function(){
+		if ((this.frame < this.frames) && !this.isRunning()) pushInstance.call(this, this.options.fps);
+		return this;
+	},
+
+	isRunning: function(){
+		var list = instances[this.options.fps];
+		return list && list.contains(this);
+	}
+
+});
+
+Fx.compute = function(from, to, delta){
+	return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+	var now = Date.now();
+	for (var i = this.length; i--;){
+		var instance = this[i];
+		if (instance) instance.step(now);
+	}
+};
+
+var pushInstance = function(fps){
+	var list = instances[fps] || (instances[fps] = []);
+	list.push(this);
+	if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+	var list = instances[fps];
+	if (list){
+		list.erase(this);
+		if (!list.length && timers[fps]){
+			delete instances[fps];
+			timers[fps] = clearInterval(timers[fps]);
+		}
+	}
+};
+
+})();
+
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+	Extends: Fx,
+
+	//prepares the base from/to object
+
+	prepare: function(element, property, values){
+		values = Array.from(values);
+		if (values[1] == null){
+			values[1] = values[0];
+			values[0] = element.getStyle(property);
+		}
+		var parsed = values.map(this.parse);
+		return {from: parsed[0], to: parsed[1]};
+	},
+
+	//parses a value into an array
+
+	parse: function(value){
+		value = Function.from(value)();
+		value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+		return value.map(function(val){
+			val = String(val);
+			var found = false;
+			Object.each(Fx.CSS.Parsers, function(parser, key){
+				if (found) return;
+				var parsed = parser.parse(val);
+				if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+			});
+			found = found || {value: val, parser: Fx.CSS.Parsers.String};
+			return found;
+		});
+	},
+
+	//computes by a from and to prepared objects, using their parsers.
+
+	compute: function(from, to, delta){
+		var computed = [];
+		(Math.min(from.length, to.length)).times(function(i){
+			computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+		});
+		computed.$family = Function.from('fx:css:value');
+		return computed;
+	},
+
+	//serves the value as settable
+
+	serve: function(value, unit){
+		if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+		var returned = [];
+		value.each(function(bit){
+			returned = returned.concat(bit.parser.serve(bit.value, unit));
+		});
+		return returned;
+	},
+
+	//renders the change to an element
+
+	render: function(element, property, value, unit){
+		element.setStyle(property, this.serve(value, unit));
+	},
+
+	//searches inside the page css to find the values for a selector
+
+	search: function(selector){
+		if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+		var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+		Array.each(document.styleSheets, function(sheet, j){
+			var href = sheet.href;
+			if (href && href.contains('://') && !href.contains(document.domain)) return;
+			var rules = sheet.rules || sheet.cssRules;
+			Array.each(rules, function(rule, i){
+				if (!rule.style) return;
+				var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+					return m.toLowerCase();
+				}) : null;
+				if (!selectorText || !selectorTest.test(selectorText)) return;
+				Object.each(Element.Styles, function(value, style){
+					if (!rule.style[style] || Element.ShortStyles[style]) return;
+					value = String(rule.style[style]);
+					to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+				});
+			});
+		});
+		return Fx.CSS.Cache[selector] = to;
+	}
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+	Color: {
+		parse: function(value){
+			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+		},
+		compute: function(from, to, delta){
+			return from.map(function(value, i){
+				return Math.round(Fx.compute(from[i], to[i], delta));
+			});
+		},
+		serve: function(value){
+			return value.map(Number);
+		}
+	},
+
+	Number: {
+		parse: parseFloat,
+		compute: Fx.compute,
+		serve: function(value, unit){
+			return (unit) ? value + unit : value;
+		}
+	},
+
+	String: {
+		parse: Function.from(false),
+		compute: function(zero, one){
+			return one;
+		},
+		serve: function(zero){
+			return zero;
+		}
+	}
+
+};
+
+
+
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(property, now){
+		if (arguments.length == 1){
+			now = property;
+			property = this.property || this.options.property;
+		}
+		this.render(this.element, property, now, this.options.unit);
+		return this;
+	},
+
+	start: function(property, from, to){
+		if (!this.check(property, from, to)) return this;
+		var args = Array.flatten(arguments);
+		this.property = this.options.property || args.shift();
+		var parsed = this.prepare(this.element, this.property, args);
+		return this.parent(parsed.from, parsed.to);
+	}
+
+});
+
+Element.Properties.tween = {
+
+	set: function(options){
+		this.get('tween').cancel().setOptions(options);
+		return this;
+	},
+
+	get: function(){
+		var tween = this.retrieve('tween');
+		if (!tween){
+			tween = new Fx.Tween(this, {link: 'cancel'});
+			this.store('tween', tween);
+		}
+		return tween;
+	}
+
+};
+
+Element.implement({
+
+	tween: function(property, from, to){
+		this.get('tween').start(arguments);
+		return this;
+	},
+
+	fade: function(how){
+		var fade = this.get('tween'), o = 'opacity', toggle;
+		how = [how, 'toggle'].pick();
+		switch (how){
+			case 'in': fade.start(o, 1); break;
+			case 'out': fade.start(o, 0); break;
+			case 'show': fade.set(o, 1); break;
+			case 'hide': fade.set(o, 0); break;
+			case 'toggle':
+				var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+				fade.start(o, (flag) ? 0 : 1);
+				this.store('fade:flag', !flag);
+				toggle = true;
+			break;
+			default: fade.start(o, arguments);
+		}
+		if (!toggle) this.eliminate('fade:flag');
+		return this;
+	},
+
+	highlight: function(start, end){
+		if (!end){
+			end = this.retrieve('highlight:original', this.getStyle('background-color'));
+			end = (end == 'transparent') ? '#fff' : end;
+		}
+		var tween = this.get('tween');
+		tween.start('background-color', start || '#ffff88', end).chain(function(){
+			this.setStyle('background-color', this.retrieve('highlight:original'));
+			tween.callChain();
+		}.bind(this));
+		return this;
+	}
+
+});
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(now){
+		if (typeof now == 'string') now = this.search(now);
+		for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+		return now;
+	},
+
+	start: function(properties){
+		if (!this.check(properties)) return this;
+		if (typeof properties == 'string') properties = this.search(properties);
+		var from = {}, to = {};
+		for (var p in properties){
+			var parsed = this.prepare(this.element, p, properties[p]);
+			from[p] = parsed.from;
+			to[p] = parsed.to;
+		}
+		return this.parent(from, to);
+	}
+
+});
+
+Element.Properties.morph = {
+
+	set: function(options){
+		this.get('morph').cancel().setOptions(options);
+		return this;
+	},
+
+	get: function(){
+		var morph = this.retrieve('morph');
+		if (!morph){
+			morph = new Fx.Morph(this, {link: 'cancel'});
+			this.store('morph', morph);
+		}
+		return morph;
+	}
+
+};
+
+Element.implement({
+
+	morph: function(props){
+		this.get('morph').start(props);
+		return this;
+	}
+
+});
+
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+  - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+	getTransition: function(){
+		var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+		if (typeof trans == 'string'){
+			var data = trans.split(':');
+			trans = Fx.Transitions;
+			trans = trans[data[0]] || trans[data[0].capitalize()];
+			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+		}
+		return trans;
+	}
+
+});
+
+Fx.Transition = function(transition, params){
+	params = Array.from(params);
+	var easeIn = function(pos){
+		return transition(pos, params);
+	};
+	return Object.append(easeIn, {
+		easeIn: easeIn,
+		easeOut: function(pos){
+			return 1 - transition(1 - pos, params);
+		},
+		easeInOut: function(pos){
+			return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+		}
+	});
+};
+
+Fx.Transitions = {
+
+	linear: function(zero){
+		return zero;
+	}
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+	for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+	Pow: function(p, x){
+		return Math.pow(p, x && x[0] || 6);
+	},
+
+	Expo: function(p){
+		return Math.pow(2, 8 * (p - 1));
+	},
+
+	Circ: function(p){
+		return 1 - Math.sin(Math.acos(p));
+	},
+
+	Sine: function(p){
+		return 1 - Math.cos(p * Math.PI / 2);
+	},
+
+	Back: function(p, x){
+		x = x && x[0] || 1.618;
+		return Math.pow(p, 2) * ((x + 1) * p - x);
+	},
+
+	Bounce: function(p){
+		var value;
+		for (var a = 0, b = 1; 1; a += b, b /= 2){
+			if (p >= (7 - 4 * a) / 11){
+				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+				break;
+			}
+		}
+		return value;
+	},
+
+	Elastic: function(p, x){
+		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+	}
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+	Fx.Transitions[transition] = new Fx.Transition(function(p){
+		return Math.pow(p, i + 2);
+	});
+});
+
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+	progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {/*
+		onRequest: function(){},
+		onLoadstart: function(event, xhr){},
+		onProgress: function(event, xhr){},
+		onComplete: function(){},
+		onCancel: function(){},
+		onSuccess: function(responseText, responseXML){},
+		onFailure: function(xhr){},
+		onException: function(headerName, value){},
+		onTimeout: function(){},
+		user: '',
+		password: '',*/
+		url: '',
+		data: '',
+		headers: {
+			'X-Requested-With': 'XMLHttpRequest',
+			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+		},
+		async: true,
+		format: false,
+		method: 'post',
+		link: 'ignore',
+		isSuccess: null,
+		emulation: true,
+		urlEncoded: true,
+		encoding: 'utf-8',
+		evalScripts: false,
+		evalResponse: false,
+		timeout: 0,
+		noCache: false
+	},
+
+	initialize: function(options){
+		this.xhr = new Browser.Request();
+		this.setOptions(options);
+		this.headers = this.options.headers;
+	},
+
+	onStateChange: function(){
+		var xhr = this.xhr;
+		if (xhr.readyState != 4 || !this.running) return;
+		this.running = false;
+		this.status = 0;
+		Function.attempt(function(){
+			var status = xhr.status;
+			this.status = (status == 1223) ? 204 : status;
+		}.bind(this));
+		xhr.onreadystatechange = empty;
+		if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+		clearTimeout(this.timer);
+
+		this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+		if (this.options.isSuccess.call(this, this.status))
+			this.success(this.response.text, this.response.xml);
+		else
+			this.failure();
+	},
+
+	isSuccess: function(){
+		var status = this.status;
+		return (status >= 200 && status < 300);
+	},
+
+	isRunning: function(){
+		return !!this.running;
+	},
+
+	processScripts: function(text){
+		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+		return text.stripScripts(this.options.evalScripts);
+	},
+
+	success: function(text, xml){
+		this.onSuccess(this.processScripts(text), xml);
+	},
+
+	onSuccess: function(){
+		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+	},
+
+	failure: function(){
+		this.onFailure();
+	},
+
+	onFailure: function(){
+		this.fireEvent('complete').fireEvent('failure', this.xhr);
+	},
+
+	loadstart: function(event){
+		this.fireEvent('loadstart', [event, this.xhr]);
+	},
+
+	progress: function(event){
+		this.fireEvent('progress', [event, this.xhr]);
+	},
+
+	timeout: function(){
+		this.fireEvent('timeout', this.xhr);
+	},
+
+	setHeader: function(name, value){
+		this.headers[name] = value;
+		return this;
+	},
+
+	getHeader: function(name){
+		return Function.attempt(function(){
+			return this.xhr.getResponseHeader(name);
+		}.bind(this));
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!this.check(options)) return this;
+
+		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+		this.running = true;
+
+		var type = typeOf(options);
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		var old = this.options;
+		options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+		switch (typeOf(data)){
+			case 'element': data = document.id(data).toQueryString(); break;
+			case 'object': case 'hash': data = Object.toQueryString(data);
+		}
+
+		if (this.options.format){
+			var format = 'format=' + this.options.format;
+			data = (data) ? format + '&' + data : format;
+		}
+
+		if (this.options.emulation && !['get', 'post'].contains(method)){
+			var _method = '_method=' + method;
+			data = (data) ? _method + '&' + data : _method;
+			method = 'post';
+		}
+
+		if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+			this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+		}
+
+		if (!url) url = document.location.pathname;
+
+		var trimPosition = url.lastIndexOf('/');
+		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+		if (this.options.noCache)
+			url += (url.contains('?') ? '&' : '?') + String.uniqueID();
+
+		if (data && method == 'get'){
+			url += (url.contains('?') ? '&' : '?') + data;
+			data = null;
+		}
+
+		var xhr = this.xhr;
+		if (progressSupport){
+			xhr.onloadstart = this.loadstart.bind(this);
+			xhr.onprogress = this.progress.bind(this);
+		}
+
+		xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+		if (this.options.user && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+		xhr.onreadystatechange = this.onStateChange.bind(this);
+
+		Object.each(this.headers, function(value, key){
+			try {
+				xhr.setRequestHeader(key, value);
+			} catch (e){
+				this.fireEvent('exception', [key, value]);
+			}
+		}, this);
+
+		this.fireEvent('request');
+		xhr.send(data);
+		if (!this.options.async) this.onStateChange();
+		if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		var xhr = this.xhr;
+		xhr.abort();
+		clearTimeout(this.timer);
+		xhr.onreadystatechange = empty;
+		if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+		this.xhr = new Browser.Request();
+		this.fireEvent('cancel');
+		return this;
+	}
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+	methods[method] = function(data){
+		var object = {
+			method: method
+		};
+		if (data != null) object.data = data;
+		return this.send(object);
+	};
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+	set: function(options){
+		var send = this.get('send').cancel();
+		send.setOptions(options);
+		return this;
+	},
+
+	get: function(){
+		var send = this.retrieve('send');
+		if (!send){
+			send = new Request({
+				data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+			});
+			this.store('send', send);
+		}
+		return send;
+	}
+
+};
+
+Element.implement({
+
+	send: function(url){
+		var sender = this.get('send');
+		sender.send({data: this, url: url || sender.options.url});
+		return this;
+	}
+
+});
+
+})();
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+	Extends: Request,
+
+	options: {
+		update: false,
+		append: false,
+		evalScripts: true,
+		filter: false,
+		headers: {
+			Accept: 'text/html, application/xml, text/xml, */*'
+		}
+	},
+
+	success: function(text){
+		var options = this.options, response = this.response;
+
+		response.html = text.stripScripts(function(script){
+			response.javascript = script;
+		});
+
+		var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+		if (match) response.html = match[1];
+		var temp = new Element('div').set('html', response.html);
+
+		response.tree = temp.childNodes;
+		response.elements = temp.getElements(options.filter || '*');
+
+		if (options.filter) response.tree = response.elements;
+		if (options.update){
+			var update = document.id(options.update).empty();
+			if (options.filter) update.adopt(response.elements);
+			else update.set('html', response.html);
+		} else if (options.append){
+			var append = document.id(options.append);
+			if (options.filter) response.elements.reverse().inject(append);
+			else append.adopt(temp.getChildren());
+		}
+		if (options.evalScripts) Browser.exec(response.javascript);
+
+		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+	}
+
+});
+
+Element.Properties.load = {
+
+	set: function(options){
+		var load = this.get('load').cancel();
+		load.setOptions(options);
+		return this;
+	},
+
+	get: function(){
+		var load = this.retrieve('load');
+		if (!load){
+			load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+			this.store('load', load);
+		}
+		return load;
+	}
+
+};
+
+Element.implement({
+
+	load: function(){
+		this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+		return this;
+	}
+
+});
+
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+	return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+	string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+					replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+					replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+	return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+	return JSON.stringify(obj);
+} : function(obj){
+	if (obj && obj.toJSON) obj = obj.toJSON();
+
+	switch (typeOf(obj)){
+		case 'string':
+			return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+		case 'array':
+			return '[' + obj.map(JSON.encode).clean() + ']';
+		case 'object': case 'hash':
+			var string = [];
+			Object.each(obj, function(value, key){
+				var json = JSON.encode(value);
+				if (json) string.push(JSON.encode(key) + ':' + json);
+			});
+			return '{' + string + '}';
+		case 'number': case 'boolean': return '' + obj;
+		case 'null': return 'null';
+	}
+
+	return null;
+};
+
+JSON.decode = function(string, secure){
+	if (!string || typeOf(string) != 'string') return null;
+
+	if (secure || JSON.secure){
+		if (JSON.parse) return JSON.parse(string);
+		if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+	}
+
+	return eval('(' + string + ')');
+};
+
+})();
+
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+	Extends: Request,
+
+	options: {
+		/*onError: function(text, error){},*/
+		secure: true
+	},
+
+	initialize: function(options){
+		this.parent(options);
+		Object.append(this.headers, {
+			'Accept': 'application/json',
+			'X-Request': 'JSON'
+		});
+	},
+
+	success: function(text){
+		var json;
+		try {
+			json = this.response.json = JSON.decode(text, this.options.secure);
+		} catch (error){
+			this.fireEvent('error', [text, error]);
+			return;
+		}
+		if (json == null) this.onFailure();
+		else this.onSuccess(json, text);
+	}
+
+});
+
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+  - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+	Implements: Options,
+
+	options: {
+		path: '/',
+		domain: false,
+		duration: false,
+		secure: false,
+		document: document,
+		encode: true
+	},
+
+	initialize: function(key, options){
+		this.key = key;
+		this.setOptions(options);
+	},
+
+	write: function(value){
+		if (this.options.encode) value = encodeURIComponent(value);
+		if (this.options.domain) value += '; domain=' + this.options.domain;
+		if (this.options.path) value += '; path=' + this.options.path;
+		if (this.options.duration){
+			var date = new Date();
+			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+			value += '; expires=' + date.toGMTString();
+		}
+		if (this.options.secure) value += '; secure';
+		this.options.document.cookie = this.key + '=' + value;
+		return this;
+	},
+
+	read: function(){
+		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+		return (value) ? decodeURIComponent(value[1]) : null;
+	},
+
+	dispose: function(){
+		new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+		return this;
+	}
+
+});
+
+Cookie.write = function(key, value, options){
+	return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+	return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+	return new Cookie(key, options).dispose();
+};
+
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+	loaded,
+	checks = [],
+	shouldPoll,
+	timer,
+	testElement = document.createElement('div');
+
+var domready = function(){
+	clearTimeout(timer);
+	if (ready) return;
+	Browser.loaded = ready = true;
+	document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+	document.fireEvent('domready');
+	window.fireEvent('domready');
+};
+
+var check = function(){
+	for (var i = checks.length; i--;) if (checks[i]()){
+		domready();
+		return true;
+	}
+	return false;
+};
+
+var poll = function(){
+	clearTimeout(timer);
+	if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+	try {
+		testElement.doScroll();
+		return true;
+	} catch (e){}
+	return false;
+};
+// If doScroll works already, it can't be used to determine domready
+//   e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+	checks.push(doScrollWorks);
+	shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+	var state = document.readyState;
+	return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+	onAdd: function(fn){
+		if (ready) fn.call(this);
+	}
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+	base: 'load',
+	onAdd: function(fn){
+		if (loaded && this == window) fn.call(this);
+	},
+	condition: function(){
+		if (this == window){
+			domready();
+			delete Element.Events.load;
+		}
+		return true;
+	}
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+	loaded = true;
+});
+
+})(window, document);
+
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+  - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Options, Object, Element]
+
+provides: Swiff
+
+...
+*/
+
+(function(){
+
+var Swiff = this.Swiff = new Class({
+
+	Implements: Options,
+
+	options: {
+		id: null,
+		height: 1,
+		width: 1,
+		container: null,
+		properties: {},
+		params: {
+			quality: 'high',
+			allowScriptAccess: 'always',
+			wMode: 'window',
+			swLiveConnect: true
+		},
+		callBacks: {},
+		vars: {}
+	},
+
+	toElement: function(){
+		return this.object;
+	},
+
+	initialize: function(path, options){
+		this.instance = 'Swiff_' + String.uniqueID();
+
+		this.setOptions(options);
+		options = this.options;
+		var id = this.id = options.id || this.instance;
+		var container = document.id(options.container);
+
+		Swiff.CallBacks[this.instance] = {};
+
+		var params = options.params, vars = options.vars, callBacks = options.callBacks;
+		var properties = Object.append({height: options.height, width: options.width}, options.properties);
+
+		var self = this;
+
+		for (var callBack in callBacks){
+			Swiff.CallBacks[this.instance][callBack] = (function(option){
+				return function(){
+					return option.apply(self.object, arguments);
+				};
+			})(callBacks[callBack]);
+			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+		}
+
+		params.flashVars = Object.toQueryString(vars);
+		if (Browser.ie){
+			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+			params.movie = path;
+		} else {
+			properties.type = 'application/x-shockwave-flash';
+		}
+		properties.data = path;
+
+		var build = '<object id="' + id + '"';
+		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+		build += '>';
+		for (var param in params){
+			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+		}
+		build += '</object>';
+		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+	},
+
+	replaces: function(element){
+		element = document.id(element, true);
+		element.parentNode.replaceChild(this.toElement(), element);
+		return this;
+	},
+
+	inject: function(element){
+		document.id(element, true).appendChild(this.toElement());
+		return this;
+	},
+
+	remote: function(){
+		return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments));
+	}
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+	return eval(rs);
+};
+
+})();
+

+ 6 - 0
js/libraries/balupton-history/vendor/qunit/.gitignore

@@ -0,0 +1,6 @@
+.project
+*~
+*.diff
+*.patch
+.DS_Store
+

+ 27 - 0
js/libraries/balupton-history/vendor/qunit/README.md

@@ -0,0 +1,27 @@
+[QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework.
+================================
+
+QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery
+project to test its code and plugins but is capable of testing any generic
+JavaScript code (and even capable of testing JavaScript code on the server-side).
+
+QUnit is especially useful for regression testing: Whenever a bug is reported,
+write a test that asserts the existence of that particular bug. Then fix it and
+commit both. Every time you work on the code again, run the tests. If the bug
+comes up again - a regression - you'll spot it immediately and know how to fix
+it, because you know what code you just changed.
+
+Having good unit test coverage makes safe refactoring easy and cheap. You can
+run the tests after each small refactoring step and always know what change
+broke something.
+
+QUnit is similar to other unit testing frameworks like JUnit, but makes use of
+the features JavaScript provides and helps with testing code in the browser, eg.
+with it's stop/start facilities for testing asynchronous code.
+
+If you are interested in helping developing QUnit, you are in the right place.
+For related discussions, visit the
+[QUnit and Testing forum](http://forum.jquery.com/qunit-and-testing).
+
+Planning for a qunitjs.com site and other testing tools related work now happens
+on the [jQuery Testing Team planning wiki](http://jquerytesting.pbworks.com/w/page/41556026/FrontPage).

+ 21 - 0
js/libraries/balupton-history/vendor/qunit/package.json

@@ -0,0 +1,21 @@
+{
+	"name": "qunit",
+	"author": {
+		"name": "John Resig",
+		"email": "jeresig@gmail.com",
+		"url": "http://ejohn.org/"
+	},
+	"maintainer": {
+		"name": "Jörn Zaefferer",
+		"email": "joern.zaefferer@googlemail.com",
+		"url": "http://bassistance.de/"
+	},
+	"url": "http://docs.jquery.com/QUnit",
+	"license": {
+		"name": "MIT",
+		"url": "http://www.opensource.org/licenses/mit-license.php"
+	},
+	"description": "An easy-to-use JavaScript Unit Testing framework.",
+	"keywords": [ "testing", "unit", "jquery" ],
+	"lib": "qunit"
+}

+ 225 - 0
js/libraries/balupton-history/vendor/qunit/qunit/qunit.css

@@ -0,0 +1,225 @@
+/**
+ * QUnit - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+/** 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, sans-serif;
+}
+
+#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: #8699a4;
+	background-color: #0d3349;
+
+	font-size: 1.5em;
+	line-height: 1em;
+	font-weight: normal;
+
+	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: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+	color: #fff;
+}
+
+#qunit-banner {
+	height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+	padding: 0.5em 0 0.5em 2em;
+	color: #5E740B;
+	background-color: #eee;
+}
+
+#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.hidepass li.pass, #qunit-tests.hidepass li.running  {
+	display: none;
+}
+
+#qunit-tests li strong {
+	cursor: pointer;
+}
+
+#qunit-tests li a {
+	padding: 0.5em;
+	color: #c2ccd1;
+	text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+	color: #000;
+}
+
+#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;
+}
+
+#qunit-tests table {
+	border-collapse: collapse;
+	margin-top: .2em;
+}
+
+#qunit-tests th {
+	text-align: right;
+	vertical-align: top;
+	padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+	vertical-align: top;
+}
+
+#qunit-tests pre {
+	margin: 0;
+	white-space: pre-wrap;
+	word-wrap: break-word;
+}
+
+#qunit-tests del {
+	background-color: #e0f2be;
+	color: #374e0c;
+	text-decoration: none;
+}
+
+#qunit-tests ins {
+	background-color: #ffcaca;
+	color: #500;
+	text-decoration: none;
+}
+
+/*** 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 > li:last-child {
+	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;
+}
+
+#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                    { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+	padding: 0.5em 0.5em 0.5em 2.5em;
+
+	color: #2b81af;
+	background-color: #D2E0E6;
+
+	border-bottom: 1px solid white;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+	position: absolute;
+	top: -10000px;
+	left: -10000px;
+}

+ 1442 - 0
js/libraries/balupton-history/vendor/qunit/qunit/qunit.js

@@ -0,0 +1,1442 @@
+/**
+ * QUnit - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+(function(window) {
+
+var defined = {
+	setTimeout: typeof window.setTimeout !== "undefined",
+	sessionStorage: (function() {
+		try {
+			return !!sessionStorage.getItem;
+		} catch(e){
+			return false;
+		}
+  })()
+};
+
+var testId = 0;
+
+var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
+	this.name = name;
+	this.testName = testName;
+	this.expected = expected;
+	this.testEnvironmentArg = testEnvironmentArg;
+	this.async = async;
+	this.callback = callback;
+	this.assertions = [];
+};
+Test.prototype = {
+	init: function() {
+		var tests = id("qunit-tests");
+		if (tests) {
+			var b = document.createElement("strong");
+				b.innerHTML = "Running " + this.name;
+			var li = document.createElement("li");
+				li.appendChild( b );
+				li.className = "running";
+				li.id = this.id = "test-output" + testId++;
+			tests.appendChild( li );
+		}
+	},
+	setup: function() {
+		if (this.module != config.previousModule) {
+			if ( config.previousModule ) {
+				QUnit.moduleDone( {
+					name: config.previousModule,
+					failed: config.moduleStats.bad,
+					passed: config.moduleStats.all - config.moduleStats.bad,
+					total: config.moduleStats.all
+				} );
+			}
+			config.previousModule = this.module;
+			config.moduleStats = { all: 0, bad: 0 };
+			QUnit.moduleStart( {
+				name: this.module
+			} );
+		}
+
+		config.current = this;
+		this.testEnvironment = extend({
+			setup: function() {},
+			teardown: function() {}
+		}, this.moduleTestEnvironment);
+		if (this.testEnvironmentArg) {
+			extend(this.testEnvironment, this.testEnvironmentArg);
+		}
+
+		QUnit.testStart( {
+			name: this.testName
+		} );
+
+		// allow utility functions to access the current test environment
+		// TODO why??
+		QUnit.current_testEnvironment = this.testEnvironment;
+
+		try {
+			if ( !config.pollution ) {
+				saveGlobal();
+			}
+
+			this.testEnvironment.setup.call(this.testEnvironment);
+		} catch(e) {
+			QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
+		}
+	},
+	run: function() {
+		if ( this.async ) {
+			QUnit.stop();
+		}
+
+		if ( config.notrycatch ) {
+			this.callback.call(this.testEnvironment);
+			return;
+		}
+		try {
+			this.callback.call(this.testEnvironment);
+		} catch(e) {
+			fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
+			QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
+			// else next test will carry the responsibility
+			saveGlobal();
+
+			// Restart the tests if they're blocking
+			if ( config.blocking ) {
+				start();
+			}
+		}
+	},
+	teardown: function() {
+		try {
+			this.testEnvironment.teardown.call(this.testEnvironment);
+			checkPollution();
+		} catch(e) {
+			QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
+		}
+	},
+	finish: function() {
+		if ( this.expected && this.expected != this.assertions.length ) {
+			QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
+		}
+
+		var good = 0, bad = 0,
+			tests = id("qunit-tests");
+
+		config.stats.all += this.assertions.length;
+		config.moduleStats.all += this.assertions.length;
+
+		if ( tests ) {
+			var ol  = document.createElement("ol");
+
+			for ( var i = 0; i < this.assertions.length; i++ ) {
+				var assertion = this.assertions[i];
+
+				var li = document.createElement("li");
+				li.className = assertion.result ? "pass" : "fail";
+				li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
+				ol.appendChild( li );
+
+				if ( assertion.result ) {
+					good++;
+				} else {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+
+			// store result when possible
+			if ( QUnit.config.reorder && defined.sessionStorage ) {
+				if (bad) {
+					sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
+				} else {
+					sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
+				}
+			}
+
+			if (bad == 0) {
+				ol.style.display = "none";
+			}
+
+			var b = document.createElement("strong");
+			b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+			var a = document.createElement("a");
+			a.innerHTML = "Rerun";
+			a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
+
+			addEvent(b, "click", function() {
+				var next = b.nextSibling.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 = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
+				}
+			});
+
+			var li = id(this.id);
+			li.className = bad ? "fail" : "pass";
+			li.removeChild( li.firstChild );
+			li.appendChild( b );
+			li.appendChild( a );
+			li.appendChild( ol );
+
+		} else {
+			for ( var i = 0; i < this.assertions.length; i++ ) {
+				if ( !this.assertions[i].result ) {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+		}
+
+		try {
+			QUnit.reset();
+		} catch(e) {
+			fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
+		}
+
+		QUnit.testDone( {
+			name: this.testName,
+			failed: bad,
+			passed: this.assertions.length - bad,
+			total: this.assertions.length
+		} );
+	},
+
+	queue: function() {
+		var test = this;
+		synchronize(function() {
+			test.init();
+		});
+		function run() {
+			// each of these can by async
+			synchronize(function() {
+				test.setup();
+			});
+			synchronize(function() {
+				test.run();
+			});
+			synchronize(function() {
+				test.teardown();
+			});
+			synchronize(function() {
+				test.finish();
+			});
+		}
+		// defer when previous test run passed, if storage is available
+		var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
+		if (bad) {
+			run();
+		} else {
+			synchronize(run);
+		};
+	}
+
+};
+
+var QUnit = {
+
+	// call on start of module test to prepend name to all tests
+	module: function(name, testEnvironment) {
+		config.currentModule = name;
+		config.currentModuleTestEnviroment = 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>', 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;
+		}
+
+		var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
+		test.module = config.currentModule;
+		test.moduleTestEnvironment = config.currentModuleTestEnviroment;
+		test.queue();
+	},
+
+	/**
+	 * 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.current.expected = asserts;
+	},
+
+	/**
+	 * Asserts true.
+	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+	 */
+	ok: function(a, msg) {
+		a = !!a;
+		var details = {
+			result: a,
+			message: msg
+		};
+		msg = escapeHtml(msg);
+		QUnit.log(details);
+		config.current.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) {
+		QUnit.push(expected == actual, actual, expected, message);
+	},
+
+	notEqual: function(actual, expected, message) {
+		QUnit.push(expected != actual, actual, expected, message);
+	},
+
+	deepEqual: function(actual, expected, message) {
+		QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
+	},
+
+	notDeepEqual: function(actual, expected, message) {
+		QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
+	},
+
+	strictEqual: function(actual, expected, message) {
+		QUnit.push(expected === actual, actual, expected, message);
+	},
+
+	notStrictEqual: function(actual, expected, message) {
+		QUnit.push(expected !== actual, actual, expected, message);
+	},
+
+	raises: function(block, expected, message) {
+		var actual, ok = false;
+
+		if (typeof expected === 'string') {
+			message = expected;
+			expected = null;
+		}
+
+		try {
+			block();
+		} catch (e) {
+			actual = e;
+		}
+
+		if (actual) {
+			// we don't want to validate thrown error
+			if (!expected) {
+				ok = true;
+			// expected is a regexp
+			} else if (QUnit.objectType(expected) === "regexp") {
+				ok = expected.test(actual);
+			// expected is a constructor
+			} else if (actual instanceof expected) {
+				ok = true;
+			// expected is a validation function which returns true is validation passed
+			} else if (expected.call({}, actual) === true) {
+				ok = true;
+			}
+		}
+
+		QUnit.ok(ok, message);
+	},
+
+	start: function() {
+		config.semaphore--;
+		if (config.semaphore > 0) {
+			// don't start until equal number of stop-calls
+			return;
+		}
+		if (config.semaphore < 0) {
+			// ignore if start is called more often then stop
+			config.semaphore = 0;
+		}
+		// A slight delay, to avoid any current callbacks
+		if ( defined.setTimeout ) {
+			window.setTimeout(function() {
+				if ( config.timeout ) {
+					clearTimeout(config.timeout);
+				}
+
+				config.blocking = false;
+				process();
+			}, 13);
+		} else {
+			config.blocking = false;
+			process();
+		}
+	},
+
+	stop: function(timeout) {
+		config.semaphore++;
+		config.blocking = true;
+
+		if ( timeout && defined.setTimeout ) {
+			clearTimeout(config.timeout);
+			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,
+
+	// by default, run previously failed tests first
+	// very useful in combination with "Hide passed tests" checked
+	reorder: true,
+
+	noglobals: false,
+	notrycatch: false
+};
+
+// Load paramaters
+(function() {
+	var location = window.location || { search: "", protocol: "file:" },
+		params = location.search.slice( 1 ).split( "&" ),
+		length = params.length,
+		urlParams = {},
+		current;
+
+	if ( params[ 0 ] ) {
+		for ( var i = 0; i < length; i++ ) {
+			current = params[ i ].split( "=" );
+			current[ 0 ] = decodeURIComponent( current[ 0 ] );
+			// allow just a key to turn on a flag, e.g., test.html?noglobals
+			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+			urlParams[ current[ 0 ] ] = current[ 1 ];
+			if ( current[ 0 ] in config ) {
+				config[ current[ 0 ] ] = current[ 1 ];
+			}
+		}
+	}
+
+	QUnit.urlParams = urlParams;
+	config.filter = urlParams.filter;
+
+	// 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,
+			filter: "",
+			queue: [],
+			semaphore: 0
+		});
+
+		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 );
+		}
+
+		if ( tests ) {
+			result = document.createElement( "p" );
+			result.id = "qunit-testresult";
+			result.className = "result";
+			tests.parentNode.insertBefore( result, tests );
+			result.innerHTML = 'Running...<br/>&nbsp;';
+		}
+	},
+
+	/**
+	 * Resets the test setup. Useful for tests that modify the DOM.
+	 *
+	 * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
+	 */
+	reset: function() {
+		if ( window.jQuery ) {
+			jQuery( "#qunit-fixture" ).html( config.fixture );
+		} else {
+			var main = id( 'qunit-fixture' );
+			if ( main ) {
+				main.innerHTML = 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;
+	},
+
+	push: function(result, actual, expected, message) {
+		var details = {
+			result: result,
+			message: message,
+			actual: actual,
+			expected: expected
+		};
+
+		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 + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
+		if (actual != expected) {
+			output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
+			output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
+		}
+		if (!result) {
+			var source = sourceFromStacktrace();
+			if (source) {
+				details.source = source;
+				output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>';
+			}
+		}
+		output += "</table>";
+
+		QUnit.log(details);
+
+		config.current.assertions.push({
+			result: !!result,
+			message: output
+		});
+	},
+
+	url: function( params ) {
+		params = extend( extend( {}, QUnit.urlParams ), params );
+		var querystring = "?",
+			key;
+		for ( key in params ) {
+			querystring += encodeURIComponent( key ) + "=" +
+				encodeURIComponent( params[ key ] ) + "&";
+		}
+		return window.location.pathname + querystring.slice( 0, -1 );
+	},
+
+	// Logging callbacks; all receive a single argument with the listed properties
+	// run test/logs.html for any related changes
+	begin: function() {},
+	// done: { failed, passed, total, runtime }
+	done: function() {},
+	// log: { result, actual, expected, message }
+	log: function() {},
+	// testStart: { name }
+	testStart: function() {},
+	// testDone: { name, failed, passed, total }
+	testDone: function() {},
+	// moduleStart: { name }
+	moduleStart: function() {},
+	// moduleDone: { name, failed, passed, total }
+	moduleDone: function() {}
+});
+
+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="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' +
+			'<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' +
+			'<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>';
+		addEvent( banner, "change", function( event ) {
+			var params = {};
+			params[ event.target.name ] = event.target.checked ? true : undefined;
+			window.location = QUnit.url( params );
+		});
+	}
+
+	var toolbar = id("qunit-testrunner-toolbar");
+	if ( toolbar ) {
+		var filter = document.createElement("input");
+		filter.type = "checkbox";
+		filter.id = "qunit-filter-pass";
+		addEvent( filter, "click", function() {
+			var ol = document.getElementById("qunit-tests");
+			if ( filter.checked ) {
+				ol.className = ol.className + " hidepass";
+			} else {
+				var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+				ol.className = tmp.replace(/ hidepass /, " ");
+			}
+			if ( defined.sessionStorage ) {
+				if (filter.checked) {
+					sessionStorage.setItem("qunit-filter-passed-tests",  "true");
+				} else {
+					sessionStorage.removeItem("qunit-filter-passed-tests");
+				}
+			}
+		});
+		if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
+			filter.checked = true;
+			var ol = document.getElementById("qunit-tests");
+			ol.className = ol.className + " hidepass";
+		}
+		toolbar.appendChild( filter );
+
+		var label = document.createElement("label");
+		label.setAttribute("for", "qunit-filter-pass");
+		label.innerHTML = "Hide passed tests";
+		toolbar.appendChild( label );
+	}
+
+	var main = id('qunit-fixture');
+	if ( main ) {
+		config.fixture = main.innerHTML;
+	}
+
+	if (config.autostart) {
+		QUnit.start();
+	}
+});
+
+function done() {
+	config.autorun = true;
+
+	// Log the last module results
+	if ( config.currentModule ) {
+		QUnit.moduleDone( {
+			name: config.currentModule,
+			failed: config.moduleStats.bad,
+			passed: config.moduleStats.all - config.moduleStats.bad,
+			total: config.moduleStats.all
+		} );
+	}
+
+	var banner = id("qunit-banner"),
+		tests = id("qunit-tests"),
+		runtime = +new Date - config.started,
+		passed = config.stats.all - config.stats.bad,
+		html = [
+			'Tests completed in ',
+			runtime,
+			' milliseconds.<br/>',
+			'<span class="passed">',
+			passed,
+			'</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 ) {
+		id( "qunit-testresult" ).innerHTML = html;
+	}
+
+	QUnit.done( {
+		failed: config.stats.bad,
+		passed: passed,
+		total: config.stats.all,
+		runtime: runtime
+	} );
+}
+
+function validTest( name ) {
+	var filter = config.filter,
+		run = false;
+
+	if ( !filter ) {
+		return true;
+	}
+
+	var not = filter.charAt( 0 ) === "!";
+	if ( not ) {
+		filter = filter.slice( 1 );
+	}
+
+	if ( name.indexOf( filter ) !== -1 ) {
+		return !not;
+	}
+
+	if ( not ) {
+		run = true;
+	}
+
+	return run;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy)
+// could be extended in the future to use something like https://github.com/csnover/TraceKit
+function sourceFromStacktrace() {
+	try {
+		throw new Error();
+	} catch ( e ) {
+		if (e.stacktrace) {
+			// Opera
+			return e.stacktrace.split("\n")[6];
+		} else if (e.stack) {
+			// Firefox, Chrome
+			return e.stack.split("\n")[4];
+		}
+	}
+}
+
+function escapeHtml(s) {
+	if (!s) {
+		return "";
+	}
+	s = 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 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 {
+			window.setTimeout( process, 13 );
+			break;
+		}
+	}
+  if (!config.blocking && !config.queue.length) {
+    done();
+  }
+}
+
+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( config.pollution, old );
+	if ( newGlobals.length > 0 ) {
+		ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
+	}
+
+	var deletedGlobals = diff( old, config.pollution );
+	if ( deletedGlobals.length > 0 ) {
+		ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
+	}
+}
+
+// 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 ) {
+		if ( b[prop] === undefined ) {
+			delete a[prop];
+		} else {
+			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 (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
+				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, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
+				return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
+			},
+			array: array,
+			nodelist: array,
+			arguments: array,
+			object:function( map ) {
+				var ret = [ ];
+				QUnit.jsDump.up();
+				for ( var key in map )
+					ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
+				QUnit.jsDump.down();
+				return join( '{', ret, '}' );
+			},
+			node:function( node ) {
+				var open = QUnit.jsDump.HTML ? '&lt;' : '<',
+					close = QUnit.jsDump.HTML ? '&gt;' : '>';
+
+				var tag = node.nodeName.toLowerCase(),
+					ret = open + tag;
+
+				for ( var a in QUnit.jsDump.DOMAttrs ) {
+					var val = node[QUnit.jsDump.DOMAttrs[a]];
+					if ( val )
+						ret += ' ' + a + '=' + QUnit.jsDump.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:true //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);

+ 24 - 0
js/libraries/balupton-history/vendor/qunit/test/headless.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>QUnit Test Suite</title>
+	<link rel="stylesheet" href="../qunit/qunit.css" type="text/css" media="screen">
+	<script type="text/javascript" src="../qunit/qunit.js"></script>
+	<script type="text/javascript" src="test.js"></script>
+	<script type="text/javascript" src="same.js"></script>
+	<script>
+		var logs = ["begin", "testStart", "testDone", "log", "moduleStart", "moduleDone", "done"];
+		for (var i = 0; i < logs.length; i++) {
+			(function() {
+				var log = logs[i];
+				QUnit[log] = function() {
+					console.log(log, arguments);
+				};
+			})();
+		}
+	</script>
+</head>
+<body>
+	<div id="qunit-fixture">test markup</div>
+</body>
+</html>

+ 19 - 0
js/libraries/balupton-history/vendor/qunit/test/index.html

@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="UTF-8" />
+	<title>QUnit Test Suite</title>
+	<link rel="stylesheet" href="../qunit/qunit.css" type="text/css" media="screen">
+	<script type="text/javascript" src="../qunit/qunit.js"></script>
+	<script type="text/javascript" src="test.js"></script>
+	<script type="text/javascript" src="same.js"></script>
+</head>
+<body>
+	<h1 id="qunit-header">QUnit Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+</body>
+</html>

+ 17 - 0
js/libraries/balupton-history/vendor/qunit/test/logs.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>QUnit Test Suite</title>
+	<link rel="stylesheet" href="../qunit/qunit.css" type="text/css" media="screen">
+	<script type="text/javascript" src="../qunit/qunit.js"></script>
+	<script type="text/javascript" src="logs.js"></script>
+</head>
+<body>
+	<h1 id="qunit-header">QUnit Test Suite</h1>
+	<h2 id="qunit-banner"></h2>
+	<div id="qunit-testrunner-toolbar"></div>
+	<h2 id="qunit-userAgent"></h2>
+	<ol id="qunit-tests"></ol>
+	<div id="qunit-fixture">test markup</div>
+</body>
+</html>

+ 150 - 0
js/libraries/balupton-history/vendor/qunit/test/logs.js

@@ -0,0 +1,150 @@
+// TODO disable reordering for this suite!
+
+
+var begin = 0,
+	moduleStart = 0,
+	moduleDone = 0,
+	testStart = 0,
+	testDone = 0,
+	log = 0,
+	moduleContext,
+	moduleDoneContext,
+	testContext,
+	testDoneContext,
+	logContext;
+
+QUnit.begin = function() {
+	begin++;
+};
+QUnit.done = function() {
+};
+QUnit.moduleStart = function(context) {
+	moduleStart++;
+	moduleContext = context;
+};
+QUnit.moduleDone = function(context) {
+	moduleDone++;
+	moduleDoneContext = context;
+};
+QUnit.testStart = function(context) {
+	testStart++;
+	testContext = context;
+};
+QUnit.testDone = function(context) {
+	testDone++;
+	testDoneContext = context;
+};
+QUnit.log = function(context) {
+	log++;
+	logContext = context;
+};
+
+var logs = ["begin", "testStart", "testDone", "log", "moduleStart", "moduleDone", "done"];
+for (var i = 0; i < logs.length; i++) {
+	(function() {
+		var log = logs[i],
+			logger = QUnit[log];
+		QUnit[log] = function() {
+			console.log(log, arguments);
+			logger.apply(this, arguments);
+		};
+	})();
+}
+
+module("logs1");
+
+test("test1", 13, function() {
+	equal(begin, 1);
+	equal(moduleStart, 1);
+	equal(testStart, 1);
+	equal(testDone, 0);
+	equal(moduleDone, 0);
+
+	deepEqual(logContext, {
+		result: true,
+		message: undefined,
+		actual: 0,
+		expected: 0
+	});
+	equal("foo", "foo", "msg");
+	deepEqual(logContext, {
+		result: true,
+		message: "msg",
+		actual: "foo",
+		expected: "foo"
+	});
+	strictEqual(testDoneContext, undefined);
+	deepEqual(testContext, {
+		name: "test1"
+	});
+	strictEqual(moduleDoneContext, undefined);
+	deepEqual(moduleContext, {
+		name: "logs1"
+	});
+
+	equal(log, 12);
+});
+test("test2", 10, function() {
+	equal(begin, 1);
+	equal(moduleStart, 1);
+	equal(testStart, 2);
+	equal(testDone, 1);
+	equal(moduleDone, 0);
+
+	deepEqual(testDoneContext, {
+		name: "test1",
+		failed: 0,
+		passed: 13,
+		total: 13
+	});
+	deepEqual(testContext, {
+		name: "test2"
+	});
+	strictEqual(moduleDoneContext, undefined);
+	deepEqual(moduleContext, {
+		name: "logs1"
+	});
+
+	equal(log, 22);
+});
+
+module("logs2");
+
+test("test1", 9, function() {
+	equal(begin, 1);
+	equal(moduleStart, 2);
+	equal(testStart, 3);
+	equal(testDone, 2);
+	equal(moduleDone, 1);
+
+	deepEqual(testContext, {
+		name: "test1"
+	});
+	deepEqual(moduleDoneContext, {
+		name: "logs1",
+		failed: 0,
+		passed: 23,
+		total: 23
+	});
+	deepEqual(moduleContext, {
+		name: "logs2"
+	});
+
+	equal(log, 31);
+});
+test("test2", 8, function() {
+	equal(begin, 1);
+	equal(moduleStart, 2);
+	equal(testStart, 4);
+	equal(testDone, 3);
+	equal(moduleDone, 1);
+
+	deepEqual(testContext, {
+		name: "test2"
+	});
+	deepEqual(moduleContext, {
+		name: "logs2"
+	});
+
+	equal(log, 39);
+});

+ 1421 - 0
js/libraries/balupton-history/vendor/qunit/test/same.js

@@ -0,0 +1,1421 @@
+module("equiv");
+
+
+test("Primitive types and constants", function () {
+    equals(QUnit.equiv(null, null), true, "null");
+    equals(QUnit.equiv(null, {}), false, "null");
+    equals(QUnit.equiv(null, undefined), false, "null");
+    equals(QUnit.equiv(null, 0), false, "null");
+    equals(QUnit.equiv(null, false), false, "null");
+    equals(QUnit.equiv(null, ''), false, "null");
+    equals(QUnit.equiv(null, []), false, "null");
+
+    equals(QUnit.equiv(undefined, undefined), true, "undefined");
+    equals(QUnit.equiv(undefined, null), false, "undefined");
+    equals(QUnit.equiv(undefined, 0), false, "undefined");
+    equals(QUnit.equiv(undefined, false), false, "undefined");
+    equals(QUnit.equiv(undefined, {}), false, "undefined");
+    equals(QUnit.equiv(undefined, []), false, "undefined");
+    equals(QUnit.equiv(undefined, ""), false, "undefined");
+
+    // Nan usually doest not equal to Nan using the '==' operator.
+    // Only isNaN() is able to do it.
+    equals(QUnit.equiv(0/0, 0/0), true, "NaN"); // NaN VS NaN
+    equals(QUnit.equiv(1/0, 2/0), true, "Infinity"); // Infinity VS Infinity
+    equals(QUnit.equiv(-1/0, 2/0), false, "-Infinity, Infinity"); // -Infinity VS Infinity
+    equals(QUnit.equiv(-1/0, -2/0), true, "-Infinity, -Infinity"); // -Infinity VS -Infinity
+    equals(QUnit.equiv(0/0, 1/0), false, "NaN, Infinity"); // Nan VS Infinity
+    equals(QUnit.equiv(1/0, 0/0), false, "NaN, Infinity"); // Nan VS Infinity
+    equals(QUnit.equiv(0/0, null), false, "NaN");
+    equals(QUnit.equiv(0/0, undefined), false, "NaN");
+    equals(QUnit.equiv(0/0, 0), false, "NaN");
+    equals(QUnit.equiv(0/0, false), false, "NaN");
+    equals(QUnit.equiv(0/0, function () {}), false, "NaN");
+    equals(QUnit.equiv(1/0, null), false, "NaN, Infinity");
+    equals(QUnit.equiv(1/0, undefined), false, "NaN, Infinity");
+    equals(QUnit.equiv(1/0, 0), false, "NaN, Infinity");
+    equals(QUnit.equiv(1/0, 1), false, "NaN, Infinity");
+    equals(QUnit.equiv(1/0, false), false, "NaN, Infinity");
+    equals(QUnit.equiv(1/0, true), false, "NaN, Infinity");
+    equals(QUnit.equiv(1/0, function () {}), false, "NaN, Infinity");
+
+    equals(QUnit.equiv(0, 0), true, "number");
+    equals(QUnit.equiv(0, 1), false, "number");
+    equals(QUnit.equiv(1, 0), false, "number");
+    equals(QUnit.equiv(1, 1), true, "number");
+    equals(QUnit.equiv(1.1, 1.1), true, "number");
+    equals(QUnit.equiv(0.0000005, 0.0000005), true, "number");
+    equals(QUnit.equiv(0, ''), false, "number");
+    equals(QUnit.equiv(0, '0'), false, "number");
+    equals(QUnit.equiv(1, '1'), false, "number");
+    equals(QUnit.equiv(0, false), false, "number");
+    equals(QUnit.equiv(1, true), false, "number");
+
+    equals(QUnit.equiv(true, true), true, "boolean");
+    equals(QUnit.equiv(true, false), false, "boolean");
+    equals(QUnit.equiv(false, true), false, "boolean");
+    equals(QUnit.equiv(false, 0), false, "boolean");
+    equals(QUnit.equiv(false, null), false, "boolean");
+    equals(QUnit.equiv(false, undefined), false, "boolean");
+    equals(QUnit.equiv(true, 1), false, "boolean");
+    equals(QUnit.equiv(true, null), false, "boolean");
+    equals(QUnit.equiv(true, undefined), false, "boolean");
+
+    equals(QUnit.equiv('', ''), true, "string");
+    equals(QUnit.equiv('a', 'a'), true, "string");
+    equals(QUnit.equiv("foobar", "foobar"), true, "string");
+    equals(QUnit.equiv("foobar", "foo"), false, "string");
+    equals(QUnit.equiv('', 0), false, "string");
+    equals(QUnit.equiv('', false), false, "string");
+    equals(QUnit.equiv('', null), false, "string");
+    equals(QUnit.equiv('', undefined), false, "string");
+
+    // Short annotation VS new annotation
+    equals(QUnit.equiv(0, new Number()), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Number(), 0), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(1, new Number(1)), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Number(1), 1), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Number(0), 1), false, "short annotation VS new annotation");
+    equals(QUnit.equiv(0, new Number(1)), false, "short annotation VS new annotation");
+
+    equals(QUnit.equiv(new String(), ""), true, "short annotation VS new annotation");
+    equals(QUnit.equiv("", new String()), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new String("My String"), "My String"), true, "short annotation VS new annotation");
+    equals(QUnit.equiv("My String", new String("My String")), true, "short annotation VS new annotation");
+    equals(QUnit.equiv("Bad String", new String("My String")), false, "short annotation VS new annotation");
+    equals(QUnit.equiv(new String("Bad String"), "My String"), false, "short annotation VS new annotation");
+
+    equals(QUnit.equiv(false, new Boolean()), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Boolean(), false), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(true, new Boolean(true)), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Boolean(true), true), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(true, new Boolean(1)), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(false, new Boolean(false)), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Boolean(false), false), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(false, new Boolean(0)), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(true, new Boolean(false)), false, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Boolean(false), true), false, "short annotation VS new annotation");
+
+    equals(QUnit.equiv(new Object(), {}), true, "short annotation VS new annotation");
+    equals(QUnit.equiv({}, new Object()), true, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Object(), {a:1}), false, "short annotation VS new annotation");
+    equals(QUnit.equiv({a:1}, new Object()), false, "short annotation VS new annotation");
+    equals(QUnit.equiv({a:undefined}, new Object()), false, "short annotation VS new annotation");
+    equals(QUnit.equiv(new Object(), {a:undefined}), false, "short annotation VS new annotation");
+});
+
+test("Objects Basics.", function() {
+    equals(QUnit.equiv({}, {}), true);
+    equals(QUnit.equiv({}, null), false);
+    equals(QUnit.equiv({}, undefined), false);
+    equals(QUnit.equiv({}, 0), false);
+    equals(QUnit.equiv({}, false), false);
+
+    // This test is a hard one, it is very important
+    // REASONS:
+    //      1) They are of the same type "object"
+    //      2) [] instanceof Object is true
+    //      3) Their properties are the same (doesn't exists)
+    equals(QUnit.equiv({}, []), false);
+
+    equals(QUnit.equiv({a:1}, {a:1}), true);
+    equals(QUnit.equiv({a:1}, {a:"1"}), false);
+    equals(QUnit.equiv({a:[]}, {a:[]}), true);
+    equals(QUnit.equiv({a:{}}, {a:null}), false);
+    equals(QUnit.equiv({a:1}, {}), false);
+    equals(QUnit.equiv({}, {a:1}), false);
+
+    // Hard ones
+    equals(QUnit.equiv({a:undefined}, {}), false);
+    equals(QUnit.equiv({}, {a:undefined}), false);
+    equals(QUnit.equiv(
+        {
+            a: [{ bar: undefined }]
+        },
+        {
+            a: [{ bat: undefined }]
+        }
+    ), false);
+});
+
+
+test("Arrays Basics.", function() {
+
+    equals(QUnit.equiv([], []), true);
+
+    // May be a hard one, can invoke a crash at execution.
+    // because their types are both "object" but null isn't
+    // like a true object, it doesn't have any property at all.
+    equals(QUnit.equiv([], null), false);
+
+    equals(QUnit.equiv([], undefined), false);
+    equals(QUnit.equiv([], false), false);
+    equals(QUnit.equiv([], 0), false);
+    equals(QUnit.equiv([], ""), false);
+
+    // May be a hard one, but less hard
+    // than {} with [] (note the order)
+    equals(QUnit.equiv([], {}), false);
+
+    equals(QUnit.equiv([null],[]), false);
+    equals(QUnit.equiv([undefined],[]), false);
+    equals(QUnit.equiv([],[null]), false);
+    equals(QUnit.equiv([],[undefined]), false);
+    equals(QUnit.equiv([null],[undefined]), false);
+    equals(QUnit.equiv([[]],[[]]), true);
+    equals(QUnit.equiv([[],[],[]],[[],[],[]]), true);
+    equals(QUnit.equiv(
+                            [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
+                            [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]),
+                            true);
+    equals(QUnit.equiv(
+                            [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
+                            [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // shorter
+                            false);
+    equals(QUnit.equiv(
+                            [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
+                            [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // deepest element not an array
+                            false);
+
+    // same multidimensional
+    equals(QUnit.equiv(
+                            [1,2,3,4,5,6,7,8,9, [
+                                1,2,3,4,5,6,7,8,9, [
+                                    1,2,3,4,5,[
+                                        [6,7,8,9, [
+                                            [
+                                                1,2,3,4,[
+                                                    2,3,4,[
+                                                        1,2,[
+                                                            1,2,3,4,[
+                                                                1,2,3,4,5,6,7,8,9,[
+                                                                    0
+                                                                ],1,2,3,4,5,6,7,8,9
+                                                            ],5,6,7,8,9
+                                                        ],4,5,6,7,8,9
+                                                    ],5,6,7,8,9
+                                                ],5,6,7
+                                            ]
+                                        ]
+                                    ]
+                                ]
+                            ]]],
+                            [1,2,3,4,5,6,7,8,9, [
+                                1,2,3,4,5,6,7,8,9, [
+                                    1,2,3,4,5,[
+                                        [6,7,8,9, [
+                                            [
+                                                1,2,3,4,[
+                                                    2,3,4,[
+                                                        1,2,[
+                                                            1,2,3,4,[
+                                                                1,2,3,4,5,6,7,8,9,[
+                                                                    0
+                                                                ],1,2,3,4,5,6,7,8,9
+                                                            ],5,6,7,8,9
+                                                        ],4,5,6,7,8,9
+                                                    ],5,6,7,8,9
+                                                ],5,6,7
+                                            ]
+                                        ]
+                                    ]
+                                ]
+                            ]]]),
+                            true, "Multidimensional");
+
+    // different multidimensional
+    equals(QUnit.equiv(
+                            [1,2,3,4,5,6,7,8,9, [
+                                1,2,3,4,5,6,7,8,9, [
+                                    1,2,3,4,5,[
+                                        [6,7,8,9, [
+                                            [
+                                                1,2,3,4,[
+                                                    2,3,4,[
+                                                        1,2,[
+                                                            1,2,3,4,[
+                                                                1,2,3,4,5,6,7,8,9,[
+                                                                    0
+                                                                ],1,2,3,4,5,6,7,8,9
+                                                            ],5,6,7,8,9
+                                                        ],4,5,6,7,8,9
+                                                    ],5,6,7,8,9
+                                                ],5,6,7
+                                            ]
+                                        ]
+                                    ]
+                                ]
+                            ]]],
+                            [1,2,3,4,5,6,7,8,9, [
+                                1,2,3,4,5,6,7,8,9, [
+                                    1,2,3,4,5,[
+                                        [6,7,8,9, [
+                                            [
+                                                1,2,3,4,[
+                                                    2,3,4,[
+                                                        1,2,[
+                                                            '1',2,3,4,[                 // string instead of number
+                                                                1,2,3,4,5,6,7,8,9,[
+                                                                    0
+                                                                ],1,2,3,4,5,6,7,8,9
+                                                            ],5,6,7,8,9
+                                                        ],4,5,6,7,8,9
+                                                    ],5,6,7,8,9
+                                                ],5,6,7
+                                            ]
+                                        ]
+                                    ]
+                                ]
+                            ]]]),
+                            false, "Multidimensional");
+
+    // different multidimensional
+    equals(QUnit.equiv(
+                            [1,2,3,4,5,6,7,8,9, [
+                                1,2,3,4,5,6,7,8,9, [
+                                    1,2,3,4,5,[
+                                        [6,7,8,9, [
+                                            [
+                                                1,2,3,4,[
+                                                    2,3,4,[
+                                                        1,2,[
+                                                            1,2,3,4,[
+                                                                1,2,3,4,5,6,7,8,9,[
+                                                                    0
+                                                                ],1,2,3,4,5,6,7,8,9
+                                                            ],5,6,7,8,9
+                                                        ],4,5,6,7,8,9
+                                                    ],5,6,7,8,9
+                                                ],5,6,7
+                                            ]
+                                        ]
+                                    ]
+                                ]
+                            ]]],
+                            [1,2,3,4,5,6,7,8,9, [
+                                1,2,3,4,5,6,7,8,9, [
+                                    1,2,3,4,5,[
+                                        [6,7,8,9, [
+                                            [
+                                                1,2,3,4,[
+                                                    2,3,[                   // missing an element (4)
+                                                        1,2,[
+                                                            1,2,3,4,[
+                                                                1,2,3,4,5,6,7,8,9,[
+                                                                    0
+                                                                ],1,2,3,4,5,6,7,8,9
+                                                            ],5,6,7,8,9
+                                                        ],4,5,6,7,8,9
+                                                    ],5,6,7,8,9
+                                                ],5,6,7
+                                            ]
+                                        ]
+                                    ]
+                                ]
+                            ]]]),
+                            false, "Multidimensional");
+});
+
+test("Functions.", function() {
+    var f0 = function () {};
+    var f1 = function () {};
+
+    // f2 and f3 have the same code, formatted differently
+    var f2 = function () {var i = 0;};
+    var f3 = function () {
+        var i = 0 // this comment and no semicoma as difference
+    };
+
+    equals(QUnit.equiv(function() {}, function() {}), false, "Anonymous functions"); // exact source code
+    equals(QUnit.equiv(function() {}, function() {return true;}), false, "Anonymous functions");
+
+    equals(QUnit.equiv(f0, f0), true, "Function references"); // same references
+    equals(QUnit.equiv(f0, f1), false, "Function references"); // exact source code, different references
+    equals(QUnit.equiv(f2, f3), false, "Function references"); // equivalent source code, different references
+    equals(QUnit.equiv(f1, f2), false, "Function references"); // different source code, different references
+    equals(QUnit.equiv(function() {}, true), false);
+    equals(QUnit.equiv(function() {}, undefined), false);
+    equals(QUnit.equiv(function() {}, null), false);
+    equals(QUnit.equiv(function() {}, {}), false);
+});
+
+
+test("Date instances.", function() {
+    // Date, we don't need to test Date.parse() because it returns a number.
+    // Only test the Date instances by setting them a fix date.
+    // The date use is midnight January 1, 1970
+
+    var d1 = new Date();
+    d1.setTime(0); // fix the date
+
+    var d2 = new Date();
+    d2.setTime(0); // fix the date
+
+    var d3 = new Date(); // The very now
+
+    // Anyway their types differs, just in case the code fails in the order in which it deals with date
+    equals(QUnit.equiv(d1, 0), false); // d1.valueOf() returns 0, but d1 and 0 are different
+    // test same values date and different instances equality
+    equals(QUnit.equiv(d1, d2), true);
+    // test different date and different instances difference
+    equals(QUnit.equiv(d1, d3), false);
+});
+
+
+test("RegExp.", function() {
+    // Must test cases that imply those traps:
+    // var a = /./;
+    // a instanceof Object;        // Oops
+    // a instanceof RegExp;        // Oops
+    // typeof a === "function";    // Oops, false in IE and Opera, true in FF and Safari ("object")
+
+    // Tests same regex with same modifiers in different order
+    var r = /foo/;
+    var r5 = /foo/gim;
+    var r6 = /foo/gmi;
+    var r7 = /foo/igm;
+    var r8 = /foo/img;
+    var r9 = /foo/mig;
+    var r10 = /foo/mgi;
+    var ri1 = /foo/i;
+    var ri2 = /foo/i;
+    var rm1 = /foo/m;
+    var rm2 = /foo/m;
+    var rg1 = /foo/g;
+    var rg2 = /foo/g;
+
+    equals(QUnit.equiv(r5, r6), true, "Modifier order");
+    equals(QUnit.equiv(r5, r7), true, "Modifier order");
+    equals(QUnit.equiv(r5, r8), true, "Modifier order");
+    equals(QUnit.equiv(r5, r9), true, "Modifier order");
+    equals(QUnit.equiv(r5, r10), true, "Modifier order");
+    equals(QUnit.equiv(r, r5), false, "Modifier");
+
+    equals(QUnit.equiv(ri1, ri2), true, "Modifier");
+    equals(QUnit.equiv(r, ri1), false, "Modifier");
+    equals(QUnit.equiv(ri1, rm1), false, "Modifier");
+    equals(QUnit.equiv(r, rm1), false, "Modifier");
+    equals(QUnit.equiv(rm1, ri1), false, "Modifier");
+    equals(QUnit.equiv(rm1, rm2), true, "Modifier");
+    equals(QUnit.equiv(rg1, rm1), false, "Modifier");
+    equals(QUnit.equiv(rm1, rg1), false, "Modifier");
+    equals(QUnit.equiv(rg1, rg2), true, "Modifier");
+
+    // Different regex, same modifiers
+    var r11 = /[a-z]/gi;
+    var r13 = /[0-9]/gi; // oops! different
+    equals(QUnit.equiv(r11, r13), false, "Regex pattern");
+
+    var r14 = /0/ig;
+    var r15 = /"0"/ig; // oops! different
+    equals(QUnit.equiv(r14, r15), false, "Regex pattern");
+
+    var r1 = /[\n\r\u2028\u2029]/g;
+    var r2 = /[\n\r\u2028\u2029]/g;
+    var r3 = /[\n\r\u2028\u2028]/g; // differs from r1
+    var r4 = /[\n\r\u2028\u2029]/;  // differs from r1
+
+    equals(QUnit.equiv(r1, r2), true, "Regex pattern");
+    equals(QUnit.equiv(r1, r3), false, "Regex pattern");
+    equals(QUnit.equiv(r1, r4), false, "Regex pattern");
+
+    // More complex regex
+    var regex1 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$";
+    var regex2 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$";
+    // regex 3 is different: '.' not escaped
+    var regex3 = "^[-_.a-z0-9]+@([-_a-z0-9]+.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$";
+
+    var r21 = new RegExp(regex1);
+    var r22 = new RegExp(regex2);
+    var r23 = new RegExp(regex3); // diff from r21, not same pattern
+    var r23a = new RegExp(regex3, "gi"); // diff from r23, not same modifier
+    var r24a = new RegExp(regex3, "ig"); // same as r23a
+
+    equals(QUnit.equiv(r21, r22), true, "Complex Regex");
+    equals(QUnit.equiv(r21, r23), false, "Complex Regex");
+    equals(QUnit.equiv(r23, r23a), false, "Complex Regex");
+    equals(QUnit.equiv(r23a, r24a), true, "Complex Regex");
+
+    // typeof r1 is "function" in some browsers and "object" in others so we must cover this test
+    var re = / /;
+    equals(QUnit.equiv(re, function () {}), false, "Regex internal");
+    equals(QUnit.equiv(re, {}), false, "Regex internal");
+});
+
+
+test("Complex Objects.", function() {
+
+    function fn1() {
+        return "fn1";
+    }
+    function fn2() {
+        return "fn2";
+    }
+
+    // Try to invert the order of some properties to make sure it is covered.
+    // It can failed when properties are compared between unsorted arrays.
+    equals(QUnit.equiv(
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                a: 3.14159,
+                b: false,
+                c: {
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    r: "r",
+                                    s: [1,2,3],
+                                    t: undefined,
+                                    u: 0,
+                                    v: {
+                                        w: {
+                                            x: {
+                                                y: "Yahoo!",
+                                                z: null
+                                            }
+                                        }
+                                    }
+                                },
+                                q: [],
+                                p: 1/0,
+                                o: 99
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    d: 0,
+                    i: true,
+                    h: "false"
+                }
+            },
+            e: undefined,
+            g: "",
+            h: "h",
+            f: {},
+            i: []
+        },
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                b: false,
+                a: 3.14159,
+                c: {
+                    d: 0,
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    r: "r",
+                                    t: undefined,
+                                    u: 0,
+                                    s: [1,2,3],
+                                    v: {
+                                        w: {
+                                            x: {
+                                                z: null,
+                                                y: "Yahoo!"
+                                            }
+                                        }
+                                    }
+                                },
+                                o: 99,
+                                p: 1/0,
+                                q: []
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    i: true,
+                    h: "false"
+                }
+            },
+            e: undefined,
+            g: "",
+            f: {},
+            h: "h",
+            i: []
+        }
+    ), true);
+
+    equals(QUnit.equiv(
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                a: 3.14159,
+                b: false,
+                c: {
+                    d: 0,
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    //r: "r",   // different: missing a property
+                                    s: [1,2,3],
+                                    t: undefined,
+                                    u: 0,
+                                    v: {
+                                        w: {
+                                            x: {
+                                                y: "Yahoo!",
+                                                z: null
+                                            }
+                                        }
+                                    }
+                                },
+                                o: 99,
+                                p: 1/0,
+                                q: []
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    h: "false",
+                    i: true
+                }
+            },
+            e: undefined,
+            f: {},
+            g: "",
+            h: "h",
+            i: []
+        },
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                a: 3.14159,
+                b: false,
+                c: {
+                    d: 0,
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    r: "r",
+                                    s: [1,2,3],
+                                    t: undefined,
+                                    u: 0,
+                                    v: {
+                                        w: {
+                                            x: {
+                                                y: "Yahoo!",
+                                                z: null
+                                            }
+                                        }
+                                    }
+                                },
+                                o: 99,
+                                p: 1/0,
+                                q: []
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    h: "false",
+                    i: true
+                }
+            },
+            e: undefined,
+            f: {},
+            g: "",
+            h: "h",
+            i: []
+        }
+    ), false);
+
+    equals(QUnit.equiv(
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                a: 3.14159,
+                b: false,
+                c: {
+                    d: 0,
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    r: "r",
+                                    s: [1,2,3],
+                                    t: undefined,
+                                    u: 0,
+                                    v: {
+                                        w: {
+                                            x: {
+                                                y: "Yahoo!",
+                                                z: null
+                                            }
+                                        }
+                                    }
+                                },
+                                o: 99,
+                                p: 1/0,
+                                q: []
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    h: "false",
+                    i: true
+                }
+            },
+            e: undefined,
+            f: {},
+            g: "",
+            h: "h",
+            i: []
+        },
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                a: 3.14159,
+                b: false,
+                c: {
+                    d: 0,
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    r: "r",
+                                    s: [1,2,3],
+                                    //t: undefined,                 // different: missing a property with an undefined value
+                                    u: 0,
+                                    v: {
+                                        w: {
+                                            x: {
+                                                y: "Yahoo!",
+                                                z: null
+                                            }
+                                        }
+                                    }
+                                },
+                                o: 99,
+                                p: 1/0,
+                                q: []
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    h: "false",
+                    i: true
+                }
+            },
+            e: undefined,
+            f: {},
+            g: "",
+            h: "h",
+            i: []
+        }
+    ), false);
+
+    equals(QUnit.equiv(
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                a: 3.14159,
+                b: false,
+                c: {
+                    d: 0,
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    r: "r",
+                                    s: [1,2,3],
+                                    t: undefined,
+                                    u: 0,
+                                    v: {
+                                        w: {
+                                            x: {
+                                                y: "Yahoo!",
+                                                z: null
+                                            }
+                                        }
+                                    }
+                                },
+                                o: 99,
+                                p: 1/0,
+                                q: []
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    h: "false",
+                    i: true
+                }
+            },
+            e: undefined,
+            f: {},
+            g: "",
+            h: "h",
+            i: []
+        },
+        {
+            a: 1,
+            b: null,
+            c: [{}],
+            d: {
+                a: 3.14159,
+                b: false,
+                c: {
+                    d: 0,
+                    e: fn1,
+                    f: [[[]]],
+                    g: {
+                        j: {
+                            k: {
+                                n: {
+                                    r: "r",
+                                    s: [1,2,3],
+                                    t: undefined,
+                                    u: 0,
+                                    v: {
+                                        w: {
+                                            x: {
+                                                y: "Yahoo!",
+                                                z: null
+                                            }
+                                        }
+                                    }
+                                },
+                                o: 99,
+                                p: 1/0,
+                                q: {}           // different was []
+                            },
+                            l: undefined,
+                            m: null
+                        }
+                    },
+                    h: "false",
+                    i: true
+                }
+            },
+            e: undefined,
+            f: {},
+            g: "",
+            h: "h",
+            i: []
+        }
+    ), false);
+
+    var same1 = {
+        a: [
+            "string", null, 0, "1", 1, {
+                prop: null,
+                foo: [1,2,null,{}, [], [1,2,3]],
+                bar: undefined
+            }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας"
+        ],
+        unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争",
+        b: "b",
+        c: fn1
+    };
+
+    var same2 = {
+        a: [
+            "string", null, 0, "1", 1, {
+                prop: null,
+                foo: [1,2,null,{}, [], [1,2,3]],
+                bar: undefined
+            }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας"
+        ],
+        unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争",
+        b: "b",
+        c: fn1
+    };
+
+    var diff1 = {
+        a: [
+            "string", null, 0, "1", 1, {
+                prop: null,
+                foo: [1,2,null,{}, [], [1,2,3,4]], // different: 4 was add to the array
+                bar: undefined
+            }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας"
+        ],
+        unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争",
+        b: "b",
+        c: fn1
+    };
+
+    var diff2 = {
+        a: [
+            "string", null, 0, "1", 1, {
+                prop: null,
+                foo: [1,2,null,{}, [], [1,2,3]],
+                newprop: undefined, // different: newprop was added
+                bar: undefined
+            }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας"
+        ],
+        unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争",
+        b: "b",
+        c: fn1
+    };
+
+    var diff3 = {
+        a: [
+            "string", null, 0, "1", 1, {
+                prop: null,
+                foo: [1,2,null,{}, [], [1,2,3]],
+                bar: undefined
+            }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ α" // different: missing last char
+        ],
+        unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争",
+        b: "b",
+        c: fn1
+    };
+
+    var diff4 = {
+        a: [
+            "string", null, 0, "1", 1, {
+                prop: null,
+                foo: [1,2,undefined,{}, [], [1,2,3]], // different: undefined instead of null
+                bar: undefined
+            }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας"
+        ],
+        unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争",
+        b: "b",
+        c: fn1
+    };
+
+    var diff5 = {
+        a: [
+            "string", null, 0, "1", 1, {
+                prop: null,
+                foo: [1,2,null,{}, [], [1,2,3]],
+                bat: undefined // different: property name not "bar"
+            }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας"
+        ],
+        unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争",
+        b: "b",
+        c: fn1
+    };
+
+    equals(QUnit.equiv(same1, same2), true);
+    equals(QUnit.equiv(same2, same1), true);
+    equals(QUnit.equiv(same2, diff1), false);
+    equals(QUnit.equiv(diff1, same2), false);
+
+    equals(QUnit.equiv(same1, diff1), false);
+    equals(QUnit.equiv(same1, diff2), false);
+    equals(QUnit.equiv(same1, diff3), false);
+    equals(QUnit.equiv(same1, diff3), false);
+    equals(QUnit.equiv(same1, diff4), false);
+    equals(QUnit.equiv(same1, diff5), false);
+    equals(QUnit.equiv(diff5, diff1), false);
+});
+
+
+test("Complex Arrays.", function() {
+
+    function fn() {
+    }
+
+    equals(QUnit.equiv(
+                [1, 2, 3, true, {}, null, [
+                    {
+                        a: ["", '1', 0]
+                    },
+                    5, 6, 7
+                ], "foo"],
+                [1, 2, 3, true, {}, null, [
+                    {
+                        a: ["", '1', 0]
+                    },
+                    5, 6, 7
+                ], "foo"]),
+            true);
+
+    equals(QUnit.equiv(
+                [1, 2, 3, true, {}, null, [
+                    {
+                        a: ["", '1', 0]
+                    },
+                    5, 6, 7
+                ], "foo"],
+                [1, 2, 3, true, {}, null, [
+                    {
+                        b: ["", '1', 0]         // not same property name
+                    },
+                    5, 6, 7
+                ], "foo"]),
+            false);
+
+    var a = [{
+        b: fn,
+        c: false,
+        "do": "reserved word",
+        "for": {
+            ar: [3,5,9,"hey!", [], {
+                ar: [1,[
+                    3,4,6,9, null, [], []
+                ]],
+                e: fn,
+                f: undefined
+            }]
+        },
+        e: 0.43445
+    }, 5, "string", 0, fn, false, null, undefined, 0, [
+        4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0
+    ], [], [[[], "foo", null, {
+        n: 1/0,
+        z: {
+            a: [3,4,5,6,"yep!", undefined, undefined],
+            b: {}
+        }
+    }, {}]]];
+
+    equals(QUnit.equiv(a,
+            [{
+                b: fn,
+                c: false,
+                "do": "reserved word",
+                "for": {
+                    ar: [3,5,9,"hey!", [], {
+                        ar: [1,[
+                            3,4,6,9, null, [], []
+                        ]],
+                        e: fn,
+                        f: undefined
+                    }]
+                },
+                e: 0.43445
+            }, 5, "string", 0, fn, false, null, undefined, 0, [
+                4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0
+            ], [], [[[], "foo", null, {
+                n: 1/0,
+                z: {
+                    a: [3,4,5,6,"yep!", undefined, undefined],
+                    b: {}
+                }
+            }, {}]]]), true);
+
+    equals(QUnit.equiv(a,
+            [{
+                b: fn,
+                c: false,
+                "do": "reserved word",
+                "for": {
+                    ar: [3,5,9,"hey!", [], {
+                        ar: [1,[
+                            3,4,6,9, null, [], []
+                        ]],
+                        e: fn,
+                        f: undefined
+                    }]
+                },
+                e: 0.43445
+            }, 5, "string", 0, fn, false, null, undefined, 0, [
+                4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[2]]]], "3"], {}, 1/0    // different: [[[[[2]]]]] instead of [[[[[3]]]]]
+            ], [], [[[], "foo", null, {
+                n: 1/0,
+                z: {
+                    a: [3,4,5,6,"yep!", undefined, undefined],
+                    b: {}
+                }
+            }, {}]]]), false);
+
+    equals(QUnit.equiv(a,
+            [{
+                b: fn,
+                c: false,
+                "do": "reserved word",
+                "for": {
+                    ar: [3,5,9,"hey!", [], {
+                        ar: [1,[
+                            3,4,6,9, null, [], []
+                        ]],
+                        e: fn,
+                        f: undefined
+                    }]
+                },
+                e: 0.43445
+            }, 5, "string", 0, fn, false, null, undefined, 0, [
+                4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0
+            ], [], [[[], "foo", null, {
+                n: -1/0,                                                                // different, -Infinity instead of Infinity
+                z: {
+                    a: [3,4,5,6,"yep!", undefined, undefined],
+                    b: {}
+                }
+            }, {}]]]), false);
+
+    equals(QUnit.equiv(a,
+            [{
+                b: fn,
+                c: false,
+                "do": "reserved word",
+                "for": {
+                    ar: [3,5,9,"hey!", [], {
+                        ar: [1,[
+                            3,4,6,9, null, [], []
+                        ]],
+                        e: fn,
+                        f: undefined
+                    }]
+                },
+                e: 0.43445
+            }, 5, "string", 0, fn, false, null, undefined, 0, [
+                4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0
+            ], [], [[[], "foo", {                                                       // different: null is missing
+                n: 1/0,
+                z: {
+                    a: [3,4,5,6,"yep!", undefined, undefined],
+                    b: {}
+                }
+            }, {}]]]), false);
+
+    equals(QUnit.equiv(a,
+            [{
+                b: fn,
+                c: false,
+                "do": "reserved word",
+                "for": {
+                    ar: [3,5,9,"hey!", [], {
+                        ar: [1,[
+                            3,4,6,9, null, [], []
+                        ]],
+                        e: fn
+                                                                                // different: missing property f: undefined
+                    }]
+                },
+                e: 0.43445
+            }, 5, "string", 0, fn, false, null, undefined, 0, [
+                4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0
+            ], [], [[[], "foo", null, {
+                n: 1/0,
+                z: {
+                    a: [3,4,5,6,"yep!", undefined, undefined],
+                    b: {}
+                }
+            }, {}]]]), false);
+});
+
+
+test("Prototypal inheritance", function() {
+    function Gizmo(id) {
+        this.id = id;
+    }
+
+    function Hoozit(id) {
+        this.id = id;
+    }
+    Hoozit.prototype = new Gizmo();
+
+    var gizmo = new Gizmo("ok");
+    var hoozit = new Hoozit("ok");
+
+    // Try this test many times after test on instances that hold function
+    // to make sure that our code does not mess with last object constructor memoization.
+    equals(QUnit.equiv(function () {}, function () {}), false);
+
+    // Hoozit inherit from Gizmo
+    // hoozit instanceof Hoozit; // true
+    // hoozit instanceof Gizmo; // true
+    equals(QUnit.equiv(hoozit, gizmo), true);
+
+    Gizmo.prototype.bar = true; // not a function just in case we skip them
+
+    // Hoozit inherit from Gizmo
+    // They are equivalent
+    equals(QUnit.equiv(hoozit, gizmo), true);
+
+    // Make sure this is still true !important
+    // The reason for this is that I forgot to reset the last
+    // caller to where it were called from.
+    equals(QUnit.equiv(function () {}, function () {}), false);
+
+    // Make sure this is still true !important
+    equals(QUnit.equiv(hoozit, gizmo), true);
+
+    Hoozit.prototype.foo = true; // not a function just in case we skip them
+
+    // Gizmo does not inherit from Hoozit
+    // gizmo instanceof Gizmo; // true
+    // gizmo instanceof Hoozit; // false
+    // They are not equivalent
+    equals(QUnit.equiv(hoozit, gizmo), false);
+
+    // Make sure this is still true !important
+    equals(QUnit.equiv(function () {}, function () {}), false);
+});
+
+
+test("Instances", function() {
+    function A() {}
+    var a1 = new A();
+    var a2 = new A();
+
+    function B() {
+        this.fn = function () {};
+    }
+    var b1 = new B();
+    var b2 = new B();
+
+    equals(QUnit.equiv(a1, a2), true, "Same property, same constructor");
+
+    // b1.fn and b2.fn are functions but they are different references
+    // But we decided to skip function for instances.
+    equals(QUnit.equiv(b1, b2), true, "Same property, same constructor");
+    equals(QUnit.equiv(a1, b1), false, "Same properties but different constructor"); // failed
+
+    function Car(year) {
+        var privateVar = 0;
+        this.year = year;
+        this.isOld = function() {
+            return year > 10;
+        };
+    }
+
+    function Human(year) {
+        var privateVar = 1;
+        this.year = year;
+        this.isOld = function() {
+            return year > 80;
+        };
+    }
+
+    var car = new Car(30);
+    var carSame = new Car(30);
+    var carDiff = new Car(10);
+    var human = new Human(30);
+
+    var diff = {
+        year: 30
+    };
+
+    var same = {
+        year: 30,
+        isOld: function () {}
+    };
+
+    equals(QUnit.equiv(car, car), true);
+    equals(QUnit.equiv(car, carDiff), false);
+    equals(QUnit.equiv(car, carSame), true);
+    equals(QUnit.equiv(car, human), false);
+});
+
+
+test("Complex Instances Nesting (with function value in literals and/or in nested instances)", function() {
+    function A(fn) {
+        this.a = {};
+        this.fn = fn;
+        this.b = {a: []};
+        this.o = {};
+        this.fn1 = fn;
+    }
+    function B(fn) {
+        this.fn = fn;
+        this.fn1 = function () {};
+        this.a = new A(function () {});
+    }
+
+    function fnOutside() {
+    }
+
+    function C(fn) {
+        function fnInside() {
+        }
+        this.x = 10;
+        this.fn = fn;
+        this.fn1 = function () {};
+        this.fn2 = fnInside;
+        this.fn3 = {
+            a: true,
+            b: fnOutside // ok make reference to a function in all instances scope
+        };
+        this.o1 = {};
+
+        // This function will be ignored.
+        // Even if it is not visible for all instances (e.g. locked in a closures),
+        // it is from a  property that makes part of an instance (e.g. from the C constructor)
+        this.b1 = new B(function () {});
+        this.b2 = new B({
+            x: {
+                b2: new B(function() {})
+            }
+        });
+    }
+
+    function D(fn) {
+        function fnInside() {
+        }
+        this.x = 10;
+        this.fn = fn;
+        this.fn1 = function () {};
+        this.fn2 = fnInside;
+        this.fn3 = {
+            a: true,
+            b: fnOutside, // ok make reference to a function in all instances scope
+
+            // This function won't be ingored.
+            // It isn't visible for all C insances
+            // and it is not in a property of an instance. (in an Object instances e.g. the object literal)
+            c: fnInside
+        };
+        this.o1 = {};
+
+        // This function will be ignored.
+        // Even if it is not visible for all instances (e.g. locked in a closures),
+        // it is from a  property that makes part of an instance (e.g. from the C constructor)
+        this.b1 = new B(function () {});
+        this.b2 = new B({
+            x: {
+                b2: new B(function() {})
+            }
+        });
+    }
+
+    function E(fn) {
+        function fnInside() {
+        }
+        this.x = 10;
+        this.fn = fn;
+        this.fn1 = function () {};
+        this.fn2 = fnInside;
+        this.fn3 = {
+            a: true,
+            b: fnOutside // ok make reference to a function in all instances scope
+        };
+        this.o1 = {};
+
+        // This function will be ignored.
+        // Even if it is not visible for all instances (e.g. locked in a closures),
+        // it is from a  property that makes part of an instance (e.g. from the C constructor)
+        this.b1 = new B(function () {});
+        this.b2 = new B({
+            x: {
+                b1: new B({a: function() {}}),
+                b2: new B(function() {})
+            }
+        });
+    }
+
+
+    var a1 = new A(function () {});
+    var a2 = new A(function () {});
+    equals(QUnit.equiv(a1, a2), true);
+
+    equals(QUnit.equiv(a1, a2), true); // different instances
+
+    var b1 = new B(function () {});
+    var b2 = new B(function () {});
+    equals(QUnit.equiv(b1, b2), true);
+
+    var c1 = new C(function () {});
+    var c2 = new C(function () {});
+    equals(QUnit.equiv(c1, c2), true);
+
+    var d1 = new D(function () {});
+    var d2 = new D(function () {});
+    equals(QUnit.equiv(d1, d2), false);
+
+    var e1 = new E(function () {});
+    var e2 = new E(function () {});
+    equals(QUnit.equiv(e1, e2), false);
+
+});
+
+
+test('object with references to self wont loop', function(){
+    var circularA = {
+        abc:null
+    }, circularB = {
+        abc:null
+    };
+    circularA.abc = circularA;
+    circularB.abc = circularB;
+    equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambigous test)");
+
+    circularA.def = 1;
+    circularB.def = 1;
+    equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambigous test)");
+
+    circularA.def = 1;
+    circularB.def = 0;
+    equals(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object (unambigous test)");
+});
+
+test('array with references to self wont loop', function(){
+    var circularA = [],
+        circularB = [];
+    circularA.push(circularA);
+    circularB.push(circularB);
+    equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambigous test)");
+
+    circularA.push( 'abc' );
+    circularB.push( 'abc' );
+    equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambigous test)");
+
+    circularA.push( 'hello' );
+    circularB.push( 'goodbye' );
+    equals(QUnit.equiv(circularA, circularB), false, "Should not repeat test on array (unambigous test)");
+});
+
+test('mixed object/array with references to self wont loop', function(){
+    var circularA = [{abc:null}],
+        circularB = [{abc:null}];
+    circularA[0].abc = circularA;
+    circularB[0].abc = circularB;
+
+    circularA.push(circularA);
+    circularB.push(circularB);
+    equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambigous test)");
+
+    circularA[0].def = 1;
+    circularB[0].def = 1;
+    equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambigous test)");
+
+    circularA[0].def = 1;
+    circularB[0].def = 0;
+    equals(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object/array (unambigous test)");
+});
+
+test("Test that must be done at the end because they extend some primitive's prototype", function() {
+    // Try that a function looks like our regular expression.
+    // This tests if we check that a and b are really both instance of RegExp
+    Function.prototype.global = true;
+    Function.prototype.multiline = true;
+    Function.prototype.ignoreCase = false;
+    Function.prototype.source = "my regex";
+    var re = /my regex/gm;
+    equals(QUnit.equiv(re, function () {}), false, "A function that looks that a regex isn't a regex");
+    // This test will ensures it works in both ways, and ALSO especially that we can make differences
+    // between RegExp and Function constructor because typeof on a RegExpt instance is "function"
+    equals(QUnit.equiv(function () {}, re), false, "Same conversely, but ensures that function and regexp are distinct because their constructor are different");
+});

+ 324 - 0
js/libraries/balupton-history/vendor/qunit/test/test.js

@@ -0,0 +1,324 @@
+test("module without setup/teardown (default)", function() {
+	expect(1);
+	ok(true);
+});
+
+test("expect in test", 3, function() {
+	ok(true);
+	ok(true);
+	ok(true);
+});
+
+test("expect in test", 1, function() {
+	ok(true);
+});
+
+module("setup test", {
+	setup: function() {
+		ok(true);
+	}
+});
+
+test("module with setup", function() {
+	expect(2);
+	ok(true);
+});
+
+test("module with setup, expect in test call", 2, function() {
+	ok(true);
+});
+
+var state;
+
+module("setup/teardown test", {
+	setup: function() {
+		state = true;
+		ok(true);
+	},
+	teardown: function() {
+		ok(true);
+	}
+});
+
+test("module with setup/teardown", function() {
+	expect(3);
+	ok(true);
+});
+
+module("setup/teardown test 2");
+
+test("module without setup/teardown", function() {
+	expect(1);
+	ok(true);
+});
+
+if (typeof setTimeout !== 'undefined') {
+state = 'fail';
+
+module("teardown and stop", {
+	teardown: function() {
+		equal(state, "done", "Test teardown.");
+	}
+});
+
+test("teardown must be called after test ended", function() {
+	expect(1);
+	stop();
+	setTimeout(function() {
+		state = "done";
+		start();
+	}, 13);
+});
+
+module("async setup test", {
+	setup: function() {
+		stop();
+		setTimeout(function(){
+			ok(true);
+			start();
+		}, 500);
+	}
+});
+
+asyncTest("module with async setup", function() {
+	expect(2);
+	ok(true);
+	start();
+});
+
+module("async teardown test", {
+	teardown: function() {
+		stop();
+		setTimeout(function(){
+			ok(true);
+			start();
+		}, 500);
+	}
+});
+
+asyncTest("module with async teardown", function() {
+	expect(2);
+	ok(true);
+	start();
+});
+
+module("asyncTest");
+
+asyncTest("asyncTest", function() {
+	expect(2);
+	ok(true);
+	setTimeout(function() {
+		state = "done";
+		ok(true);
+		start();
+	}, 13);
+});
+
+asyncTest("asyncTest", 2, function() {
+	ok(true);
+	setTimeout(function() {
+		state = "done";
+		ok(true);
+		start();
+	}, 13);
+});
+
+test("sync", 2, function() {
+	stop();
+	setTimeout(function() {
+		ok(true);
+		start();
+	}, 13);
+	stop();
+	setTimeout(function() {
+		ok(true);
+		start();
+	}, 125);
+});
+}
+
+module("save scope", {
+	setup: function() {
+		this.foo = "bar";
+	},
+	teardown: function() {
+		deepEqual(this.foo, "bar");
+	}
+});
+test("scope check", function() {
+	expect(2);
+	deepEqual(this.foo, "bar");
+});
+
+module("simple testEnvironment setup", {
+	foo: "bar",
+	bugid: "#5311" // example of meta-data
+});
+test("scope check", function() {
+	deepEqual(this.foo, "bar");
+});
+test("modify testEnvironment",function() {
+	this.foo="hamster";
+});
+test("testEnvironment reset for next test",function() {
+	deepEqual(this.foo, "bar");
+});
+
+module("testEnvironment with object", {
+	options:{
+		recipe:"soup",
+		ingredients:["hamster","onions"]
+	}
+});
+test("scope check", function() {
+	deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions"]}) ;
+});
+test("modify testEnvironment",function() {
+	// since we do a shallow copy, the testEnvironment can be modified
+	this.options.ingredients.push("carrots");
+});
+test("testEnvironment reset for next test",function() {
+	deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions","carrots"]}, "Is this a bug or a feature? Could do a deep copy") ;
+});
+
+
+module("testEnvironment tests");
+
+function makeurl() {
+	var testEnv = QUnit.current_testEnvironment;
+	var url = testEnv.url || 'http://example.com/search';
+	var q   = testEnv.q   || 'a search test';
+	return url + '?q='+encodeURIComponent(q);
+}
+
+test("makeurl working",function() {
+	equal( QUnit.current_testEnvironment, this, 'The current testEnvironment is global');
+	equal( makeurl(), 'http://example.com/search?q=a%20search%20test', 'makeurl returns a default url if nothing specified in the testEnvironment');
+});
+
+module("testEnvironment with makeurl settings", {
+	url: 'http://google.com/',
+	q: 'another_search_test'
+});
+test("makeurl working with settings from testEnvironment", function() {
+	equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to form the url');
+});
+test("each test can extend the module testEnvironment", {
+	q:'hamstersoup'
+}, function() {
+	equal( makeurl(), 'http://google.com/?q=hamstersoup', 'url from module, q from test');
+});
+
+module("jsDump");
+test("jsDump output", function() {
+	equals( QUnit.jsDump.parse([1, 2]), "[\n  1,\n  2\n]" );
+	equals( QUnit.jsDump.parse({top: 5, left: 0}), "{\n  \"top\": 5,\n  \"left\": 0\n}" );
+	if (typeof document !== 'undefined' && document.getElementById("qunit-header")) {
+		equals( QUnit.jsDump.parse(document.getElementById("qunit-header")), "<h1 id=\"qunit-header\"></h1>" );
+		equals( QUnit.jsDump.parse(document.getElementsByTagName("h1")), "[\n  <h1 id=\"qunit-header\"></h1>\n]" );
+	}
+});
+
+module("assertions");
+test("raises",function() {
+	function CustomError( message ) {
+		this.message = message;
+	}
+
+	CustomError.prototype.toString = function() {
+		return this.message;
+	};
+
+	raises(
+		function() {
+			throw "error"
+		}
+	);
+
+	raises(
+		function() {
+			throw "error"
+		},
+		'raises with just a message, no expected'
+	);
+
+	raises(
+		function() {
+			throw new CustomError();
+		},
+		CustomError,
+		'raised error is an instance of CustomError'
+	);
+
+	raises(
+		function() {
+			throw new CustomError("some error description");
+		},
+		/description/,
+		"raised error message contains 'description'"
+	);
+
+	raises(
+		function() {
+			throw new CustomError("some error description");
+		},
+		function( err ) {
+			if ( (err instanceof CustomError) && /description/.test(err) ) {
+				return true;
+			}
+		},
+		"custom validation function"
+	);
+
+});
+
+if (typeof document !== "undefined") {
+
+module("fixture");
+test("setup", function() {
+	document.getElementById("qunit-fixture").innerHTML = "foobar";
+});
+test("basics", function() {
+	equal( document.getElementById("qunit-fixture").innerHTML, "test markup", "automatically reset" );
+});
+
+}
+
+module("custom assertions");
+(function() {
+	function mod2(value, expected, message) {
+		var actual = value % 2;
+		QUnit.push(actual == expected, actual, expected, message);
+	}
+	test("mod2", function() {
+		mod2(2, 0, "2 % 2 == 0");
+		mod2(3, 1, "3 % 2 == 1");
+	})
+})();
+
+(function() {
+	var reset = QUnit.reset;
+	function afterTest() {
+		ok( false, "reset should not modify test status" );
+	}
+	module("reset");
+	test("reset runs assertions", function() {
+		QUnit.reset = function() {
+			afterTest();
+			reset.apply( this, arguments );
+		};
+	});
+	test("reset runs assertions2", function() {
+		QUnit.reset = reset;
+	});
+})();
+
+module("noglobals", {
+	teardown: function() {
+		delete window.badGlobalVariableIntroducedInTest;
+	}
+});
+test("let teardown clean up globals", function() {
+	// this test will always pass if run without ?noglobals=true
+	window.badGlobalVariableIntroducedInTest = true;
+});

+ 6014 - 0
js/libraries/balupton-history/vendor/right.js

@@ -0,0 +1,6014 @@
+/**
+ * RightJS v2.2.3 - http://rightjs.org
+ * Released under the terms of MIT license
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+/**
+ * The basic layout for RightJS builds
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+var RightJS = (function(window, document, Object, Array, String, Function, Number, Math, undefined) {
+
+/**
+ * The framework description object
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+var RightJS = function(value) {
+  return value; // <- a dummy method to emulate the safe-mode
+};
+
+RightJS.version = "2.2.3";
+RightJS.modules =["core", "dom", "form", "events", "xhr", "fx", "cookie"];
+
+
+
+/**
+ * There are some util methods
+ *
+ * Credits:
+ *   Some of the functionality and names are inspired or copied from
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+
+/**
+ * Some top-level variables to shortify the things
+ */
+var A_proto = Array.prototype,
+to_s = Object.prototype.toString, slice = A_proto.slice,
+HTML = document.documentElement, UID = 1,       // !#server
+Wrappers_Cache = [], UID_KEY = 'uniqueNumber',  // DON'T change the UID_KEY!
+
+/**
+ * extends the first object with the keys and values of the second one
+ *
+ * NOTE: the third optional argument tells if the existing values
+ *       of the first object should _NOT_ get updated by the values of the second object
+ *
+ * @param oritinal Object destintation object
+ * @param source Object source object
+ * @param Boolean flag if the function should not overwrite intersecting values
+ * @return Object extended destination object
+ */
+$ext = RightJS.$ext = function(dest, source, dont_overwrite) {
+  var src = source || {}, key;
+
+  for (key in src) {
+    if (!dont_overwrite || !(key in dest)) {
+      dest[key] = src[key];
+    }
+  }
+
+  return dest;
+},
+
+/** !#server
+ * evals the given javascript text in the context of the current window
+ *
+ * @param String javascript
+ * @return void
+ */
+$eval = RightJS.$eval = function(text) {
+  if (text) {
+    if ('execScript' in window) {
+      current_Document.win()._.execScript(text);
+    } else {
+      $E('script', {text: text}).insertTo(HTML);
+    }
+  }
+},
+
+/**
+ * throws an exception to break iterations throw a callback
+ *
+ * @return void
+ * @throws Break
+ */
+$break = RightJS.$break = function() {
+  throw new Break();
+},
+
+/**
+ * generates aliases for the object properties
+ *
+ * @param object Object object
+ * @param names Object aliases hash
+ * @return Object the extended objects
+ */
+$alias = RightJS.$alias = function(object, names) {
+  for (var new_name in names) {
+    object[new_name] = object[names[new_name]];
+  }
+  return object;
+},
+
+/**
+ * checks if the given value or a reference points
+ * to a really defined value
+ *
+ * NOTE: will return true for variables equal to null, false, 0, and so one.
+ *
+ * EXAMPLE:
+ *
+ *   var smth = null;
+ *   defined(smth); <- will return true
+ *
+ *   var obj = {};
+ *   defined(obj['smth']); <- will return false
+ *
+ * @param mixed value
+ * @return boolean check result
+ */
+defined = RightJS.defined = function(value) {
+  return typeof(value) !== 'undefined';
+},
+
+
+/**
+ * checks if the given value is a function
+ *
+ * @param mixed value
+ * @return boolean check result
+ */
+isFunction = RightJS.isFunction = function(value) {
+  return typeof(value) === 'function';
+},
+
+/**
+ * checks if the given value is a string
+ *
+ * @param mixed value
+ * @return boolean check result
+ */
+isString = RightJS.isString = function(value) {
+  return typeof(value) === 'string';
+},
+
+
+/**
+ * checks if the given value is a number
+ *
+ * @param mixed value to check
+ * @return boolean check result
+ */
+isNumber = RightJS.isNumber = function(value) {
+  return typeof(value) === 'number';
+},
+
+/**
+ * checks if the given value is a hash-like object
+ *
+ * @param mixed value
+ * @return boolean check result
+ */
+isHash = RightJS.isHash = function(value) {
+  return to_s.call(value) === '[object Object]';
+},
+
+/**
+ * checks if the given value is an array
+ *
+ * @param mixed value to check
+ * @return boolean check result
+ */
+isArray = RightJS.isArray = function(value) {
+  return to_s.call(value) === '[object Array]';
+},
+
+/** !#server
+ * checks if the given value is an element
+ *
+ * @param mixed value to check
+ * @return boolean check result
+ */
+isElement = RightJS.isElement = function(value) {
+  return value != null && value.nodeType === 1;
+},
+
+/** !#server
+ * checks if the given value is a DOM-node
+ *
+ * @param mixed value to check
+ * @return boolean check result
+ */
+isNode = RightJS.isNode = function(value) {
+  return value != null && value.nodeType != null;
+},
+
+/** !#server
+ * searches an element by id and/or extends it with the framework extentions
+ *
+ * @param String element id or Element to extend
+ * @return Element or null
+ */
+$ = RightJS.$ = function(object) {
+  if (object instanceof Wrapper) {
+    return object;
+  } else if (typeof object === 'string') {
+    object = document.getElementById(object);
+  }
+
+  return wrap(object);
+},
+
+/** !#server
+ * Finds all the elements in the document by the given css_rule
+ *
+ * @param String element
+ * @param Boolean raw search marker
+ * @return Array search result
+ */
+$$ = RightJS.$$ = function(css_rule, raw) {
+  return current_Document.find(css_rule, raw);
+},
+
+/** !#server
+ * shortcut to instance new elements
+ *
+ * @param String tag name
+ * @param object options
+ * @return Element instance
+ */
+$E = RightJS.$E = function(tag_name, options) {
+  return new Element(tag_name, options);
+},
+
+/**
+ * shortcut, generates an array of words from a given string
+ *
+ * @param String string
+ * @return Array of words
+ */
+$w = RightJS.$w = function(string) {
+  return string.trim().split(/\s+/);
+},
+
+/**
+ * generates an unique id for an object
+ *
+ * @param Object object
+ * @return Integer uniq id
+ */
+$uid = RightJS.$uid = function(item) {
+  return UID_KEY in item ? item[UID_KEY] : (item[UID_KEY] = UID++);
+},
+
+/**
+ * converts any iterables into an array
+ *
+ * @param Object iterable
+ * @return Array list
+ */
+$A = RightJS.$A = function(it) {
+  return slice.call(it, 0);
+};
+
+/** !#server
+ * IE needs a patch for the $A function
+ * because it doesn't handle all the cases
+ */
+if (!A_proto.map) {
+  $A = RightJS.$A = function(it) {
+    try {
+      return slice.call(it, 0);
+    } catch(e) {
+      for (var a=[], i=0, length = it.length; i < length; i++) {
+        a[i] = it[i];
+      }
+      return a;
+    }
+  };
+}
+
+/** !#server
+ * Internet Explorer needs some additional mumbo-jumbo in here
+ */
+if (isHash(HTML)) {
+  isHash = RightJS.isHash = function(value) {
+    return to_s.call(value) === '[object Object]' &&
+      value != null && value.hasOwnProperty != null;
+  };
+}
+
+
+/**
+ * Generating methods for native units extending
+ */
+// adds a standard '.include' method to the native unit
+function extend_native(klass) {
+  return $ext(klass, {
+    Methods: {},
+    include: function() {
+      for (var i=0, l = arguments.length; i < l; i++) {
+        if (isHash(arguments[i])) {
+          $ext(klass.prototype, arguments[i]);
+          $ext(klass.Methods,   arguments[i]);
+        }
+      }
+    }
+  });
+}
+
+for (var i=0, natives = 'Array Function Number String Date RegExp'.split(' '); i < natives.length; i++) {
+  RightJS[natives[i]] = extend_native(new Function('return '+ natives[i])());
+}
+
+// referring those two as well
+RightJS.Object = Object;
+RightJS.Math   = Math;
+
+
+/**
+ * Checks if the data is an array and if not,
+ * then makes an array out of it
+ *
+ * @param mixed in data
+ * @return Array data
+ */
+function ensure_array(data) {
+  return isArray(data) ? data : [data];
+}
+
+
+/**
+ * The Object class extentions
+ *
+ * Credits:
+ *   Some functionality is inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+$ext(Object, {
+  /**
+   * extracts the list of the attribute names of the given object
+   *
+   * @param Object object
+   * @return Array keys list
+   */
+  keys: function(object) {
+    var keys = [], key;
+    for (key in object) {
+      keys.push(key);
+    }
+    return keys;
+  },
+
+  /**
+   * extracts the list of the attribute values of the given object
+   *
+   * @param Object object
+   * @return Array values list
+   */
+  values: function(object) {
+    var values = [], key;
+    for (key in object) {
+      values.push(object[key]);
+    }
+    return values;
+  },
+
+  /**
+   * Calls the function with every key/value pair on the hash
+   *
+   * @param in Object the data hash
+   * @param Function the callback
+   * @param scope Object an optional scope
+   * @return Object the original hash
+   */
+  each: function(object, callback, scope) {
+    for (var key in object) {
+      callback.call(scope, key, object[key]);
+    }
+
+    return object;
+  },
+
+  /**
+   * checks if the object-hash has no keys
+   *
+   * @param Object object
+   * @return check result
+   */
+  empty: function(object) {
+    for (var key in object) { return false; }
+    return true;
+  },
+
+  /**
+   * A simple cloning method
+   * NOTE: does not clone the things recoursively!
+   *
+   * @param Object object
+   * @return Object clone
+   */
+  clone: function(object) {
+    return Object.merge(object);
+  },
+
+  /**
+   * returns a copy of the object which contains
+   * all the same keys/values except the key-names
+   * passed the the method arguments
+   *
+   * @param Object object
+   * @param String key-name to exclude
+   * .....
+   * @return Object filtered copy
+   */
+  without: function() {
+    var filter = $A(arguments), object = filter.shift(), copy = {}, key;
+
+    for (key in object) {
+      if (!filter.include(key)) {
+        copy[key] = object[key];
+      }
+    }
+
+    return copy;
+  },
+
+  /**
+   * returns a copy of the object which contains all the
+   * key/value pairs from the specified key-names list
+   *
+   * NOTE: if some key does not exists in the original object, it will be just skipped
+   *
+   * @param Object object
+   * @param String key name to exclude
+   * .....
+   * @return Object filtered copy
+   */
+  only: function() {
+    var filter = $A(arguments), object = filter.shift(), copy = {},
+        i=0, length = filter.length;
+
+    for (; i < length; i++) {
+      if (filter[i] in object) {
+        copy[filter[i]] = object[filter[i]];
+      }
+    }
+
+    return copy;
+  },
+
+  /**
+   * merges the given objects and returns the result
+   *
+   * NOTE this method _DO_NOT_ change the objects, it creates a new object
+   *      which conatins all the given ones.
+   *      if there is some keys introspections, the last object wins.
+   *      all non-object arguments will be omitted
+   *
+   * @param first Object object
+   * @param second Object mixing
+   * ......
+   * @return Object merged object
+   */
+  merge: function() {
+    var object = {}, i=0, args=arguments, key;
+    for (l = args.length; i < l; i++) {
+      if (isHash(args[i])) {
+        for (key in args[i]) {
+          object[key] = isHash(args[i][key]) && !(args[i][key] instanceof Class) ?
+            Object.merge(key in object ? object[key] : {}, args[i][key]) : args[i][key];
+        }
+      }
+    }
+    return object;
+  },
+
+  /**
+   * converts a hash-object into an equivalent url query string
+   *
+   * @param Object object
+   * @return String query
+   */
+  toQueryString: function(object) {
+    var tokens = [], key, value, encode = encodeURIComponent;
+    for (key in object) {
+      value = ensure_array(object[key]);
+      for (var i=0, l = value.length; i < l; i++) {
+        tokens.push(encode(key) +'='+ encode(value[i]));
+      }
+    }
+    return tokens.join('&');
+  }
+}, true);
+
+
+/**
+ * here are the starndard Math object extends
+ *
+ * Credits:
+ *   The idea of random mehtod is taken from
+ *     - Ruby      (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
+ *
+ * Copyright (C) 2008-2010 Nikolay Nemshilov
+ */
+var Math_old_random = Math.random;
+
+/**
+ * the standard random method replacement, to make it more useful
+ *
+ * USE:
+ *   Math.random();    // original functionality, returns a float between 0 and 1
+ *   Math.random(10);  // returns an integer between 0 and 10
+ *   Math.random(1,4); // returns an integer between 1 and 4
+ *
+ * @param min Integer minimum value if there's two arguments and maximum value if there's only one
+ * @param max Integer maximum value
+ * @return Float random between 0 and 1 if there's no arguments or an integer in the given range
+ */
+Math.random = function(min, max) {
+
+  if (arguments.length === 0) {
+    return Math_old_random();
+  } else if (arguments.length === 1) {
+    max = min;
+    min = 0;
+  }
+
+  return ~~(Math_old_random() * (max-min+1) + ~~min);
+};
+
+
+/**
+ * The Array class extentions
+ *
+ * Credits:
+ *   Some of the functionality is inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *     - Ruby      (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
+ *
+ * Copyright (C) 2008-2010 Nikolay Nemshilov
+ */
+var original_sort = A_proto.sort,
+
+// JavaScript 1.6 methods recatching up or faking
+for_each = A_proto.forEach || function(callback, scope) {
+  for (var i=0, l=this.length; i < l; i++) {
+    callback.call(scope, this[i], i, this);
+  }
+},
+
+filter   = A_proto.filter || function(callback, scope) {
+  for (var result=[], j=0, i=0, l=this.length; i < l; i++) {
+    if (callback.call(scope, this[i], i, this)) {
+      result[j++] = this[i];
+    }
+  }
+  return result;
+},
+
+reject   = function(callback, scope) {
+  for (var result=[], j=0, i=0, l=this.length; i < l; i++) {
+    if (!callback.call(scope, this[i], i, this)) {
+      result[j++] = this[i];
+    }
+  }
+  return result;
+},
+
+map      = A_proto.map || function(callback, scope) {
+  for (var result=[], i=0, l=this.length; i < l; i++) {
+    result[i] = callback.call(scope, this[i], i, this);
+  }
+  return result;
+},
+
+some     = A_proto.some || function(callback, scope) {
+  for (var i=0, l=this.length; i < l; i++) {
+    if (callback.call(scope, this[i], i, this)) {
+      return true;
+    }
+  }
+  return false;
+},
+
+every    = A_proto.every || function(callback, scope) {
+  for (var i=0, l=this.length; i < l; i++) {
+    if (!callback.call(scope, this[i], i, this)) {
+      return false;
+    }
+  }
+  return true;
+},
+
+first    = function(callback, scope) {
+  for (var i=0, l=this.length; i < l; i++) {
+    if (callback.call(scope, this[i], i, this)) {
+      return this[i];
+    }
+  }
+  return undefined;
+},
+
+last     = function(callback, scope) {
+  for (var i=this.length-1; i > -1; i--) {
+    if (callback.call(scope, this[i], i, this)) {
+      return this[i];
+    }
+  }
+  return undefined;
+};
+
+
+//
+// RightJS callbacks magick preprocessing
+//
+
+// prepares a correct callback function
+function guess_callback(argsi, array) {
+  var callback = argsi[0], args = slice.call(argsi, 1), scope = array, attr;
+
+  if (typeof(callback) === 'string') {
+    attr = callback;
+    if (array.length !== 0 && typeof(array[0][attr]) === 'function') {
+      callback = function(object) { return object[attr].apply(object, args); };
+    } else {
+      callback = function(object) { return object[attr]; };
+    }
+  } else {
+    scope = args[0];
+  }
+
+  return [callback, scope];
+}
+
+// defining the manual break errors class
+function Break() {}
+
+// calls the given method with preprocessing the arguments
+function call_method(func, scope, args) {
+  try {
+    return func.apply(scope, guess_callback(args, scope));
+  } catch(e) { if (!(e instanceof Break)) { throw(e); } }
+
+  return undefined;
+}
+
+// checks the value as a boolean
+function boolean_check(i) {
+  return !!i;
+}
+
+// default sorting callback
+function default_sort(a, b) {
+  return a > b ? 1 : a < b ? -1 : 0;
+}
+
+Array.include({
+  /**
+   * IE fix
+   * returns the index of the value in the array
+   *
+   * @param mixed value
+   * @param Integer optional offset
+   * @return Integer index or -1 if not found
+   */
+  indexOf: A_proto.indexOf || function(value, from) {
+    for (var i=(from<0) ? Math.max(0, this.length+from) : from || 0, l=this.length; i < l; i++) {
+      if (this[i] === value) {
+        return i;
+      }
+    }
+    return -1;
+  },
+
+  /**
+   * IE fix
+   * returns the last index of the value in the array
+   *
+   * @param mixed value
+   * @return Integer index or -1 if not found
+   */
+  lastIndexOf: A_proto.lastIndexOf || function(value) {
+    for (var i=this.length-1; i > -1; i--) {
+      if (this[i] === value) {
+        return i;
+      }
+    }
+    return -1;
+  },
+
+  /**
+   * returns the first element of the array
+   *
+   * @return mixed first element of the array
+   */
+  first: function() {
+    return arguments.length ? call_method(first, this, arguments) : this[0];
+  },
+
+  /**
+   * returns the last element of the array
+   *
+   * @return mixed last element of the array
+   */
+  last: function() {
+    return arguments.length ? call_method(last, this, arguments) : this[this.length-1];
+  },
+
+  /**
+   * returns a random item of the array
+   *
+   * @return mixed a random item
+   */
+  random: function() {
+    return this.length === 0 ? undefined : this[Math.random(this.length-1)];
+  },
+
+  /**
+   * returns the array size
+   *
+   * @return Integer the array size
+   */
+  size: function() {
+    return this.length;
+  },
+
+  /**
+   * cleans the array
+   * @return Array this
+   */
+  clean: function() {
+    this.length = 0;
+    return this;
+  },
+
+  /**
+   * checks if the array has no elements in it
+   *
+   * @return boolean check result
+   */
+  empty: function() {
+    return this.length === 0;
+  },
+
+  /**
+   * creates a copy of the given array
+   *
+   * @return Array copy of the array
+   */
+  clone: function() {
+    return this.slice(0);
+  },
+
+  /**
+   * calls the given callback function in the given scope for each element of the array
+   *
+   * @param Function callback
+   * @param Object scope
+   * @return Array this
+   */
+  each: function() {
+    call_method(for_each, this, arguments);
+    return this;
+  },
+  forEach: for_each,
+
+  /**
+   * creates a list of the array items converted in the given callback function
+   *
+   * @param Function callback
+   * @param Object optional scope
+   * @return Array collected
+   */
+  map: function() {
+    return call_method(map, this, arguments);
+  },
+
+  /**
+   * creates a list of the array items which are matched in the given callback function
+   *
+   * @param Function callback
+   * @param Object optional scope
+   * @return Array filtered copy
+   */
+  filter: function() {
+    return call_method(filter, this, arguments);
+  },
+
+  /**
+   * creates a list of the array items that are not matching the give callback function
+   *
+   * @param Function callback
+   * @param Object optionl scope
+   * @return Array filtered copy
+   */
+  reject: function() {
+    return call_method(reject, this, arguments);
+  },
+
+  /**
+   * checks if any of the array elements is logically true
+   *
+   * @param Function optional callback for checks
+   * @param Object optional scope for the callback
+   * @return boolean check result
+   */
+  some: function(value) {
+    return call_method(some, this, value ? arguments : [boolean_check]);
+  },
+
+  /**
+   * checks if all the array elements are logically true
+   *
+   * @param Function optional callback for checks
+   * @param Object optional scope for the callback
+   * @return Boolean check result
+   */
+  every: function(value) {
+    return call_method(every, this, value ? arguments : [boolean_check]);
+  },
+
+  /**
+   * applies the given lambda to each element in the array
+   *
+   * NOTE: changes the array by itself
+   *
+   * @param Function callback
+   * @param Object optional scope
+   * @return Array this
+   */
+  walk: function() {
+    this.map.apply(this, arguments).forEach(function(value, i) { this[i] = value; }, this);
+    return this;
+  },
+
+  /**
+   * similar to the concat function but it adds only the values which are not on the list yet
+   *
+   * @param Array to merge
+   * ....................
+   * @return Array new merged
+   */
+  merge: function() {
+    for (var copy = this.clone(), arg, i=0; i < arguments.length; i++) {
+      arg = ensure_array(arguments[i]);
+
+      for (var j=0; j < arg.length; j++) {
+        if (copy.indexOf(arg[j]) == -1) {
+          copy.push(arg[j]);
+        }
+      }
+    }
+    return copy;
+  },
+
+  /**
+   * flats out complex array into a single dimension array
+   *
+   * @return Array flatten copy
+   */
+  flatten: function() {
+    var copy = [];
+    this.forEach(function(value) {
+      if (isArray(value)) {
+        copy = copy.concat(value.flatten());
+      } else {
+        copy.push(value);
+      }
+    });
+    return copy;
+  },
+
+  /**
+   * returns a copy of the array whithout any null or undefined values
+   *
+   * @return Array filtered version
+   */
+  compact: function() {
+    return this.without(null, undefined);
+  },
+
+  /**
+   * returns a copy of the array which contains only the unique values
+   *
+   * @return Array filtered copy
+   */
+  uniq: function() {
+    return [].merge(this);
+  },
+
+  /**
+   * checks if all of the given values
+   * exists in the given array
+   *
+   * @param mixed value
+   * ....
+   * @return boolean check result
+   */
+  includes: function() {
+    for (var i=0; i < arguments.length; i++) {
+      if (this.indexOf(arguments[i]) === -1) {
+        return false;
+      }
+    }
+    return true;
+  },
+
+  /**
+   * returns a copy of the array without the items passed as the arguments
+   *
+   * @param mixed value
+   * ......
+   * @return Array filtered copy
+   */
+  without: function() {
+    var filter = slice.call(arguments);
+    return this.filter(function(value) {
+      return filter.indexOf(value) === -1;
+    });
+  },
+
+  /**
+   * Shuffles the array items in a random order
+   *
+   * @return Array shuffled version
+   */
+  shuffle: function() {
+    var shuff = this.clone(), j, x, i = shuff.length;
+
+    for (; i > 0; j = Math.random(i-1), x = shuff[--i], shuff[i] = shuff[j], shuff[j] = x) {}
+
+    return shuff;
+  },
+
+  /**
+   * Default sort fix for numeric values
+   *
+   * @param Function callback
+   * @return Array self
+   */
+  sort: function(callback) {
+    return original_sort.apply(this, (callback || !isNumber(this[0])) ? arguments : [default_sort]);
+  },
+
+  /**
+   * sorts the array by running its items though a lambda or calling their attributes
+   *
+   * @param Function callback or attribute name
+   * @param Object scope or attribute argument
+   * @return Array sorted copy
+   */
+  sortBy: function() {
+    var pair = guess_callback(arguments, this);
+
+    return this.sort(function(a, b) {
+      return default_sort(
+        pair[0].call(pair[1], a),
+        pair[0].call(pair[1], b)
+      );
+    });
+  },
+
+  /**
+   * Returns the minimal value on the list
+   *
+   * @return Number minimal value
+   */
+  min: function() {
+    return Math.min.apply(Math, this);
+  },
+
+  /**
+   * Returns the maximal value
+   *
+   * @return Number maximal value
+   */
+  max: function() {
+    return Math.max.apply(Math, this);
+  },
+
+  /**
+   * Returns a summ of all the items on the list
+   *
+   * @return Number a summ of values on the list
+   */
+  sum: function() {
+    for(var sum=0, i=0, l=this.length; i < l; sum += this[i++]) {}
+    return sum;
+  }
+});
+
+A_proto.include = A_proto.includes;
+
+
+/**
+ * The String class extentions
+ *
+ * Credits:
+ *   Some of the functionality inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *   The trim function taken from work of Steven Levithan
+ *     - http://blog.stevenlevithan.com/archives/faster-trim-javascript
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+String.include({
+  /**
+   * checks if the string is an empty string
+   *
+   * @return boolean check result
+   */
+  empty: function() {
+    return this == '';
+  },
+
+  /**
+   * checks if the string contains only white-spaces
+   *
+   * @return boolean check result
+   */
+  blank: function() {
+    return this == false;
+  },
+
+  /**
+   * removes trailing whitespaces
+   *
+   * @return String trimmed version
+   */
+  trim: String.prototype.trim || function() {
+    var str = this.replace(/^\s\s*/, ''), i = str.length;
+    while ((/\s/).test(str.charAt(--i))) {}
+    return str.slice(0, i + 1);
+  },
+
+  /**
+   * returns a copy of the string with all the tags removed
+   * @return String without tags
+   */
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/ig, '');
+  },
+
+  /**
+   * removes all the scripts declarations out of the string
+   * @param mixed option. If it equals true the scrips will be executed,
+   *                      if a function the scripts will be passed in it
+   * @return String without scripts
+   */
+  stripScripts: function(option) {
+    var scripts = '', text = this.replace(
+      /<script[^>]*>([\s\S]*?)<\/script>/img,
+      function(match, source) {
+        scripts += source + "\n";
+        return '';
+      }
+    );
+
+    if (option === true) {
+      $eval(scripts);
+    } else if (isFunction(option)) {
+      option(scripts, text);
+    }
+
+    return text;
+  },
+
+  /**
+   * extracts all the scripts out of the string
+   *
+   * @return String the extracted stcripts
+   */
+  extractScripts: function() {
+    var scripts = '';
+    this.stripScripts(function(s) { scripts = s; });
+    return scripts;
+  },
+
+  /**
+   * evals all the scripts in the string
+   *
+   * @return String self (unchanged version with scripts still in their place)
+   */
+  evalScripts: function() {
+    this.stripScripts(true);
+    return this;
+  },
+
+  /**
+   * converts underscored or dasherized string to a camelized one
+   * @returns String camelized version
+   */
+  camelize: function() {
+    return this.replace(/(\-|_)+(.)?/g, function(match, dash, chr) {
+      return chr ? chr.toUpperCase() : '';
+    });
+  },
+
+  /**
+   * converts a camelized or dasherized string into an underscored one
+   * @return String underscored version
+   */
+  underscored: function() {
+    return this.replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-/g, '_').toLowerCase();
+  },
+
+  /**
+   * returns a capitalised version of the string
+   *
+   * @return String captialised version
+   */
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  /**
+   * checks if the string contains the given substring
+   *
+   * @param String string
+   * @return boolean check result
+   */
+  includes: function(string) {
+    return this.indexOf(string) != -1;
+  },
+
+  /**
+   * checks if the string starts with the given substring
+   *
+   * @param String string
+   * @param boolean ignore the letters case
+   * @return boolean check result
+   */
+  startsWith: function(string, ignorecase) {
+    return (ignorecase !== true ? this.indexOf(string) :
+      this.toLowerCase().indexOf(string.toLowerCase())
+    ) === 0;
+  },
+
+  /**
+   * checks if the string ends with the given substring
+   *
+   * @param String substring
+   * @param boolean ignore the letters case
+   * @return boolean check result
+   */
+  endsWith: function(string, ignorecase) {
+    return this.length - (
+      ignorecase !== true ? this.lastIndexOf(string) :
+        this.toLowerCase().lastIndexOf(string.toLowerCase())
+    ) === string.length;
+  },
+
+  /**
+   * converts the string to an integer value
+   * @param Integer base
+   * @return Integer or NaN
+   */
+  toInt: function(base) {
+    return parseInt(this, base === undefined ? 10 : base);
+  },
+
+  /**
+   * converts the string to a float value
+   * @param boolean flat if the method should not use a flexible matching
+   * @return Float or NaN
+   */
+  toFloat: function(strict) {
+    return parseFloat(strict === true ? this :
+      this.replace(',', '.').replace(/(\d)-(\d)/, '$1.$2'));
+  }
+
+});
+
+String.prototype.include = String.prototype.includes;
+
+
+/**
+ * The Function class extentions
+ *
+ * Credits:
+ *   Some of the functionality inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Function.include({
+  /**
+   * binds the function to be executed in the given scope
+   *
+   * @param Object scope
+   * @param mixed optional curry (left) argument
+   * ....
+   * @return Function binded function
+   */
+  bind: function() {
+    var args = $A(arguments), scope = args.shift(), func = this;
+    return function() {
+      return func.apply(scope,
+        (args.length !== 0 || arguments.length !== 0) ?
+          args.concat($A(arguments)) : args
+      );
+    };
+  },
+
+  /**
+   * binds the function as an event listener to the given scope object
+   *
+   * @param Object scope
+   * @param mixed optional curry (left) argument
+   * .......
+   * @return Function binded function
+   */
+  bindAsEventListener: function() {
+    var args = $A(arguments), scope = args.shift(), func = this;
+    return function(event) {
+      return func.apply(scope, [event].concat(args).concat($A(arguments)));
+    };
+  },
+
+  /**
+   * allows you to put some curry in your cookery
+   *
+   * @param mixed value to curry
+   * ....
+   * @return Function curried function
+   */
+  curry: function() {
+    return this.bind.apply(this, [this].concat($A(arguments)));
+  },
+
+  /**
+   * The right side curry feature
+   *
+   * @param mixed value to curry
+   * ....
+   * @return Function curried function
+   */
+  rcurry: function() {
+    var curry = $A(arguments), func = this;
+    return function() {
+      return func.apply(func, $A(arguments).concat(curry));
+    };
+  },
+
+  /**
+   * delays the function execution
+   *
+   * @param Integer delay ms
+   * @param mixed value to curry
+   * .....
+   * @return Integer timeout marker
+   */
+  delay: function() {
+    var args  = $A(arguments), timeout = args.shift(),
+        timer = new Number(setTimeout(this.bind.apply(this, [this].concat(args)), timeout));
+
+    timer.cancel = function() { clearTimeout(this); };
+
+    return timer;
+  },
+
+  /**
+   * creates a periodical execution of the function with the given timeout
+   *
+   * @param Integer delay ms
+   * @param mixed value to curry
+   * ...
+   * @return Ineger interval marker
+   */
+  periodical: function() {
+    var args  = $A(arguments), timeout = args.shift(),
+        timer = new Number(setInterval(this.bind.apply(this, [this].concat(args)), timeout));
+
+    timer.stop = function() { clearInterval(this); };
+
+    return timer;
+  },
+
+  /**
+   * Chains the given function after the current one
+   *
+   * @param Function the next function
+   * @param mixed optional value to curry
+   * ......
+   * @return Function chained function
+   */
+  chain: function() {
+    var args = $A(arguments), func = args.shift(), current = this;
+    return function() {
+      var result = current.apply(current, arguments);
+      func.apply(func, args);
+      return result;
+    };
+  }
+});
+
+
+/**
+ * The Number class extentions
+ *
+ * Credits:
+ *   Some methods inspired by
+ *     - Ruby      (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
+ *
+ * Copyright (C) 2008-2010 Nikolay V. Nemshilov
+ */
+Number.include({
+  /**
+   * executes the given callback the given number of times
+   *
+   * @param Function callback
+   * @param Object optional callback execution scope
+   * @return void
+   */
+  times: function(callback, scope) {
+    for (var i=0; i < this; i++) {
+      callback.call(scope, i);
+    }
+    return this;
+  },
+
+  upto: function(number, callback, scope) {
+    for (var i=this+0; i <= number; i++) {
+      callback.call(scope, i);
+    }
+    return this;
+  },
+
+  downto: function(number, callback, scope) {
+    for (var i=this+0; i >= number; i--) {
+      callback.call(scope, i);
+    }
+    return this;
+  },
+
+  abs: function() {
+    return Math.abs(this);
+  },
+
+  round: function(size) {
+    return size ? parseFloat(this.toFixed(size)) : Math.round(this);
+  },
+
+  ceil: function() {
+    return Math.ceil(this);
+  },
+
+  floor: function() {
+    return Math.floor(this);
+  },
+
+  min: function(value) {
+    return this < value ? value : this + 0;
+  },
+
+  max: function(value) {
+    return this > value ? value : this + 0;
+  }
+});
+
+
+/**
+ * The Regexp class extentions
+ *
+ * Credits:
+ *   Inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *
+ * Copyright (C) 2008-2010 Nikolay V. Nemshilov
+ */
+
+
+ /**
+  * Escapes the string for safely use as a regular expression
+  *
+  * @param String raw string
+  * @return String escaped string
+  */
+RegExp.escape = function(string) {
+  return (''+string).replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1');
+};
+
+
+/**
+ * The basic Class unit
+ *
+ * Credits:
+ *   The Class unit is inspired by its implementation in
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *     - Ruby      (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+var Class = RightJS.Class = function() {
+  var args   = $A(arguments).slice(0,2),
+      props  = args.pop() || {},
+      parent = args.pop(),
+      klass  = arguments[2], // you can send your own klass as the third argument
+      SKlass = function() {};
+
+  // if the parent class only was specified
+  if (!args.length && !isHash(props)) {
+    parent = props; props = {};
+  }
+
+// !#server:begin
+  if (!klass && parent && (parent === Wrapper || parent.ancestors.include(Wrapper))) {
+    klass = Wrapper_makeKlass();
+  }
+// !#server:end
+
+  // defining the basic klass function
+  klass = $ext(klass || function() {
+    Class_checkPrebind(this);
+    return 'initialize' in this ?
+      this.initialize.apply(this, arguments) :
+      this;
+  }, Class_Methods);
+
+  // handling the inheritance
+  parent = parent || Class;
+
+  SKlass.prototype = parent.prototype;
+  klass.prototype  = new SKlass();
+  klass.parent     = parent;
+  klass.prototype.constructor = klass;
+
+  // collecting the list of ancestors
+  klass.ancestors = [];
+  while (parent) {
+    klass.ancestors.push(parent);
+    parent = parent.parent;
+  }
+
+  // handling the module injections
+  ['extend', 'include'].each(function(name) {
+    if (name in props) {
+      klass[name].apply(klass, ensure_array(props[name]));
+    }
+  });
+
+  return klass.include(props);
+},
+
+/**
+ * Class utility methods
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+Class_Methods = {
+  /**
+   * this method will extend the class-level with the given objects
+   *
+   * NOTE: this method _WILL_OVERWRITE_ the existing itercecting entries
+   *
+   * NOTE: this method _WILL_NOT_OVERWRITE_ the class prototype and
+   *       the class 'name' and 'parent' attributes. If one of those
+   *       exists in one of the received modeuls, the attribute will be
+   *       skipped
+   *
+   * @param Object module to extend
+   * ....
+   * @return Class the klass
+   */
+  extend: function() {
+    $A(arguments).filter(isHash).each(function(module) {
+      $ext(this, Class_clean_module(module, true));
+      Class_handle_module_callbacks(this, module, true);
+    }, this);
+
+    return this;
+  },
+
+  /**
+   * extends the class prototype with the given objects
+   * NOTE: this method _WILL_OVERWRITE_ the existing itercecting entries
+   * NOTE: this method _WILL_NOT_OVERWRITE_ the 'klass' attribute of the klass.prototype
+   *
+   * @param Object module to include
+   * ....
+   * @return Class the klass
+   */
+  include: function() {
+    var klasses = [this].concat(this.ancestors);
+
+    $A(arguments).filter(isHash).each(function(module) {
+      Object.each(Class_clean_module(module, false), function(name, method) {
+        // searching for the super-method
+        for (var super_method, i=0, l = klasses.length; i < l; i++) {
+          if (name in klasses[i].prototype) {
+            super_method = klasses[i].prototype[name];
+            break;
+          }
+        }
+
+        this.prototype[name] = isFunction(method) && isFunction(super_method) ?
+          function() {
+            this.$super = super_method;
+            return method.apply(this, arguments);
+          } : method;
+      }, this);
+
+      Class_handle_module_callbacks(this, module, false);
+    }, this);
+
+    return this;
+  }
+},
+
+Class_module_callback_names = $w(
+  'selfExtended self_extended selfIncluded self_included extend include'
+);
+
+// hooking up the class-methods to the root class
+$ext(Class, Class_Methods);
+Class.prototype.$super = undefined;
+
+function Class_clean_module(module, extend) {
+  return Object.without.apply(Object, [module].concat(
+    Class_module_callback_names.concat( extend ?
+      $w('prototype parent ancestors') : ['constructor']
+    )
+  ));
+}
+
+function Class_handle_module_callbacks(klass, module, extend) {
+  (module[Class_module_callback_names[extend ? 0 : 2]] ||
+   module[Class_module_callback_names[extend ? 1 : 3]] ||
+   function() {}
+  ).call(module, klass);
+}
+
+/**
+ * This method gets through a list of the object its class and all the ancestors
+ * and finds a hash named after property, used for configuration purposes with
+ * the Observer and Options modules
+ *
+ * NOTE: this method will look for capitalized and uppercased versions of the
+ *       property name
+ *
+ * @param Object a class instance
+ * @param String property name
+ * @return Object hash or null if nothing found
+ */
+function Class_findSet(object, property) {
+  var upcased   = property.toUpperCase(),
+    constructor = object.constructor,
+    candidates  = [object, constructor].concat(constructor.ancestors || []),
+    i = 0;
+
+  for (l = candidates.length; i < l; i++) {
+    if (upcased in candidates[i]) {
+      return candidates[i][upcased];
+    } else if (property in candidates[i]) {
+      return candidates[i][property];
+    }
+  }
+
+  return null;
+}
+
+/**
+ * Handles the 'prebind' feature for Class instances
+ *
+ * @param Class instance
+ * @return void
+ */
+function Class_checkPrebind(object) {
+  if ('prebind' in object && isArray(object.prebind)) {
+    object.prebind.each(function(method) {
+      object[method] = object[method].bind(object);
+    });
+  }
+}
+
+/**
+ * This is a simple mix-in module to be included in other classes
+ *
+ * Basically it privdes the <tt>setOptions</tt> method which processes
+ * an instance options assigment and merging with the default options
+ *
+ * Credits:
+ *   The idea of the module is inspired by
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+var Options = RightJS.Options = {
+  /**
+   * assigns the options by merging them with the default ones
+   *
+   * @param Object options
+   * @return Object current instance
+   */
+  setOptions: function(opts) {
+    var options = this.options = $ext($ext({},
+      Object.clone(Class_findSet(this, 'Options'))), opts
+    ), match, key;
+
+    // hooking up the observer options
+    if (isFunction(this.on)) {
+      for (key in options) {
+        if ((match = key.match(/on([A-Z][A-Za-z]+)/))) {
+          this.on(match[1].toLowerCase(), options[key]);
+          delete(options[key]);
+        }
+      }
+    }
+
+    return this;
+  },
+
+  /**
+   * Cuts of an options hash from the end of the arguments list
+   * assigns them using the #setOptions method and then
+   * returns the list of other arguments as an Array instance
+   *
+   * @param mixed iterable
+   * @return Array of the arguments
+   */
+  cutOptions: function(in_args) {
+    var args = $A(in_args);
+    this.setOptions(isHash(args.last()) ? args.pop() : {});
+    return args;
+  }
+};
+
+
+/**
+ * standard Observer class.
+ *
+ * Might be used as a usual class or as a builder over another objects
+ *
+ * Credits:
+ *   The naming principle is inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+var Observer = RightJS.Observer = new Class({
+  include: Options,
+
+  /**
+   * general constructor
+   *
+   * @param Object options
+   */
+  initialize: function(options) {
+    this.setOptions(options);
+    Observer_createShortcuts(this, Class_findSet(this, 'Events'));
+    return this;
+  },
+
+  /**
+   * binds an event listener
+   *
+   * USAGE:
+   *  on(String event, Function callback[, arguments, ...]);
+   *  on(String event, String method_name[, arguments, ...]);
+   *  on(Object events_hash);
+   *
+   * @return Observer self
+   */
+  on: function() {
+    Observer_on(this, arguments, function(h) { return h; });
+    return this;
+  },
+
+  /**
+   * checks if the observer observes given event and/or callback
+   *
+   * USAGE:
+   *   observes(String event)
+   *   observes(Function callback)
+   *   observes(String event, Function callback)
+   *
+   * @retun boolean check result
+   */
+  observes: function(event, callback) {
+    if (!isString(event)) { callback = event; event = null; }
+    if (isString(callback)) { callback = callback in this ? this[callback] : null; }
+
+    return (this.$listeners || []).some(function(i) {
+      return (event && callback) ? i.e === event && i.f === callback :
+        event ? i.e === event : i.f === callback;
+    });
+  },
+
+  /**
+   * stops observing an event or/and function
+   *
+   * USAGE:
+   *   stopObserving(String event)
+   *   stopObserving(Function callback)
+   *   stopObserving(String event, Function callback)
+   *
+   * @return Observer self
+   */
+  stopObserving: function(event, callback) {
+    Observer_stopObserving(this, event, callback, function() {});
+    return this;
+  },
+
+  /**
+   * returns the listeners list for the event
+   *
+   * NOTE: if no event was specified the method will return _all_
+   *       event listeners for _all_ the events
+   *
+   * @param String event name
+   * @return Array of listeners
+   */
+  listeners: function(event) {
+    return (this.$listeners || []).filter(function(i) {
+      return !event || i.e === event;
+    }).map(function(i) { return i.f; }).uniq();
+  },
+
+  /**
+   * initiates the event handling
+   *
+   * @param String event name
+   * @param mixed optional argument
+   * ........
+   * @return Observer self
+   */
+  fire: function() {
+    var args = $A(arguments), event = args.shift();
+
+    (this.$listeners || []).each(function(i) {
+      if (i.e === event) {
+        i.f.apply(this, i.a.concat(args));
+      }
+    }, this);
+
+    return this;
+  }
+}),
+
+/**
+ * adds an observer functionality to any object
+ *
+ * @param Object object
+ * @param Array optional events list to build shortcuts
+ * @return Object extended object
+ */
+Observer_create = Observer.create =  function(object, events) {
+  $ext(object, Object.without(Observer.prototype, 'initialize', 'setOptions'), true);
+  return Observer_createShortcuts(object, events || Class_findSet(object, 'Events'));
+},
+
+/**
+ * builds shortcut methods to wire/fire events on the object
+ *
+ * @param Object object to extend
+ * @param Array list of event names
+ * @return Object extended object
+ */
+Observer_createShortcuts = Observer.createShortcuts = function(object, names) {
+  (names || []).each(function(name) {
+    var method_name = 'on'+name.replace(/(^|_|:)([a-z])/g,
+      function(match, pre, chr) { return chr.toUpperCase(); }
+    );
+
+    if (!(method_name in object)) {
+      object[method_name] = function() {
+        return this.on.apply(this, [name].concat($A(arguments)));
+      };
+    }
+  });
+
+  return object;
+};
+
+function Observer_on(object, o_args, preprocess) {
+  var args     = slice.call(o_args, 2),
+      event    = o_args[0],
+      callback = o_args[1],
+      name     = false;
+
+  if (isString(event)) {
+    switch (typeof callback) {
+      case "string":
+        name     = callback;
+        callback = callback in object ? object[callback] : function() {};
+
+      case "function":
+        ('$listeners' in object ? object.$listeners : (
+          object.$listeners = []
+        )).push(preprocess({
+          e: event, f: callback, a: args, r: name || false, t: object
+        }));
+        break;
+
+      default:
+        if (isArray(callback)) {
+          for (var i=0; i < callback.length; i++) {
+            object.on.apply(object, [event].concat(
+              ensure_array(callback[i])
+            ).concat(args));
+          }
+        }
+    }
+
+  } else {
+    // assuming it's a hash of key-value pairs
+    args = slice.call(o_args, 1);
+
+    for (name in event) {
+      object.on.apply(object, [name].concat(
+        ensure_array(event[name])
+      ).concat(args));
+    }
+  }
+}
+
+function Observer_stopObserving(object, event, callback, preprocess) {
+  if (isHash(event)) {
+    for (var key in event) {
+      object.stopObserving(key, event[key]);
+    }
+  } else {
+    if (!isString(event)) {  callback = event; event = null; }
+    if (isString(callback)){ callback = object[callback]; }
+
+    object.$listeners = (object.$listeners || []).filter(function(i) {
+      var result = (event && callback) ?
+        (i.e !== event || i.f !== callback) :
+        (event ? i.e !== event : i.f !== callback);
+
+      if (!result) { preprocess(i); }
+
+      return result;
+    });
+  }
+}
+
+
+/**
+ * this object will contain info about the current browser
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+var agent = navigator.userAgent,
+    Browser_Opera = 'opera' in window,
+    Browser_IE    = 'attachEvent' in window && !Browser_Opera,
+
+Browser = RightJS.Browser = {
+  IE:           Browser_IE,
+  Opera:        Browser_Opera,
+  WebKit:       agent.include('AppleWebKit/'),
+  Gecko:        agent.include('Gecko') && !agent.include('KHTML'),
+  MobileSafari: /Apple.*Mobile.*Safari/.test(agent),
+  Konqueror:    agent.include('Konqueror'),
+
+  // internal marker for the browsers which require the olds module
+  OLD:          !document.querySelector,
+  // internal marker for IE browsers version <= 8
+  IE8L:         false
+},
+
+IE8_OR_LESS = false,
+IE_OPACITY  = !('opacity' in HTML.style) && ('filter' in HTML.style);
+
+try {
+  // checking if that an IE version <= 8
+  document.createElement('<input/>');
+  Browser.OLD = Browser.IE8L = IE8_OR_LESS = true;
+} catch(e) {}
+
+
+/**
+ * The dom-wrapper main unit
+ *
+ * This unit is basically for the internal use
+ * so that we could control the common functionality
+ * among all the wrappers
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+
+var Wrapper = RightJS.Wrapper = new Class({
+  // predefining the property in the prototype
+  _: undefined,
+
+  /**
+   * Default constructor
+   *
+   * @param mixed raw dom unit
+   * @return void
+   */
+  initialize: function(raw_object) {
+    this._ = raw_object;
+  }
+});
+
+// exposing the cache so it could be manupulated externally
+Wrapper.Cache = Wrappers_Cache;
+
+// instantiating the actual class object for a wrapper
+function Wrapper_makeKlass() {
+  /**
+   * Default wrappers Klass function
+   *
+   * @param mixed the raw object
+   * @param Object options
+   * @return void
+   */
+  return function(object, options) {
+    Class_checkPrebind(this);
+
+    this.initialize.apply(this, arguments); // <- there might be a different number of args in a subclass
+
+    var item = this._, uid = UID_KEY in item ? item[UID_KEY] :
+      // NOTE we use positive indexes for dom-elements and negative for everything else
+      (item[UID_KEY] = (item.nodeType === 1 ? 1 : -1) * UID++);
+
+    Wrappers_Cache[uid] = this;
+  };
+}
+
+/**
+ * Element's own Klass function
+ * we need that because it does some dynamic typecasting mumbo jumbo
+ * plus we would like to optimize some stuff here and there
+ *
+ * @param raw dom element or the tag name
+ * @param Object options
+ * @return Element instance
+ */
+function Element_Klass(element, options) {
+  Element_initialize(this, element, options);
+
+  var inst = this, raw = inst._, cast = Wrapper.Cast(raw),
+      uid = UID_KEY in raw ? raw[UID_KEY] : (raw[UID_KEY] = UID++);
+
+  if (cast !== undefined) {
+    inst = new cast(raw, options);
+    if ('$listeners' in this) {
+      inst.$listeners = this.$listeners;
+    }
+  }
+
+  Wrappers_Cache[uid] = inst;
+
+  return inst;
+}
+
+// searches for a suitable class for dynamic typecasting
+Wrapper.Cast = function(unit) {
+  return unit.tagName in Element_wrappers ? Element_wrappers[unit.tagName] : undefined;
+};
+
+/**
+ * Event's own Klass function, we don't need to check
+ * nothing in here, don't need to hit the wrappers cache and so one
+ *
+ * @param raw dom-event or a string event-name
+ * @param bounding element or an object with options
+ * @return void
+ */
+function Event_Klass(event, bound_element) {
+  if (typeof(event) === 'string') {
+    event = $ext({type: event}, bound_element);
+    this.stopped = event.bubbles === false;
+
+    if (isHash(bound_element)) {
+      $ext(this, bound_element);
+    }
+  }
+
+  this._             = event;
+  this.type          = event.type;
+
+  this.which         = event.which;
+  this.keyCode       = event.keyCode;
+
+  this.target        = wrap(
+    // Webkit throws events on textual nodes as well, gotta fix that
+    event.target != null && 'nodeType' in event.target && event.target.nodeType === 3 ?
+      event.target.parentNode : event.target
+  );
+
+  this.currentTarget = wrap(event.currentTarget);
+  this.relatedTarget = wrap(event.relatedTarget);
+
+  this.pageX         = event.pageX;
+  this.pageY         = event.pageY;
+
+  // making old IE attrs looks like w3c standards
+  if (IE8_OR_LESS && 'srcElement' in event) {
+    this.which         = event.button === 2 ? 3 : event.button === 4 ? 2 : 1;
+
+    this.target        = wrap(event.srcElement) || bound_element;
+    this.relatedTarget = this.target._ === event.fromElement ? wrap(event.toElement) : this.target;
+    this.currentTarget = bound_element;
+
+    var scrolls = this.target.win().scrolls();
+
+    this.pageX = event.clientX + scrolls.x;
+    this.pageY = event.clientY + scrolls.y;
+  }
+}
+
+
+/**
+ * Private quick wrapping function, unlike `$`
+ * it doesn't search by ID and handle double-wrapps
+ * just pure dom-wrapping functionality
+ *
+ * @param raw dom unit
+ * @return Wrapper dom-wrapper
+ */
+function wrap(object) {
+  if (object != null) {
+    var wrapper = UID_KEY in object ? Wrappers_Cache[object[UID_KEY]] : undefined;
+
+    if (wrapper !== undefined) {
+      return wrapper;
+    } else if (object.nodeType === 1) {
+      return new Element(object);
+    } else if (object.nodeType === 9) {
+      return new Document(object);
+    } else if (object.window == object) {
+      return new Window(object);
+    } else if (isElement(object.target) || isElement(object.srcElement)) {
+      return new Event(object);
+    }
+  }
+
+  return object;
+}
+
+/**
+ * A simple document wrapper
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+var Document = RightJS.Document = new Class(Wrapper, {
+  // returns the window reference
+  win: function() {
+    return wrap(this._.defaultView || this._.parentWindow);
+  }
+}),
+
+// a common local wrapped document reference
+current_Document = wrap(document);
+
+
+/**
+ * the window object extensions
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+var Window = RightJS.Window = new Class(Wrapper, {
+  /**
+   * Selfreference to have a common interface with the rest of the wrappers
+   * in case of events handling
+   *
+   * @return Window
+   */
+  win: function() {
+    return this;
+  },
+
+  /**
+   * returns the inner-size of the window
+   *
+   * @return Object x: d+, y: d+
+   */
+  size: function() {
+    var win = this._, html = win.document.documentElement;
+    return win.innerWidth ? {x: win.innerWidth, y: win.innerHeight} :
+      {x: html.clientWidth, y: html.clientHeight};
+  },
+
+  /**
+   * returns the scrolls for the window
+   *
+   * @return Object x: d+, y: d+
+   */
+  scrolls: function() {
+    var win = this._, doc = win.document, body = doc.body, html = doc.documentElement;
+
+    return (win.pageXOffset || win.pageYOffset) ? {x: win.pageXOffset, y: win.pageYOffset} :
+      (body && (body.scrollLeft || body.scrollTop)) ? {x: body.scrollLeft, y: body.scrollTop} :
+      {x: html.scrollLeft, y: html.scrollTop};
+  },
+
+  /**
+   * overloading the native scrollTo method to support hashes and element references
+   *
+   * @param mixed number left position, a hash position, element or a string element id
+   * @param number top position
+   * @param Object fx options
+   * @return window self
+   */
+  scrollTo: function(left, top, fx_options) {
+    var left_pos = left, top_pos = top,
+        element = isNumber(left) ? null : $(left);
+
+    if(element instanceof Element) {
+      left = element.position();
+    }
+
+    if (isHash(left)) {
+      top_pos  = left.y;
+      left_pos = left.x;
+    }
+
+    // checking if a smooth scroll was requested
+    if (isHash(fx_options = fx_options || top) && RightJS.Fx) {
+      new Fx.Scroll(this, fx_options).start({x: left_pos, y: top_pos});
+    } else {
+      this._.scrollTo(left_pos, top_pos);
+    }
+
+    return this;
+  }
+});
+
+
+/**
+ * represents some additional functionality for the Event class
+ *
+ * NOTE: there more additional functionality for the Event class in the rightjs-goods project
+ *
+ * Credits:
+ *   The additional method names are inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+var Event = RightJS.Event = new Class(Wrapper, {
+  // predefining the keys to spped up the assignments
+  type:          null,
+
+  which:         null,
+  keyCode:       null,
+
+  target:        null,
+  currentTarget: null,
+  relatedTarget: null,
+
+  pageX:         null,
+  pageY:         null,
+
+  /**
+   * the class constructor
+   *
+   * @param raw dom-event
+   * @param HTMLElement the bound element
+   * @return void
+   */
+  initialize: Event_Klass, // the actual initialization happens in the Klass function
+
+  /**
+   * Stops the event bubbling process
+   *
+   * @return RightJS.Event this
+   */
+  stopPropagation: function() {
+    if (this._.stopPropagation) {
+      this._.stopPropagation();
+    } else {
+      this._.cancelBubble = true;
+    }
+
+    this.stopped = true;
+    return this;
+  },
+
+  /**
+   * Prevents the default browser action on the event
+   *
+   * @return RightJS.Event this
+   */
+  preventDefault: function() {
+    if (this._.preventDefault) {
+      this._.preventDefault();
+    } else {
+      this._.returnValue = false;
+    }
+
+    return this;
+  },
+
+  /**
+   * Fully stops the event
+   *
+   * @return RightJS.Event this
+   */
+  stop: function() {
+    return this.stopPropagation().preventDefault();
+  },
+
+  /**
+   * Returns the event position
+   *
+   * @return Object {x: ..., y: ...}
+   */
+  position: function() {
+    return {x: this.pageX, y: this.pageY};
+  },
+
+  /**
+   * Returns the event's offset relative to the target element
+   *
+   * @return Object {x: ..., y: ...} or null
+   */
+  offset: function() {
+    if(this.target instanceof Element) {
+      var element_position = this.target.position();
+
+      return {
+        x: this.pageX - element_position.x,
+        y: this.pageY - element_position.y
+      };
+    }
+
+    // triggered outside browser window (at toolbar etc.)
+    return null;
+  },
+
+  /**
+   * Finds the element between the event target
+   * and the boundary element that matches the
+   * css-rule
+   *
+   * @param String css-rule
+   * @return Element element or null
+   */
+  find: function(css_rule) {
+    if (this.target instanceof Wrapper && this.currentTarget instanceof Wrapper) {
+      var target = this.target._,
+          search = this.currentTarget.find(css_rule, true);
+
+      while (target) {
+        if (search.indexOf(target) !== -1) {
+          return wrap(target);
+        }
+        target = target.parentNode;
+      }
+    }
+
+    return undefined;
+  }
+}, Event_Klass),
+
+Event_delegation_shortcuts = [];
+
+
+/**
+ * The DOM Element unit handling
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+
+var Element = RightJS.Element = new Class(Wrapper, {
+  /**
+   * constructor
+   *
+   * NOTE: this constructor will dynamically typecast
+   *       the wrappers depending on the element tag-name
+   *
+   * @param String element tag name or an HTMLElement instance
+   * @param Object options
+   * @return Element element
+   */
+  initialize: function(element, options) {
+    Element_initialize(this, element, options);
+  }
+
+}, Element_Klass),
+
+Element_wrappers = Element.Wrappers = {},
+elements_cache = {},
+
+/**
+ * bulds dom-elements
+ *
+ * @param String element tag name
+ * @param Object options
+ * @return HTMLElement
+ */
+make_element = function (tag, options) {
+  return (tag in elements_cache ? elements_cache[tag] : (
+    elements_cache[tag] = document.createElement(tag)
+  )).cloneNode(false);
+};
+
+//
+// IE 6,7,8 (not 9!) browsers have a bug with checkbox and radio input elements
+// it doesn't place the 'checked' property correctly, plus there are some issues
+// with clonned SELECT objects, so we are replaceing the elements maker in here
+//
+if (IE8_OR_LESS) {
+  make_element = function(tag, options) {
+    if (tag === 'input' && options !== undefined) {
+      tag = '<input name="'+ options.name +
+        '" type='+ options.type +
+        (options.checked ? ' checked' : '') +
+      '/>';
+    }
+
+    return document.createElement(tag);
+  };
+}
+
+/**
+ * Basic element's constructor
+ *
+ * @param Element wrapper instance
+ * @param mixed raw dom element of a string tag name
+ * @param Object options
+ * @return void
+ */
+function Element_initialize(inst, element, options) {
+  if (typeof element === 'string') {
+    inst._ = make_element(element, options);
+
+    if (options !== undefined) {
+      for (var key in options) {
+        switch (key) {
+          case 'id':    inst._.id        = options[key]; break;
+          case 'html':  inst._.innerHTML = options[key]; break;
+          case 'class': inst._.className = options[key]; break;
+          case 'on':    inst.on(options[key]);           break;
+          default:      inst.set(key, options[key]);
+        }
+      }
+    }
+  } else {
+    inst._ = element;
+  }
+}
+
+
+/**
+ * The DOM Element unit structures handling module
+ *
+ * NOTE: all the methods will process and return only the Element nodes
+ *       all the textual nodes will be skipped
+ *
+ * NOTE: if a css-rule was specified then the result of the method
+ *       will be filtered/adjusted depends on the rule
+ *
+ * Credits:
+ *   The naming principle and most of the names are taken from
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *   The insertions system implementation is inspired by
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+
+Element.include({
+  parent: function(css_rule) {
+    var parent = this._.parentNode, parent_type = parent && parent.nodeType;
+
+    return css_rule ? this.parents(css_rule)[0] :
+      (parent_type === 1 || parent_type === 9) ? // <- IE6 sometimes has a fragment node in there
+      wrap(parent) : null;
+  },
+
+  parents: function(css_rule) {
+    return recursively_collect(this, 'parentNode', css_rule);
+  },
+
+  children: function(css_rule) {
+    return this.find(css_rule).filter(function(element) {
+      return element._.parentNode === this._;
+    }, this);
+  },
+
+  siblings: function(css_rule) {
+    return this.prevSiblings(css_rule).reverse().concat(this.nextSiblings(css_rule));
+  },
+
+  nextSiblings: function(css_rule) {
+    return recursively_collect(this, 'nextSibling', css_rule);
+  },
+
+  prevSiblings: function(css_rule) {
+    return recursively_collect(this, 'previousSibling', css_rule);
+  },
+
+  next: function(css_rule) {
+    return !css_rule && this._.nextElementSibling !== undefined ?
+      wrap(this._.nextElementSibling) : this.nextSiblings(css_rule)[0];
+  },
+
+  prev: function(css_rule) {
+    return !css_rule && this._.previousElementSibling !== undefined ?
+      wrap(this._.previousElementSibling) : this.prevSiblings(css_rule)[0];
+  },
+
+  /**
+   * removes the elemnt out of this parent node
+   *
+   * @return Element self
+   */
+  remove: function() {
+    var element = this._, parent = element.parentNode;
+    if (parent) {
+      parent.removeChild(element);
+    }
+    return this;
+  },
+
+  /**
+   * handles the elements insertion functionality
+   *
+   * The content might be one of the following data
+   *
+   *  o) an element instance
+   *  o) a String (all the scripts will be parsed out and executed)
+   *  o) a list of Elements
+   *  o) a hash like {position: content}
+   *
+   * @param mixed data to insert
+   * @param String position to insert  top/bottom/before/after/instead
+   * @return Element self
+   */
+  insert: function(content, position) {
+    var scripts = null, element = this._;
+    position = position === undefined ? 'bottom' : position;
+
+    if (typeof(content) !== 'object') {
+      scripts = content = (''+content);
+    } else if (content instanceof Element) {
+      content = content._;
+    }
+
+    Element_insertions[position](element,
+      content.nodeType === undefined ?
+        Element_createFragment(
+          (position === 'bottom' || position === 'top') ?
+            element : element.parentNode, content
+        ) : content
+    );
+
+    if (scripts !== null) { scripts.evalScripts(); }
+
+    return this;
+  },
+
+  /**
+   * Inserts the element inside the given one at the given position
+   *
+   * @param mixed destination element reference
+   * @param String optional position
+   * @return Element this
+   */
+  insertTo: function(element, position) {
+    $(element).insert(this, position);
+    return this;
+  },
+
+  /**
+   * A shortcut to uppend several units into the element
+   *
+   * @param mixed data
+   * ..................
+   * @return Element this
+   */
+  append: function(first) {
+    return this.insert(isString(first) ? $A(arguments).join('') : arguments);
+  },
+
+  /**
+   * updates the content of the element by the given content
+   *
+   * @param mixed content (a String, an Element or a list of elements)
+   * @return Element self
+   */
+  update: function(content) {
+    if (typeof(content) !== 'object') {
+      content = '' + content;
+
+      try {
+        this._.innerHTML = content;
+      } catch(e) {
+        return this.clean().insert(content);
+      }
+
+      content.evalScripts();
+
+      return this;
+    } else {
+      return this.clean().insert(content);
+    }
+  },
+
+  /**
+   * Works with the Element's innerHTML property
+   * This method works both ways! if a content is provided
+   * then it will be assigned, otherwise will return
+   * the innerHTML property
+   *
+   * @param String html content
+   * @return String html content or Element this
+   */
+  html: function(content) {
+    return content === undefined ? this._.innerHTML : this.update(content);
+  },
+
+  /**
+   * Works with the Element's innerHTML property as a text
+   * when set something, it will appear as is with everything quoted
+   * when get, will return a string without any tags in it
+   *
+   * @param String text content
+   * @return String text content or Element this
+   */
+  text: function(text) {
+    return text === undefined ? (this._.textContent || this._.innerText) :
+      this.update(this.doc()._.createTextNode(text));
+  },
+
+  /**
+   * replaces the current element by the given content
+   *
+   * @param mixed content (a String, an Element or a list of elements)
+   * @return Element self
+   */
+  replace: function(content) {
+    return this.insert(content, 'instead');
+  },
+
+  /**
+   * wraps the element with the given element
+   *
+   * @param Element wrapper
+   * @return Element self
+   */
+  wrap: function(wrapper) {
+    var element = this._, parent = element.parentNode;
+    if (parent) {
+      wrapper = $(wrapper)._;
+      parent.replaceChild(wrapper, element);
+      wrapper.appendChild(element);
+    }
+    return this;
+  },
+
+  /**
+   * removes all the child nodes out of the element
+   *
+   * @return Element self
+   */
+  clean: function() {
+    while (this._.firstChild) {
+      this._.removeChild(this._.firstChild);
+    }
+
+    return this;
+  },
+
+  /**
+   * checks if the element has no child nodes
+   *
+   * @return boolean check result
+   */
+  empty: function() {
+    return this.html().blank();
+  },
+
+  /**
+   * Creates a clean clone of the element without any events attached to it
+   *
+   * @return Element new clone
+   */
+  clone: function() {
+    return new Element(this._.cloneNode(true));
+  },
+
+  /**
+   * Returns an index of the element among the other child elements
+   *
+   * NOTE: doesn't count the textual nodes!
+   *
+   * @return Integer index
+   */
+  index: function() {
+    var node    = this._,
+        sibling = node.parentNode.firstChild,
+        index   = 0;
+
+    while (sibling !== node) {
+      if (sibling.nodeType === 1) { // counting elements only
+        index ++;
+      }
+      sibling = sibling.nextSibling;
+    }
+
+    return index;
+  }
+});
+
+/**
+ * Recursively collects the target element's related nodes
+ *
+ * @param Element context
+ * @param name String pointer attribute name
+ * @param rule String optional css-atom rule
+ * @return Array found elements
+ */
+function recursively_collect(where, attr, css_rule) {
+  var node = where._, result = [], i=0, no_rule = !css_rule;
+
+  while ((node = node[attr])) {
+    if (node.nodeType === 1 && (no_rule || wrap(node).match(css_rule))) {
+      result[i++] = wrap(node);
+    }
+  }
+
+  return result;
+}
+
+// list of insertions handling functions
+// NOTE: each of the methods will be called in the contects of the current element
+var Element_insertions = {
+  bottom: function(target, content) {
+    target.appendChild(content);
+  },
+
+  top: function(target, content) {
+    if (target.firstChild !== null) {
+      target.insertBefore(content, target.firstChild);
+    } else {
+      target.appendChild(content);
+    }
+  },
+
+  after: function(target, content) {
+    var parent = target.parentNode, sibling = target.nextSibling;
+    if (sibling !== null) {
+      parent.insertBefore(content, sibling);
+    } else {
+      parent.appendChild(content);
+    }
+  },
+
+  before: function(target, content) {
+    target.parentNode.insertBefore(content, target);
+  },
+
+  instead: function(target, content) {
+    target.parentNode.replaceChild(content, target);
+  }
+},
+
+// the element insertion wrappers list
+Element_wraps = {
+  TBODY:  ['<TABLE>',            '</TABLE>',                           2],
+  TR:     ['<TABLE><TBODY>',     '</TBODY></TABLE>',                   3],
+  TD:     ['<TABLE><TBODY><TR>', '</TR></TBODY></TABLE>',              4],
+  COL:    ['<TABLE><COLGROUP>',  '</COLGROUP><TBODY></TBODY></TABLE>', 2],
+  LEGEND: ['<FIELDSET>',         '</FIELDSET>',                        2],
+  AREA:   ['<map>',              '</map>',                             2],
+  OPTION: ['<SELECT>',           '</SELECT>',                          2]
+};
+
+$alias(Element_wraps, {
+  OPTGROUP: 'OPTION',
+  THEAD:    'TBODY',
+  TFOOT:    'TBODY',
+  TH:       'TD'
+});
+
+// converts any data into a html fragment unit
+var fragment = document.createDocumentFragment(),
+    tmp_cont = document.createElement('DIV');
+
+function Element_createFragment(context, content) {
+  if (typeof(content) === 'string') {
+    var tag   = context.tagName,
+        tmp   = tmp_cont,
+        wrap  = tag in Element_wraps ? Element_wraps[tag] : ['', '', 1],
+        depth = wrap[2];
+
+    tmp.innerHTML = wrap[0] + '<'+ tag + '>' + content + '</'+ tag + '>' + wrap[1];
+
+    while (depth-- !== 0) {
+      tmp = tmp.firstChild;
+    }
+
+    content = tmp.childNodes;
+
+    while (content.length !== 0) {
+      fragment.appendChild(content[0]);
+    }
+
+  } else {
+    for (var i=0, length = content.length, node; i < length; i++) {
+      node = content[content.length === length ? i : 0];
+      fragment.appendChild(node instanceof Element ? node._ : node);
+    }
+  }
+
+  return fragment;
+}
+
+
+/**
+ * this module contains the element unit styles related methods
+ *
+ * Credits:
+ *   Some of the functionality is inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *     - Dojo      (www.dojotoolkit.org)      Copyright (C) The Dojo Foundation
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Element.include({
+  /**
+   * assigns styles out of the hash to the element
+   *
+   * NOTE: the style keys might be camelized or dasherized, both cases should work
+   *
+   * @param Object styles list or String style name
+   * @param String style value in case of the first param a string style name
+   * @return Element self
+   */
+  setStyle: function(hash, value) {
+    var key, c_key, style = {}, element_style = this._.style;
+
+    if (value !== undefined) { style[hash] = value; hash = style; }
+    else if(isString(hash)) {
+      hash.split(';').each(function(option) {
+        var els = option.split(':').map('trim');
+        if (els[0] && els[1]) {
+          style[els[0]] = els[1];
+        }
+      });
+      hash = style;
+    }
+
+
+    for (key in hash) {
+      c_key = key.indexOf('-') < 0 ? key : key.camelize();
+
+      if (IE_OPACITY && key === 'opacity') {
+        element_style.filter = 'alpha(opacity='+ hash[key] * 100 +')';
+      } else if (key === 'float') {
+        c_key = Browser_IE ? 'styleFloat' : 'cssFloat';
+      }
+
+      element_style[c_key] = hash[key];
+    }
+
+    return this;
+  },
+
+  /**
+   * returns style of the element
+   *
+   * NOTE: will include the CSS level definitions
+   *
+   * @param String style key
+   * @return String style value or null if not set
+   */
+  getStyle: function(key) {
+    return clean_style(this._.style, key) || clean_style(this.computedStyles(), key);
+  },
+
+  /**
+   * returns the hash of computed styles for the element
+   *
+   * @return Object/CSSDefinition computed styles
+   */
+  computedStyles: HTML.currentStyle ? function() {
+    return this._.currentStyle || {};
+  } : HTML.runtimeStyle ? function() {
+    return this._.runtimeStyle || {};
+  } : function() {
+    return this._.ownerDocument.defaultView.getComputedStyle(this._, null);
+  },
+
+  /**
+   * checks if the element has the given class name
+   *
+   * @param String class name
+   * @return boolean check result
+   */
+  hasClass: function(name) {
+    return (' '+this._.className+' ').indexOf(' '+name+' ') != -1;
+  },
+
+  /**
+   * sets the whole class-name string for the element
+   *
+   * @param String class-name
+   * @return Element self
+   */
+  setClass: function(class_name) {
+    this._.className = class_name;
+    return this;
+  },
+
+  /**
+   * Returns the current class-name
+   *
+   * @return String class-name
+   */
+  getClass: function() {
+    return this._.className;
+  },
+
+  /**
+   * adds the given class name to the element
+   *
+   * @param String class name
+   * @return Element self
+   */
+  addClass: function(name) {
+    var testee = ' '+this._.className+' ';
+    if (testee.indexOf(' '+name+' ') == -1) {
+      this._.className += (testee === '  ' ? '' : ' ') + name;
+    }
+    return this;
+  },
+
+  /**
+   * removes the given class name
+   *
+   * @param String class name
+   * @return Element self
+   */
+  removeClass: function(name) {
+    this._.className = (' '+this._.className+' ').replace(' '+name+' ', ' ').trim();
+    return this;
+  },
+
+  /**
+   * toggles the given class name on the element
+   *
+   * @param String class name
+   * @return Element self
+   */
+   toggleClass: function(name) {
+     return this[this.hasClass(name) ? 'removeClass' : 'addClass'](name);
+   },
+
+   /**
+    * adds the given class-name to the element
+    * and removes it from all the element siblings
+    *
+    * @param String class name
+    * @return Element self
+    */
+   radioClass: function(name) {
+     this.siblings().each('removeClass', name);
+     return this.addClass(name);
+   }
+});
+
+/**
+ * cleans up a style value
+ *
+ * @param Object styles hash
+ * @param String style-key
+ * @return String clean style
+ */
+function clean_style(style, key) {
+  key = key.camelize();
+
+  if (key === 'opacity') {
+    return IE_OPACITY ? (
+      (/opacity=(\d+)/i.exec(style.filter || '') ||
+      ['', '100'])[1].toInt() / 100
+    )+'' :style[key].replace(',', '.');
+  }
+
+  if (key === 'float') {
+    key = Browser_IE ? 'styleFloat' : 'cssFloat';
+  }
+
+  var value = style[key];
+
+  // Opera returns named colors with quotes
+  if (Browser_Opera && /color/i.test(key) && value) {
+    value = value.replace(/"/g, '');
+  }
+
+  return value;
+}
+
+
+/**
+ * Common DOM Element unit methods
+ *
+ * Credits:
+ *   Most of the naming system in the module inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Element.include({
+  /**
+   * sets the element attributes
+   *
+   * @param String attr name or Object attributes hash
+   * @param mixed attribute value
+   * @return Element self
+   */
+  set: function(hash, value) {
+    if (typeof(hash) === 'string') { var val = {}; val[hash] = value; hash = val; }
+
+    var key, element = this._;
+
+    for (key in hash) {
+      if (key === 'style') {
+        this.setStyle(hash[key]);
+      } else {
+        // some attributes are not available as properties
+        if (!(key in element)) {
+          element.setAttribute(key, ''+hash[key]);
+        }
+        element[key] = hash[key];
+      }
+    }
+
+    return this;
+  },
+
+  /**
+   * returns the attribute value for the name
+   *
+   * @param String attr name
+   * @return mixed value
+   */
+  get: function(name) {
+    var element = this._, value = element[name] || element.getAttribute(name);
+    return value === '' ? null : value;
+  },
+
+  /**
+   * checks if the element has that attribute
+   *
+   * @param String attr name
+   * @return Boolean check result
+   */
+  has: function(name) {
+    return this.get(name) !== null;
+  },
+
+  /**
+   * erases the given attribute of the element
+   *
+   * @param String attr name
+   * @return Element self
+   */
+  erase: function(name) {
+    this._.removeAttribute(name);
+    return this;
+  },
+
+  /**
+   * checks if the elemnt is hidden
+   *
+   * NOTE: will check css level computed styles too
+   *
+   * @return boolean check result
+   */
+  hidden: function() {
+    return this.getStyle('display') === 'none';
+  },
+
+  /**
+   * checks if the element is visible
+   *
+   * @return boolean check result
+   */
+  visible: function() {
+    return !this.hidden();
+  },
+
+  /**
+   * hides the element
+   *
+   * @param String optional effect name
+   * @param Object the optional effect options
+   * @return Element self
+   */
+  hide: function(effect, options) {
+    if (this.visible()) {
+      this._d = this.getStyle('display');
+      this._.style.display = 'none';
+    }
+
+    return this;
+  },
+
+  /**
+   * shows the element
+   *
+   * @return Element self
+   */
+  show: function() {
+    if (this.hidden()) {
+      var element   = this._, value = this._d, dummy;
+
+      // trying to guess the default 'style.display' for this kind of elements
+      if (!value || value === 'none') {
+        dummy = $E(element.tagName).insertTo(HTML);
+        value = dummy.getStyle('display');
+        dummy.remove();
+      }
+
+      // failsafe in case the user been naughty
+      if (value === 'none') {
+        value = 'block';
+      }
+
+      element.style.display = value;
+    }
+
+    return this;
+  },
+
+  /**
+   * toggles the visibility state of the element
+   *
+   * @return Element self
+   */
+  toggle: function() {
+    return this[this.visible() ? 'hide' : 'show']();
+  },
+
+  /**
+   * shows the element and hides all the sibligns
+   *
+   * @param String optional effect name
+   * @param Object the optional effect options
+   * @return Element self
+   */
+  radio: function(effect, options) {
+    this.siblings().each('hide', effect, options);
+    return this.show();
+  }
+});
+
+
+/**
+ * this module contains the Element's part of functionality
+ * responsible for the dimensions and positions getting/setting
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+Element.include({
+  /**
+   * Returns the reference to this element document
+   *
+   * @return RightJS.Document
+   */
+  doc: function() {
+    return wrap(this._.ownerDocument);
+  },
+
+  /**
+   * Returns the reference to this elements window
+   *
+   * @return RightJS.Window
+   */
+  win: function() {
+    return this.doc().win();
+  },
+
+  /**
+   * Returns the element size as a hash
+   *
+   * @return Object {x: NNN, y: NNN}
+   */
+  size: function() {
+    return { x: this._.offsetWidth, y: this._.offsetHeight };
+  },
+
+  /**
+   * Returns the element absolute position
+   *
+   * NOTE: see the konq.js file for the manual version of the method
+   *
+   * @return Object {x: NNN, y: NNN}
+   */
+  position: function() {
+    var rect    = this._.getBoundingClientRect(),
+        html    = this.doc()._.documentElement,
+        scrolls = this.win().scrolls();
+
+    return {
+      x: rect.left + scrolls.x - html.clientLeft,
+      y: rect.top  + scrolls.y - html.clientTop
+    };
+  },
+
+  /**
+   * Returns the element scrolls
+   *
+   * @return Object {x: NNN, y: NNN}
+   */
+  scrolls: function() {
+    return { x: this._.scrollLeft, y: this._.scrollTop };
+  },
+
+  /**
+   * returns the element dimensions hash
+   *
+   * @return Object dimensions (top, left, width, height, scrollLeft, scrollTop)
+   */
+  dimensions: function() {
+    var size     = this.size(),
+        scrolls  = this.scrolls(),
+        position = this.position();
+
+    return {
+      top:        position.y,
+      left:       position.x,
+      width:      size.x,
+      height:     size.y,
+      scrollLeft: scrolls.x,
+      scrollTop:  scrolls.y
+    };
+  },
+
+  /**
+   * Checks if the element overlaps the given position
+   *
+   * @param Object position {x: NNN, y: NNN}
+   * @return boolean check result
+   */
+  overlaps: function(target) {
+    var pos = this.position(), size = this.size();
+
+    return target.x > pos.x && target.x < (pos.x + size.x) &&
+           target.y > pos.y && target.y < (pos.y + size.y);
+  },
+
+  /**
+   * sets the width of the element in pixels
+   *
+   * NOTE: will double assign the size of the element, so it match the exact
+   *       size including any possible borders and paddings
+   *
+   * @param Integer width in pixels
+   * @return Element self
+   */
+  setWidth: function(width_px) {
+    var style = this._.style;
+    style.width = width_px + 'px';
+    style.width = (2 * width_px - this._.offsetWidth) + 'px';
+    return this;
+  },
+
+  /**
+   * sets the width of the element in pixels
+   *
+   * NOTE: will double assign the size of the element, so it match the exact
+   *       size including any possible borders and paddings
+   *
+   * @param Integer height in pixels
+   * @return Element self
+   */
+  setHeight: function(height_px) {
+    var style = this._.style;
+    style.height = height_px + 'px';
+    style.height = (2 * height_px - this._.offsetHeight) + 'px';
+    return this;
+  },
+
+  /**
+   * sets the size of the element in pixels
+   *
+   * NOTE: will double assign the size of the element, so it match the exact
+   *       size including any possible borders and paddings
+   *
+   * @param width Integer width in pixels or {x: 10, y: 20} like object
+   * @param height Integer height
+   * @return Element self
+   */
+  resize: function(width, height) {
+    if (isHash(width)) {
+      height = width.y;
+      width  = width.x;
+    }
+    return this.setWidth(width).setHeight(height);
+  },
+
+  /**
+   * sets the element position (against the window corner)
+   *
+   * @param left Number left position in pixels or an object like {x: 10, y: 20}
+   * @param top Number top position in pixels
+   * @return Element self
+   */
+  moveTo: function(left, top) {
+    if (isHash(left)) {
+      top  = left.y;
+      left = left.x;
+    }
+
+    return this.setStyle({
+      left: left + 'px',
+      top:  top  + 'px'
+    });
+  },
+
+  /**
+   * sets the scroll position
+   *
+   * @param left Integer left scroll px or an object like {x: 22, y: 33}
+   * @param top Integer top scroll px
+   * @return Element self
+   */
+  scrollTo: function(left, top) {
+    if (isHash(left)) {
+      top  = left.y;
+      left = left.x;
+    }
+
+    this._.scrollLeft = left;
+    this._.scrollTop  = top;
+
+    return this;
+  },
+
+  /**
+   * makes the window be scrolled to the element
+   *
+   * @param Object fx options
+   * @return Element self
+   */
+  scrollThere: function(options) {
+    this.win().scrollTo(this, options);
+    return this;
+  }
+});
+
+
+/**
+ * DOM Element events handling methods
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+[Element, Document, Window].each('include', $ext(Observer_create({}), {
+  /**
+   * The basic events handling attachment method
+   * SEE Observer#on for more details about supported arguments
+   *
+   * @returnt this
+   */
+  on: function() {
+    Observer_on(this, arguments, function(hash) {
+
+      if (hash.e === 'mouseenter' || hash.e === 'mouseleave') {
+        mouse_io_activate();
+        hash.n = hash.e;
+        hash.w = function() {};
+        // NOTE: we don't attach this listener to the actual element!
+        //       so it didn't screw with IE's native enter/leave handlers
+      } else {
+        if (hash.e === 'contextmenu' && Browser.Konqueror) {
+          hash.n = 'rightclick';
+        } else if (hash.e === 'mousewheel' && Browser.Gecko) {
+          hash.n = 'DOMMouseScroll';
+        } else {
+          hash.n = hash.e;
+        }
+
+        hash.w = function(event) {
+          event = new Event(event, hash.t);
+          if (hash.f.apply(hash.t, (hash.r?[]:[event]).concat(hash.a)) === false) {
+            event.stop();
+          }
+        };
+
+        if (IE8_OR_LESS) {
+          hash.t._.attachEvent('on'+hash.n, hash.w);
+        } else {
+          hash.t._.addEventListener(hash.n, hash.w, false);
+        }
+      }
+
+      return hash;
+    });
+
+    return this;
+  },
+
+  /**
+   * Stops an event handling
+   *
+   * @param String event name or a function callback
+   * @param function callback or nothing
+   * @return this
+   */
+  stopObserving: function(event, callback) {
+    Observer_stopObserving(this, event, callback, function(hash) {
+      if (IE8_OR_LESS) {
+        hash.t._.detachEvent('on'+ hash.n, hash.w);
+      } else {
+        hash.t._.removeEventListener(hash.n, hash.w, false);
+      }
+    });
+
+    return this;
+  },
+
+  /**
+   * Artificially trigers the event on the element
+   *
+   * @param string event name or an Event instance
+   * @param Object options
+   * @return this
+   */
+  fire: function(event, options) {
+    var parent = this.parent && this.parent();
+
+    if (!(event instanceof Event)) {
+      event = new Event(event, $ext({target: this._}, options));
+    }
+
+    // setting up the currentTarget reference
+    event.currentTarget = this;
+
+    (this.$listeners || []).each(function(hash) {
+      if (hash.e === event.type &&
+        hash.f.apply(this, (hash.r?[]:[event]).concat(hash.a)) === false
+      ) {
+        event.stop();
+      }
+    }, this);
+
+    // manually bypassing the event to the parent one if it should bubble
+    if (parent && parent.fire && !event.stopped) {
+      parent.fire(event);
+    }
+
+    return this;
+  },
+
+  /**
+   * a simple events terminator method to be hooked like this.onClick('stopEvent');
+   *
+   * @return false
+   */
+  stopEvent: function() { return false; }
+}));
+
+// couple more shortcuts for the window
+Observer_createShortcuts(Window.prototype, $w('blur focus scroll resize load'));
+
+/**
+ * Registers a list of event-binding shortcuts like
+ *  $(element).onClick
+ *  $(element).onMouseover
+ *
+ * @param String space separated event names
+ * @return void
+ */
+function Element_add_event_shortcuts(tokens) {
+  tokens = $w(tokens);
+  Event_delegation_shortcuts = Event_delegation_shortcuts.concat(tokens);
+
+  Observer_createShortcuts(Element.prototype, tokens);
+  Observer_createShortcuts(Document.prototype, tokens);
+}
+
+Element_add_event_shortcuts(
+  'click rightclick contextmenu mousedown mouseup '+
+  'mouseover mouseout mousemove keypress keydown keyup'
+);
+
+
+/**
+ * The DOM elements selection handling
+ *
+ * NOTE: this module is just a wrap over the native CSS-selectors feature
+ *       see the olds/css.js file for the manual selector code
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+
+[Element, Document].each('include', {
+  /**
+   * Extracts the first element matching the css-rule,
+   * or just any first element if no css-rule was specified
+   *
+   * @param String css-rule
+   * @return Element matching node or null
+   */
+  first: function(css_rule) {
+    return wrap(
+      css_rule === undefined && this._.firstElementChild !== undefined ?
+      this._.firstElementChild : this._.querySelector(css_rule || '*')
+    );
+  },
+
+  /**
+   * Finds a list of matching nodes, or all the descendant nodes if no css-rule provided
+   *
+   * @param String css-rule
+   * @param boolean raw-search
+   * @return Array of elements
+   */
+  find: function(css_rule, raw) {
+    var query = this._.querySelectorAll(css_rule || '*'), result, i=0, l = query.length;
+
+    if (raw === true) {
+      result = $A(query);
+    } else {
+      for (result = []; i < l; i++) {
+        result[i] = wrap(query[i]);
+      }
+    }
+
+    return result;
+  },
+
+  /**
+   * checks if the element matches this css-rule
+   *
+   * NOTE: the element should be attached to the page
+   *
+   * @param String css-rule
+   * @return Boolean check result
+   */
+  match: function(css_rule) {
+    // finding the top parent element (the element might not be on the document)
+    var element = this._, parent = element, result, faking = false;
+
+    while (parent.parentNode !== null && parent.parentNode.nodeType !== 11) {
+      parent = parent.parentNode;
+    }
+
+    // creating a fake context when needed
+    if (element === parent) {
+      parent = document.createElement('div');
+      parent.appendChild(element);
+      faking = true;
+    }
+
+    result = wrap(parent).find(css_rule, true).indexOf(element) !== -1;
+
+    if (faking) {
+      parent.removeChild(element);
+    }
+
+    return result;
+  }
+});
+
+
+/**
+ * The dom-ready event handling code
+ *
+ * Credits:
+ *   The basic principles of the module are originated from
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *
+ * Copyright (C) 2009-2011 Nikolay Nemshilov
+ */
+Document.include({
+  on: function(name) {
+    if (name === 'ready' && !this._iR) {
+      var document = this._, ready = this.fire.bind(this, 'ready');
+
+      // IE and Konqueror browsers
+      if ('readyState' in document) {
+        (function() {
+          if (['loaded','complete'].include(document.readyState)) {
+            ready();
+          } else {
+            arguments.callee.delay(50);
+          }
+        })();
+      } else {
+        document.addEventListener('DOMContentLoaded', ready, false);
+      }
+
+      this._iR = true;
+    }
+
+    return this.$super.apply(this, arguments);
+  }
+});
+
+Observer_createShortcuts(Document.prototype, ['ready']);
+
+/**
+ * The form unit class and extensions
+ *
+ * Credits:
+ *   The basic principles of the module are inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *
+ * Copyright (C) 2009-2011 Nikolay Nemshilov
+ */
+
+var Form = RightJS.Form = Element_wrappers.FORM = new Class(Element, {
+  /**
+   * constructor
+   *
+   * NOTE: this constructor can be called as a normal Element constructor
+   *       or with the options only, which will make a FORM element
+   *
+   *   var form = new Form(raw_form_object_element);
+   *   var form = new Form({method: 'post', action: '/boo/hoo'});
+   *
+   * @param Object options or HTMLFormElement object
+   * @return void
+   */
+  initialize: function(in_options) {
+    var options = in_options || {}, remote = 'remote' in options, element = options;
+
+    if (isHash(options) && !isElement(options)) {
+      element = 'form';
+      options = Object.without(options, 'remote');
+    }
+
+    this.$super(element, options);
+
+    if (remote) {
+      this.remotize();
+    }
+  },
+
+  /**
+   * returns the form elements as an array of extended units
+   *
+   * @return Array of elements
+   */
+  elements: function() {
+    return this.find('input,button,select,textarea');
+  },
+
+  /**
+   * returns the list of all the input elements on the form
+   *
+   * @return Array of elements
+   */
+  inputs: function() {
+    return this.elements().filter(function(input) {
+      return !['submit', 'button', 'reset', 'image', null].include(input._.type);
+    });
+  },
+
+  /**
+   * Accessing an input by name
+   *
+   * @param String name
+   * @return Input field
+   */
+  input: function(name) {
+    return wrap(this._[name]);
+  },
+
+  /**
+   * focuses on the first input element on the form
+   *
+   * @return Form this
+   */
+  focus: function() {
+    var element = this.inputs().first(function(input) {
+      return input._.type !== 'hidden';
+    });
+
+    if (element) { element.focus(); }
+
+    return this;
+  },
+
+  /**
+   * removes focus out of all the form elements
+   *
+   * @return Form this
+   */
+  blur: function() {
+    this.elements().each('blur');
+    return this;
+  },
+
+  /**
+   * disables all the elements on the form
+   *
+   * @return Form this
+   */
+  disable: function() {
+    this.elements().each('disable');
+    return this;
+  },
+
+  /**
+   * enables all the elements on the form
+   *
+   * @return Form this
+   */
+  enable: function() {
+    this.elements().each('enable');
+    return this;
+  },
+
+  /**
+   * returns the list of the form values
+   *
+   * @return Object values
+   */
+  values: function() {
+    var values = {}, value, name, element, input;
+
+    this.inputs().each(function(element) {
+      input = element._;
+      name  = input.name;
+      if (!input.disabled && name && (
+        !['checkbox', 'radio'].include(input.type) || input.checked
+      )) {
+        value = element.getValue();
+        if (name.endsWith('[]')) {
+          value = (values[name] || []).concat([value]);
+        }
+
+        values[name] = value;
+      }
+    });
+
+    return values;
+  },
+
+  /**
+   * returns the key/values organized ready to be sent via a get request
+   *
+   * @return String serialized values
+   */
+  serialize: function() {
+    return Object.toQueryString(this.values());
+  },
+
+  /**
+   * Delegating the submit method
+   *
+   * @return Form this
+   */
+  submit: function() {
+    this._.submit();
+    return this;
+  },
+
+  /**
+   * Delegating the 'reset' method
+   *
+   * @return Form this
+   */
+  reset: function() {
+    this._.reset();
+    return this;
+  }
+});
+
+// creating the event shortcuts
+Element_add_event_shortcuts('submit reset focus blur disable enable change');
+
+
+/**
+ * The form input element class
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+var Input = RightJS.Input =
+
+// retgistering the typecasted wrappers
+Element_wrappers.INPUT    =
+Element_wrappers.BUTTON   =
+Element_wrappers.SELECT   =
+Element_wrappers.TEXTAREA =
+Element_wrappers.OPTGROUP =
+
+new Class(Element, {
+  /**
+   * Constructor
+   *
+   * NOTE: this constructor can be called in several ways
+   *
+   *  Like normal Element
+   *   var input = new Input('texarea', {...});
+   *   var input = new Input(document.createElement('select'));
+   *
+   *  Or with options only which will make an INPUT element by default
+   *    var input = new Input({type: 'password', name: 'password'});
+   *
+   * @param HTMLElement or a String tag name or Options for default 'input' tag
+   * @param Object options
+   * @return void
+   */
+  initialize: function(element, options) {
+    // type to tag name conversion
+    if (!element || (isHash(element) && !isElement(element))) {
+      options = element || {};
+
+      if (/textarea|select/.test(options.type || '')) {
+        element = options.type;
+        delete(options.type);
+      } else {
+        element = 'input';
+      }
+    }
+
+    this.$super(element, options);
+  },
+
+  /**
+   * Returns a reference to the input's form
+   *
+   * @return Form wrapped form
+   */
+  form: function() {
+    return wrap(this._.form);
+  },
+
+  /**
+   * Overloading the method to fix some issues with IE and FF
+   *
+   * @param mixed content
+   * @param string optional position
+   * @return Input this
+   */
+  insert: function(content, position) {
+    this.$super(content, position);
+
+    // manually resetting the selected option in here
+    this.find('option').each(function(option) {
+      option._.selected = !!option.get('selected');
+    });
+
+    return this;
+  },
+
+  /**
+   * Overloading the method so it always called the '#insert' method
+   *
+   * @param mixed content
+   * @return Input this
+   */
+  update: function(content) {
+    return this.clean().insert(content);
+  },
+
+  /**
+   * uniform access to the element values
+   *
+   * @return String element value
+   */
+  getValue: function() {
+    if (this._.type == 'select-multiple') {
+      return this.find('option').map(function(option) {
+        return option._.selected ? option._.value : null;
+      }).compact();
+    } else {
+      return this._.value;
+    }
+  },
+
+  /**
+   * uniform accesss to set the element value
+   *
+   * @param String value
+   * @return Element this
+   */
+  setValue: function(value) {
+    if (this._.type == 'select-multiple') {
+      value = ensure_array(value).map(String);
+      this.find('option').each(function(option) {
+        option._.selected = value.include(option._.value);
+      });
+    } else {
+      this._.value = value;
+    }
+    return this;
+  },
+
+  /**
+   * Both ways getter/setter for the value parameter
+   *
+   * @param mixed value
+   * @return mixed this or the value
+   */
+  value: function(value) {
+    return this[value === undefined ? 'getValue' : 'setValue'](value);
+  },
+
+  /**
+   * focuses on the first input element on the form
+   *
+   * @return Form this
+   */
+  focus: function() {
+    this._.focus();
+    this.focused = true;
+    if (Browser_IE) { this.fire('focus', {bubbles: false}); }
+    return this;
+  },
+
+  /**
+   * removes focus out of all the form elements
+   *
+   * @return Form this
+   */
+  blur: function() {
+    this._.blur();
+    this.focused = false;
+    if (Browser_IE) { this.fire('blur', {bubbles: false}); }
+    return this;
+  },
+
+  /**
+   * focuses on the element and selects its content
+   *
+   * @return Element this
+   */
+  select: function() {
+    this._.select();
+    return this.focus();
+  },
+
+  /**
+   * disables all the elements on the form
+   *
+   * @return Form this
+   */
+  disable: function() {
+    this._.disabled = true;
+    return this.fire('disable');
+  },
+
+  /**
+   * enables all the elements on the form
+   *
+   * @return Form this
+   */
+  enable: function() {
+    this._.disabled = false;
+    return this.fire('enable');
+  },
+
+  /**
+   * A bidirectional method to set/get the disabled status of the input field
+   *
+   * @param boolean optional value
+   * @return Input in setter mode boolean in getter
+   */
+  disabled: function(value) {
+    return value === undefined ? this._.disabled : this[value ? 'disable' : 'enable']();
+  },
+
+  /**
+   * A bidirectional method to set/get the checked status of the input field
+   *
+   * @param boolean optional value
+   * @return Input in setter mode boolean in getter
+   */
+  checked: function(value) {
+    if (value === undefined) {
+      value = this._.checked;
+    } else {
+      this._.checked = value;
+      value = this;
+    }
+
+    return value;
+  }
+});
+
+
+/**
+ * This module provides correct focus/blur events bubbling
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+
+/**
+ * Triggers a manual focus/blur events bubbling
+ *
+ * @param raw dom-event
+ * @return void
+ */
+function focus_boobler(raw_event) {
+  var event  = new Event(raw_event),
+      target = event.target,
+      parent = target.parent && target.parent();
+
+  event.type = raw_event.type === 'focusin' || raw_event.type === 'focus' ? 'focus' : 'blur';
+
+  if (parent) { parent.fire(event); }
+}
+
+/**
+ * Hooking up the 'focus' and 'blur' events
+ * at the document level and then rebooble them
+ * manually like they were normal events
+ *
+ */
+if (IE8_OR_LESS) {
+  document.attachEvent('onfocusin',  focus_boobler);
+  document.attachEvent('onfocusout', focus_boobler);
+} else {
+  document.addEventListener('focus', focus_boobler, true);
+  document.addEventListener('blur',  focus_boobler, true);
+}
+
+
+/**
+ * Provides the mouse enter/leave events handling emulation
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+var mouse_io_index = [], mouse_io_inactive = true;
+
+/**
+ * Fires the actual mouseenter/mouseleave event
+ *
+ * @param original event
+ * @param raw dom element
+ * @param integer uid
+ * @param boolean mouseenter or mouseleave
+ * @return void
+ */
+function mouse_io_fire(raw, element, uid, enter) {
+  var event = new Event(raw);
+  event.type    = enter === true ? 'mouseenter' : 'mouseleave';
+  event.bubbles = false;
+  event.stopped = true;
+  event.target  = wrap(element);
+
+  // replacing the #find method so that UJS didn't
+  // get broke with trying to find nested elements
+  event.find = function(css_rule) {
+    return $$(css_rule, true)
+      .indexOf(this.target._) === -1 ?
+        undefined : this.target;
+  };
+
+  event.target.fire(event);
+  current_Document.fire(event);
+}
+
+/**
+ * Figures out the enter/leave events by listening the
+ * mouseovers in the document
+ *
+ * @param raw dom event
+ * @return void
+ */
+function mouse_io_handler(e) {
+  var target  = e.target        || e.srcElement,
+      from    = e.relatedTarget || e.fromElement,
+      element = target,
+      passed  = false,
+      parents = [],
+      uid, event;
+
+  while (element.nodeType === 1) {
+    uid = $uid(element);
+
+    if (mouse_io_index[uid] === undefined) {
+      mouse_io_fire(e, element, uid,
+        mouse_io_index[uid] = true
+      );
+    }
+
+    if (element === from) {
+      passed = true;
+    }
+
+    parents.push(element);
+
+    element = element.parentNode;
+  }
+
+  if (from && !passed) {
+    while (from !== null && from.nodeType === 1 && parents.indexOf(from) === -1) {
+      uid = $uid(from);
+      if (mouse_io_index[uid] !== undefined) {
+        mouse_io_fire(e, from, uid,
+          mouse_io_index[uid] = undefined
+        );
+      }
+
+      from = from.parentNode;
+    }
+  }
+}
+
+/**
+ * Calling 'mouseleave' for all currently active elements on the page
+ *
+ * @return void
+ */
+function mouse_io_reset(e) {
+  mouse_io_index.each(function(value, uid) {
+    if (value && Wrappers_Cache[uid]) {
+      mouse_io_fire(e, Wrappers_Cache[uid]._, uid, false);
+    }
+  });
+}
+
+/**
+ * Activating the mouse-io events emulation
+ *
+ * @return void
+ */
+function mouse_io_activate() {
+  if (mouse_io_inactive) {
+    mouse_io_inactive = false;
+
+    if (Browser_IE) {
+      document.attachEvent('onmouseover', mouse_io_handler);
+      window.attachEvent('blur', mouse_io_reset);
+    } else {
+      document.addEventListener('mouseover', mouse_io_handler, false);
+      window.addEventListener('blur', mouse_io_reset, false);
+    }
+  }
+}
+
+Element_add_event_shortcuts('mouseenter mouseleave');
+
+/**
+ * This module the standard events delegation interface
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+[Element, Document].each('include', {
+  /**
+   * Attaches a delegative event listener to the element/document
+   *
+   * USAGE:
+   *    $(element).delegate('click', '#css.rule', function() {...});
+   *    $(element).delegate('click', '#css.rule', [func1, func2, ...]);
+   *    $(element).delegate('click', '#css.rule', 'addClass', 'boo');
+   *    $(element).delegate('click', '#css.rule', 'hide');
+   *
+   *    $(element).delegate('click', {
+   *      '#css.rule1': function() {},
+   *      '#css.rule2': [func1, func2, ...],
+   *      '#css.rule3': ['addClass', 'boo'],
+   *      '#css.rule4': 'hide'
+   *    });
+   *
+   * @param event name
+   * @param css-rule a hash or rules
+   * @param callback
+   * @return this
+   */
+  delegate: function(event) {
+    var rules = delegation_rules(arguments), css_rule, i, j, list;
+    for (css_rule in rules) {
+      for (i=0, list = rules[css_rule]; i < list.length; i++) {
+        // registering the delegative listener
+        this.on(event, build_delegative_listener(css_rule, list[i], this));
+
+        // adding the css-rule and callback references to the store
+        $ext(this.$listeners.last(), { dr: css_rule, dc: list[i][0] });
+      }
+    }
+
+    return this;
+  },
+
+  /**
+   * Removes a delegative event listener from the element
+   *
+   * USAGE:
+   *    $(element).undelegate('click');
+   *    $(element).undelegate('click', '#css.rule');
+   *    $(element).undelegate('click', '#css.rule', function() {});
+   *    $(element).undelegate('click', '#css.rule', [func1, func2, ...]);
+   *    $(element).undelegate('click', '#css.rule', 'addClass', 'boo');
+   *    $(element).undelegate('click', '#css.rule', 'hide');
+   *
+   *    $(element).undelegate('click', {
+   *      '#css.rule1': function() {},
+   *      '#css.rule2': [func1, func2, ...],
+   *      '#css.rule3': ['addClass', 'boo'],
+   *      '#css.rule4': 'hide'
+   *    });
+   *
+   * @param event name
+   * @param css-rule or a hash or rules
+   * @param callback
+   * @return this
+   */
+  undelegate: function(event) {
+    delegation_listeners(arguments, this).each(function(h) {
+      this.stopObserving(h.n, h.f);
+    }, this);
+
+    return this;
+  },
+
+  /**
+   * Checks if there is sucha delegative event listener
+   *
+   * USAGE:
+   *    $(element).delegates('click');
+   *    $(element).delegates('click', '#css.rule');
+   *    $(element).delegates('click', '#css.rule', function() {});
+   *    $(element).delegates('click', '#css.rule', [func1, func2, ...]);
+   *    $(element).delegates('click', '#css.rule', 'addClass', 'boo');
+   *    $(element).delegates('click', '#css.rule', 'hide');
+   *
+   *    $(element).delegates('click', {
+   *      '#css.rule1': function() {},
+   *      '#css.rule2': [func1, func2, ...],
+   *      '#css.rule3': ['addClass', 'boo'],
+   *      '#css.rule4': 'hide'
+   *    });
+   *
+   * NOTE:
+   *    if several rules are specified then it will check if
+   *    _any_ of them are delegateed
+   *
+   * @param event name
+   * @param css-rule or a hash of rules
+   * @param callback
+   * @return boolean check result
+   */
+  delegates: function() {
+    return !!delegation_listeners(arguments, this).length;
+  }
+});
+
+/**
+ * Builds the actual event listener that will delegate stuff
+ * to other elements as they reach the element where the listener
+ * attached
+ *
+ * @param String css rule
+ * @param Arguments the original arguments list
+ * @param Object scope
+ * @return Function the actual event listener
+ */
+function build_delegative_listener(css_rule, entry, scope) {
+  var args = $A(entry), callback = args.shift();
+  return function(event) {
+    var target = event.find(css_rule);
+    return target === undefined ? target :
+      typeof(callback) === 'string' ?
+        target[callback].apply(target, args) :
+        callback.apply(target, [event].concat(args));
+  };
+}
+
+/**
+ * Converts the events-delegation api arguments
+ * into a systematic hash of rules
+ *
+ * @param Arguments arguments
+ * @return Object hash of rules
+ */
+function delegation_rules(raw_args) {
+  var args = $A(raw_args), rules = args[1] || {}, hash = {}, css_rule;
+
+  if (isString(rules)) {
+    hash[rules] = args.slice(2);
+    if (isArray(hash[rules][0])) {
+      hash[rules] = hash[rules][0].map(ensure_array);
+    }
+  } else {
+    hash = rules;
+  }
+
+  // converting everything into a hash of lists of callbacks
+  for (css_rule in hash) {
+    hash[css_rule] = ensure_array(hash[css_rule]);
+    hash[css_rule] = isArray(hash[css_rule][0]) ? hash[css_rule] : [hash[css_rule]];
+  }
+
+  return hash;
+}
+
+/**
+ * Returns the list of delegative listeners that match the conditions
+ *
+ * @param Arguments raw-arguments
+ * @param Element the element
+ * @return Array list of matching listeners
+ */
+function delegation_listeners(args, object) {
+  var event = args[0], i, list,
+     rules = delegation_rules(args),
+     rules_are_empty = !Object.keys(rules).length;
+
+  return (object.$listeners || []).filter(function(hash) {
+    return hash.dr && hash.n === event && (
+      rules_are_empty || (function() {
+        for (var css_rule in rules) {
+          if (hash.dr === css_rule) {
+            for (i=0, list = rules[css_rule]; i < list.length; i++) {
+              if (!list[i].length || list[i][0] === hash.dc) {
+                return true;
+              }
+            }
+          }
+        }
+
+        return false;
+      })()
+    );
+  });
+}
+
+
+/**
+ * Some String level shortcuts to handle collections of elements
+ *
+ * Copyright (C) 2011 Nikolay Nemshilov
+ */
+
+/**
+ * Some nice shortcuts for the document-level events delegation handling
+ *
+ * USAGE:
+ *
+ *   "ul#main-menu li".on("click", function() { alert('clicked'); });
+ *   "ul#main-menu li".on("mouseover", "addClass", "hovered");
+ *   "ul#main-menu li".on("mouseout", "removeClass", "hovered");
+ *
+ *   // or like that in a shash
+ *   "ul#main-menu li".on({
+ *     click:     function() { alert('clicked'); },
+ *     mouseover: ['addClass',    'hovered'],
+ *     mouseout:  ['removeClass', 'hovered'],
+ *     dblclick:  'hide'
+ *   });
+ *
+ *
+ *   "#css.rule".observes('click');
+ *   "#css.rule".observes('click', function() {});
+ *   "#css.rule".observes('click', 'method_name');
+ *   ....
+ *
+ *   "#css.rule".stopObserving('click');
+ *   "#css.rule".stopObserving('click', function() {});
+ *   "#css.rule".stopObserving('click', 'method_name');
+ *    ....
+ */
+Object.each({
+  on:            'delegate',
+  stopObserving: 'undelegate',
+  observes:      'delegates'
+}, function(name, method) {
+  String.prototype[name] = function() {
+    var args = $A(arguments), result;
+
+    args.splice(1,0,''+this);
+    result = current_Document[method].apply(current_Document, args);
+
+    return result === current_Document ? this : result;
+  };
+});
+var old_on = String.prototype.on;
+String.prototype.on = function(hash) {
+  if (isHash(hash)) {
+    for (var key in hash) {
+      old_on.apply(this, [key].concat([hash[key]]));
+    }
+  } else {
+    old_on.apply(this, arguments);
+  }
+  return this;
+};
+
+/**
+ * building the list of String#onEvent shortucts
+ *
+ * USAGE:
+ *
+ *    "#css.rule".onClick(function() {...});
+ *    "#css.rule".onMouseover('method_name');
+ */
+Event_delegation_shortcuts.each(function(name) {
+  String.prototype['on'+name.capitalize()] = function() {
+    return this.on.apply(this, [name].concat($A(arguments)));
+  };
+});
+
+/**
+ * The rest of the DOM methods access
+ *
+ * USAGE:
+ *   "#css.rule".addClass('boo-hoo');
+ *   "#css.rule".setStyle({color: 'red'});
+ *
+ */
+$w('Element Input Form').each(function(klass) {
+  Object.each(klass in RightJS ? RightJS[klass].prototype : {}, function(name, method) {
+    if (isFunction(method) && !(name in String.prototype)) {
+      String.prototype[name] = function() {
+        var nodes = $$(this, true), i=0, l = nodes.length, first=true, element, result;
+        for (; i < l; i++) {
+          element = wrap(nodes[i]);
+          result  = element[name].apply(element, arguments);
+
+          // checking if that's a data-retrieving call
+          if (first) {
+            if (result !== element) {
+              return result;
+            }
+            first = false;
+          }
+        }
+
+        // don't return the string itself in here,
+        // it will screw with data-retrieving calls on empty collections
+        return null;
+      };
+    }
+  });
+});
+
+/**
+ * XMLHttpRequest wrapper
+ *
+ * Credits:
+ *   Some of the functionality inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *     - jQuery    (http://jquery.com)        Copyright (C) John Resig
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+var Xhr = RightJS.Xhr = new Class(Observer, {
+  extend: {
+    // supported events list
+    EVENTS: $w('success failure complete request cancel create'),
+
+    // default options
+    Options: {
+      headers: {
+        'X-Requested-With': 'XMLHttpRequest',
+        'Accept': 'text/javascript,text/html,application/xml,text/xml,*/*'
+      },
+      method:       'post',
+      encoding:     'utf-8',
+      async:        true,
+      evalScripts:  false,
+      evalResponse: false,
+      evalJS:       true,
+      evalJSON:     true,
+      secureJSON:   true,
+      urlEncoded:   true,
+      spinner:      null,
+      spinnerFx:    'fade',
+      params:       null,
+      iframed:      false,
+      jsonp:        false
+    },
+
+    /**
+     * Shortcut to initiate and send an XHR in a single call
+     *
+     * @param String url
+     * @param Object options
+     * @return Xhr request
+     */
+    load: function(url, options) {
+      return new this(url, $ext({method: 'get'}, options)).send();
+    }
+  },
+
+  /**
+   * basic constructor
+   *
+   * @param String url
+   * @param Object options
+   */
+  initialize: function(url, options) {
+    this.initCallbacks(); // system level callbacks should be initialized before the user callbacks
+
+    this.url = url;
+
+    // copying some options to the instance level attributes
+    $ext(this.$super(options), this.options);
+
+    // merging in the global params
+    if (this.params != Xhr.Options.params) {
+      this.params = this.prepareData(Xhr.Options.params, this.params);
+    }
+
+    // removing the local spinner if it's the same as the global one
+    if (Xhr.Options.spinner && $(this.spinner) === $(Xhr.Options.spinner)) {
+      this.spinner = null;
+    }
+  },
+
+  /**
+   * sets a header
+   *
+   * @param name String header name
+   * @param value String header value
+   * @return Xhr self
+   */
+  setHeader: function(name, value) {
+    this.headers[name] = value;
+    return this;
+  },
+
+  /**
+   * tries to get a response header
+   *
+   * @return mixed String header value or undefined
+   */
+  getHeader: function(name) {
+    var value;
+    try {
+      value = this.xhr.getResponseHeader(name);
+    } catch(e) {}
+    return value;
+  },
+
+  /**
+   * checks if the request was successful
+   *
+   * @return boolean check result
+   */
+  successful: function() {
+    return (this.status >= 200) && (this.status < 300);
+  },
+
+  /**
+   * performs the actual request sending
+   *
+   * @param Object options
+   * @return Xhr self
+   */
+  send: function(params) {
+    var add_params = {},
+        url = this.url,
+        method  = this.method.toLowerCase(),
+        headers = this.headers,
+        key, xhr;
+
+    if (method == 'put' || method == 'delete') {
+      add_params._method = method;
+      method = 'post';
+    }
+
+    var data = this.prepareData(this.params, this.prepareParams(params), add_params);
+
+    if (this.urlEncoded && method == 'post' && !headers['Content-type']) {
+      this.setHeader('Content-type', 'application/x-www-form-urlencoded;charset='+this.encoding);
+    }
+
+    if (method == 'get') {
+      if (data) { url += (url.include('?') ? '&' : '?') + data; }
+      data = null;
+    }
+
+    xhr = this.xhr = this.createXhr();
+    this.fire('create');
+
+    xhr.open(method, url, this.async);
+
+    xhr.onreadystatechange = this.stateChanged.bind(this);
+
+    for (key in headers) {
+      xhr.setRequestHeader(key, headers[key]);
+    }
+
+    xhr.send(data);
+    this.fire('request');
+
+    if (!this.async) { this.stateChanged(); }
+
+    return this;
+  },
+
+  /**
+   * elements automaticall update method, creates an Xhr request
+   * and updates the element innerHTML value onSuccess.
+   *
+   * @param Element element
+   * @param Object optional request params
+   * @return Xhr self
+   */
+  update: function(element, params) {
+    return this.onSuccess(function(r) { element.update(r.text); }).send(params);
+  },
+
+  /**
+   * stops the request processing
+   *
+   * @return Xhr self
+   */
+  cancel: function() {
+    var xhr = this.xhr;
+
+    if (!xhr || xhr.canceled) { return this; }
+
+    xhr.abort();
+    xhr.onreadystatechange = function() {};
+    xhr.canceled = true;
+
+    return this.fire('cancel');
+  },
+
+// protected
+  // wrapping the original method to send references to the xhr objects
+  fire: function(name) {
+    return this.$super(name, this, this.xhr);
+  },
+
+  // creates new request instance
+  createXhr: function() {
+    if (this.jsonp) {
+      return new Xhr.JSONP(this);
+    } else if (this.form && this.form.first('input[type=file]')) {
+      return new Xhr.IFramed(this.form);
+    } else if ('ActiveXObject' in window){
+      return new ActiveXObject('MSXML2.XMLHTTP');
+    } else {
+      return new XMLHttpRequest();
+    }
+  },
+
+  // prepares user sending params
+  prepareParams: function(params) {
+    if (params && params instanceof Form) {
+      this.form = params;
+      params = params.values();
+    }
+    return params;
+  },
+
+  // converts all the params into a url params string
+  prepareData: function() {
+    return $A(arguments).map(function(param) {
+      if (!isString(param)) {
+        param = Object.toQueryString(param);
+      }
+      return param.blank() ? null : param;
+    }).compact().join('&');
+  },
+
+  // handles the state change
+  stateChanged: function() {
+    var xhr = this.xhr;
+
+    if (xhr.readyState != 4 || xhr.canceled) { return; }
+
+    try { this.status = xhr.status;
+    } catch(e) { this.status = 0; }
+
+    this.text = this.responseText = xhr.responseText;
+    this.xml  = this.responseXML  = xhr.responseXML;
+
+    this.fire('complete').fire(this.successful() ? 'success' : 'failure');
+  },
+
+  // called on success
+  tryScripts: function(response) {
+    var content_type = this.getHeader('Content-type');
+
+    if (this.evalResponse || (this.evalJS && /(ecma|java)script/i.test(content_type))) {
+      $eval(this.text);
+    } else if (/json/.test(content_type) && this.evalJSON) {
+      this.json = this.responseJSON = this.sanitizedJSON();
+    } else if (this.evalScripts) {
+      this.text.evalScripts();
+    }
+  },
+
+  // sanitizes the json-response texts
+  sanitizedJSON: function() {
+    if (!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(
+      this.text.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '')
+    )) {
+      if (this.secureJSON) {
+        throw "JSON error: "+this.text;
+      }
+      return null;
+    }
+
+    return 'JSON' in window ? JSON.parse(this.text) :
+      (new Function("return "+this.text))();
+  },
+
+  // initializes the request callbacks
+  initCallbacks: function() {
+    // connecting basic callbacks
+    this.on({
+      success:  'tryScripts',
+      create:   'showSpinner',
+      complete: 'hideSpinner',
+      cancel:   'hideSpinner'
+    });
+
+    // wiring the global xhr callbacks
+    Xhr.EVENTS.each(function(name) {
+      this.on(name, function() { Xhr.fire(name, this, this.xhr); });
+    }, this);
+  },
+
+  showSpinner: function() { Xhr.showSpinner.call(this, this); },
+  hideSpinner: function() { Xhr.hideSpinner.call(this, this); }
+});
+
+// attaching the common spinner handling
+$ext(Observer_create(Xhr), {
+  counter: 0,
+
+  // shows the spinner
+  showSpinner: function(context) {
+    Xhr.trySpinner(context, 'show');
+  },
+
+  // hides the spinner
+  hideSpinner: function(context) {
+    Xhr.trySpinner(context, 'hide');
+  },
+
+  trySpinner: function(context, method) {
+    var object = context || Xhr.Options, spinner = $(object.spinner);
+    if (spinner) { spinner[method](object.spinnerFx, {duration: 100}); }
+  },
+
+  // counts a request in
+  countIn: function() {
+    Xhr.counter ++;
+    Xhr.showSpinner();
+  },
+
+  // counts a request out
+  countOut: function() {
+    Xhr.counter --;
+    if (Xhr.counter < 1) {
+      Xhr.hideSpinner();
+    }
+  }
+}).on({
+  create:   'countIn',
+  complete: 'countOut',
+  cancel:   'countOut'
+});
+
+
+/**
+ * Here are the Form unit Xhr extensions
+ *
+ * Credits:
+ *   Some of the functionality inspired by
+ *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
+ *     - jQuery    (http://jquery.com)        Copyright (C) John Resig
+ *
+ * Copyright (C) 2009-2011 Nikolay V. Nemshilov
+ */
+Form.include({
+  /**
+   * sends the form via xhr request
+   *
+   * @param Options xhr request options
+   * @return Form this
+   */
+  send: function(options) {
+    options = options || {};
+    options.method = options.method || this._.method || 'post';
+
+    this.xhr = new Xhr(
+      this._.action || document.location.href,
+      $ext({spinner: this.first('.spinner')}, options)
+    )
+    .onComplete(this.enable.bind(this))
+    .onCancel(this.enable.bind(this))
+    .send(this);
+
+    this.disable.bind(this).delay(1); // webkit needs this async call with iframed calls
+    return this;
+  },
+
+  /**
+   * Cancels current Xhr request (if there are any)
+   *
+   * @return Form this
+   */
+  cancelXhr: function() {
+    if (this.xhr instanceof Xhr) {
+      this.xhr.cancel();
+    }
+
+    return this;
+  },
+
+  /**
+   * makes the form be remote by default
+   *
+   * @param Object default options
+   * @return Form this
+   */
+  remotize: function(options) {
+    if (!this.remote) {
+      this.on('submit', Form_remote_send, options);
+      this.remote = true;
+    }
+
+    return this;
+  },
+
+  /**
+   * removes the remote call hook
+   *
+   * @return Form this
+   */
+  unremotize: function() {
+    this.stopObserving('submit', Form_remote_send);
+    this.remote = false;
+    return this;
+  }
+});
+
+/**
+ * Catches the form submit events and sends the form remotely
+ *
+ * @param Event submit
+ * @param Object xhr options
+ * @return void
+ */
+function Form_remote_send(event, options) {
+  event.stop();
+  this.send(options);
+}
+
+
+/**
+ * this module contains the Element unit XHR related extensions
+ *
+ * Credits:
+ *   - jQuery    (http://jquery.com)        Copyright (C) John Resig
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Element.include({
+  /**
+   * performs an Xhr request to the given url
+   * and updates the element internals with the responseText
+   *
+   * @param String url address
+   * @param Object xhr options
+   * @return Element this
+   */
+  load: function(url, options) {
+    new Xhr(url, $ext({method: 'get'}, options)).update(this);
+    return this;
+  }
+});
+
+
+/**
+ * A dummy XmlHTTPRequest interface to be used in other
+ * fake requests
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+Xhr.Dummy = {
+  open:               function() {},
+  setRequestHeader:   function() {},
+  onreadystatechange: function() {}
+};
+
+
+/**
+ * This unit presents a fake drop in replacement for the XmlHTTPRequest unit
+ * but works with an iframe targeting in the background
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+Xhr.IFramed = new Class({
+  include: Xhr.Dummy,
+
+  /**
+   * constructor
+   *
+   * @param Form form which will be submitted via the frame
+   * @return void
+   */
+  initialize: function(form) {
+    this.form = form;
+    this.id   = 'xhr_'+ new Date().getTime();
+
+    this.form.doc().first('body').append('<i><iframe name="'+this.id+'" id="'+this.id+
+      '" width="0" height="0" frameborder="0" src="about:blank"></iframe></i>',
+      'after');
+
+    $(this.id).on('load', this.onLoad.bind(this));
+  },
+
+  send: function() {
+    this.form.set('target', this.id).submit();
+  },
+
+  onLoad: function() {
+    this.status       = 200;
+    this.readyState   = 4;
+
+    this.form.set('target', '');
+
+    try {
+      this.responseText = window[this.id].document.documentElement.innerHTML;
+    } catch(e) { }
+
+    this.onreadystatechange();
+  },
+
+  abort: function() {
+    $(this.id).set('src', 'about:blank');
+  }
+});
+
+
+/**
+ * The JSONP Xhr request tonnel
+ *
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
+ */
+Xhr.JSONP = new Class({
+  include: Xhr.Dummy,
+
+  prefix: 'jsonp',
+
+  /**
+   * Constructor
+   *
+   * @param Xhr the actual xhr request object
+   * @return void
+   */
+  initialize: function(xhr) {
+    this.xhr   = xhr;
+    this.name  = this.prefix + new Date().getTime();
+    this.param = (isString(xhr.jsonp) ?
+      xhr.jsonp : 'callback') + "=" + this.name;
+
+    this.script = $E('script', {
+      charset: xhr.encoding,
+      async:   xhr.async
+    });
+  },
+
+  /**
+   * saving the url and method for the further use
+   *
+   * @param method String request method
+   * @param address String request url address
+   * @param Boolean async request marker
+   * @return void
+   */
+  open: function(method, url, async) {
+    this.url    = url;
+    this.method = method;
+  },
+
+  /**
+   * Sends the actual request by inserting the script into the document body
+   *
+   * @param String data
+   * @return void
+   */
+  send: function(data) {
+    window[this.name] = this.finish.bind(this);
+
+    this.script.set('src', this.url + (this.url.include('?') ? '&' : '?') + this.param + "&" + data)
+      .insertTo($$('script').last(), 'after');
+  },
+
+  /**
+   * Receives the actual JSON data from the server
+   *
+   * @param Object JSON data
+   * @return void
+   */
+  finish: function(data) {
+    this.status       = 200;
+    this.readyState   = 4;
+
+    this.xhr.json = this.xhr.responseJSON = data;
+
+    this.onreadystatechange();
+  },
+
+  /**
+   * We can't really cancel a JSONP request
+   * but we can prevent the default handler to ckick in
+   *
+   * @return void
+   */
+  abort: function() {
+    window[this.name] = function() {};
+  }
+});
+
+
+/**
+ * Basic visual effects class
+ *
+ * Credits:
+ *   The basic principles, structures and naming system are inspired by
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+var Fx = RightJS.Fx = new Class(Observer, {
+  extend: {
+    EVENTS: $w('start finish cancel'),
+
+    // named durations
+    Durations: {
+      'short':  200,
+      'normal': 400,
+      'long':   800
+    },
+
+    // default options
+    Options: {
+      fps:        IE8_OR_LESS ? 40 : 60,
+      duration:   'normal',
+      transition: 'Sin',
+      queue:      true
+    },
+
+    // list of basic transitions
+    Transitions: {
+      Sin: function(i)  {
+        return -(Math.cos(Math.PI * i) - 1) / 2;
+      },
+
+      Cos: function(i) {
+        return Math.asin((i-0.5) * 2)/Math.PI + 0.5;
+      },
+
+      Exp: function(i) {
+        return Math.pow(2, 8 * (i - 1));
+      },
+
+      Log: function(i) {
+        return 1 - Math.pow(2, - 8 * i);
+      },
+
+      Lin: function(i) {
+        return i;
+      }
+    }
+  },
+
+  /**
+   * Basic constructor
+   *
+   * @param Object options
+   */
+  initialize: function(element, options) {
+    this.$super(options);
+    this.element = $(element);
+    fx_register(this);
+  },
+
+  /**
+   * starts the transition
+   *
+   * @return Fx this
+   */
+  start: function() {
+    if (fx_add_to_queue(this, arguments)) { return this; }
+
+    var options    = this.options,
+        duration   = Fx.Durations[options.duration] || options.duration,
+        transition = Fx.Transitions[options.transition] || options.transition,
+        steps      = (duration / 1000 * this.options.fps).ceil(),
+        interval   = (1000 / this.options.fps).round();
+
+    fx_mark_current(this);
+
+    this.prepare.apply(this, arguments);
+
+    fx_start_timer(this, transition, interval, steps);
+
+    return this.fire('start', this);
+  },
+
+  /**
+   * finishes the transition
+   *
+   * @return Fx this
+   */
+  finish: function() {
+    fx_stop_timer(this);
+    fx_remove_from_queue(this);
+    this.fire('finish');
+    fx_run_next(this);
+    return this;
+  },
+
+  /**
+   * interrupts the transition
+   *
+   * NOTE:
+   *   this method cancels all the scheduled effects
+   *   in the element chain
+   *
+   * @return Fx this
+   */
+  cancel: function() {
+    fx_stop_timer(this);
+    fx_remove_from_queue(this);
+    return this.fire('cancel');
+  },
+
+// protected
+  // dummy method, should be implemented in a subclass
+  prepare: function() {},
+
+  // dummy method, processes the element properties
+  render: function() {}
+}),
+
+// global effects registry
+scheduled_fx = [], running_fx = [];
+
+/**
+ * Registers the element in the effects queue
+ *
+ * @param Fx effect
+ * @return void
+ */
+function fx_register(fx) {
+  var uid = $uid((fx.element || {})._ || {});
+  fx.ch = (scheduled_fx[uid] = scheduled_fx[uid] || []);
+  fx.cr = (running_fx[uid]   = running_fx[uid]   || []);
+}
+
+/**
+ * Registers the effect in the effects queue
+ *
+ * @param Fx fx
+ * @param Arguments original arguments list
+ * @return boolean true if it queued and false if it's ready to go
+ */
+function fx_add_to_queue(fx, args) {
+  var chain = fx.ch, queue = fx.options.queue;
+
+  if (!chain || fx.$ch) {
+    return (fx.$ch = false);
+  }
+
+  if (queue) {
+    chain.push([args, fx]);
+  }
+
+  return queue && chain[0][1] !== fx;
+}
+
+/**
+ * Puts the fx into the list of currently running effects
+ *
+ * @param Fx fx
+ * @return void
+ */
+function fx_mark_current(fx) {
+  if (fx.cr) {
+    fx.cr.push(fx);
+  }
+}
+
+/**
+ * Removes the fx from the queue
+ *
+ * @param Fx fx
+ * @return void
+ */
+function fx_remove_from_queue(fx) {
+  var currents = fx.cr;
+  if (currents) {
+    currents.splice(currents.indexOf(fx), 1);
+  }
+}
+
+/**
+ * Tries to invoke the next effect in the queue
+ *
+ * @param Fx fx
+ * @return void
+ */
+function fx_run_next(fx) {
+  var chain = fx.ch, next = chain.shift();
+  if ((next = chain[0])) {
+    next[1].$ch = true;
+    next[1].start.apply(next[1], next[0]);
+  }
+}
+
+/**
+ * Cancels all currently running and scheduled effects
+ * on the element
+ *
+ * @param Element element
+ * @return void
+ */
+function fx_cancel_all(element) {
+  var uid = $uid(element._);
+
+  (running_fx[uid] || []).each('cancel');
+  (scheduled_fx[uid] || []).splice(0);
+}
+
+/**
+ * Initializes the fx rendering timer
+ *
+ * @param Fx fx
+ * @param Function transition stops calculator
+ * @param Float interval
+ * @param Integer number of steps
+ * @return void
+ */
+function fx_start_timer(fx, transition, interval, steps) {
+  var number = 1;
+  fx._timer = setInterval(function() {
+    if (number > steps) {
+      fx.finish();
+    } else {
+      fx.render(transition(number/steps));
+      number ++;
+    }
+  }, interval);
+}
+
+/**
+ * Cancels the Fx rendering timer (if any)
+ *
+ * @param Fx fx
+ * @return void
+ */
+function fx_stop_timer(fx) {
+  if (fx._timer) {
+    clearInterval(fx._timer);
+  }
+}
+
+
+/**
+ * There are the String unit extensions for the effects library
+ *
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov
+ */
+String.COLORS = {
+  maroon:  '#800000',
+  red:     '#ff0000',
+  orange:  '#ffA500',
+  yellow:  '#ffff00',
+  olive:   '#808000',
+  purple:  '#800080',
+  fuchsia: '#ff00ff',
+  white:   '#ffffff',
+  lime:    '#00ff00',
+  green:   '#008000',
+  navy:    '#000080',
+  blue:    '#0000ff',
+  aqua:    '#00ffff',
+  teal:    '#008080',
+  black:   '#000000',
+  silver:  '#c0c0c0',
+  gray:    '#808080',
+  brown:   '#a52a2a'
+};
+
+String.include({
+  /**
+   * converts a #XXX or rgb(X, X, X) sring into standard #XXXXXX color string
+   *
+   * @return String hex color
+   */
+  toHex: function() {
+    var match = /^#(\w)(\w)(\w)$/.exec(this);
+
+    if (match) {
+      match = "#"+ match[1]+match[1]+match[2]+match[2]+match[3]+match[3];
+    } else if ((match = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(this))) {
+      match = "#"+ match.slice(1).map(function(bit) {
+        bit = (bit-0).toString(16);
+        return bit.length == 1 ? '0'+bit : bit;
+      }).join('');
+    } else {
+      match = String.COLORS[this] || this;
+    }
+
+    return match;
+  },
+
+  /**
+   * converts a hex string into an rgb array
+   *
+   * @param boolean flag if need an array
+   * @return String rgb(R,G,B) or Array [R,G,B]
+   */
+  toRgb: function(array) {
+    var match = /#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})/i.exec(this.toHex()||'');
+
+    if (match) {
+      match = match.slice(1).map('toInt', 16);
+      match = array ? match : 'rgb('+match+')';
+    }
+
+    return match;
+  }
+});
+
+
+/**
+ * This block contains additional Element shortcuts for effects easy handling
+ *
+ * Credits:
+ *   Some ideas are inspired by
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Element.include({
+  /**
+   * Stops all the visual effects on the element
+   *
+   * @return Element this
+   */
+  stop: function() {
+    fx_cancel_all(this);
+    return this;
+  },
+
+  /**
+   * hides the element with given visual effect
+   *
+   * @param String fx name
+   * @param Object fx options
+   * @return Element this
+   */
+  hide: function(fx, options) {
+    return (fx && this.visible()) ? call_fx(this, fx, ['out', options]) : this.$super();
+  },
+
+  /**
+   * shows the element with the given visual effect
+   *
+   * @param String fx name
+   * @param Object fx options
+   * @return Element this
+   */
+  show: function(fx, options) {
+    return (fx && !this.visible()) ? call_fx(this, fx, ['in', options]) : this.$super();
+  },
+
+  /**
+   * Toggles the element state with visual effect
+   *
+   * @param String fx name
+   * @param Object fx options
+   * @return Element this
+   */
+  toggle: function(fx, options) {
+    return fx ? call_fx(this, fx, ['toggle', options]) : this.$super();
+  },
+
+  /**
+   * Removes the element out of the DOM structure
+   *
+   * @param String fx name
+   * @param Object fx options
+   * @return Element this
+   */
+  remove: function(fx, options) {
+    return (fx && this.visible()) ? call_fx(this, fx, ['out', $ext(options || {}, {
+      onFinish: this.$super.bind(this)
+    })]) : this.$super();
+  },
+
+  /**
+   * runs the Fx.Morth effect to the given style
+   *
+   * @param style Object style
+   * @param options Object optional effect options
+   * @return Element self
+   */
+  morph: function(style, options) {
+    return call_fx(this, 'morph', [style, options || {}]); // <- don't replace with arguments
+  },
+
+  /**
+   * highlights the element
+   *
+   * @param start String start color
+   * @param end String optional end color
+   * @param Object effect options
+   * @return Element self
+   */
+  highlight: function() {
+    return call_fx(this, 'highlight', arguments);
+  },
+
+  /**
+   * runs the Fx.Fade effect on the element
+   *
+   * @param mixed fade direction 'in' 'out' or a float number
+   * @return Element self
+   */
+  fade: function() {
+    return call_fx(this, 'fade', arguments);
+  },
+
+  /**
+   * runs the Fx.Slide effect on the element
+   *
+   * @param String 'in' or 'out'
+   * @param Object effect options
+   * @return Element self
+   */
+  slide: function() {
+    return call_fx(this, 'slide', arguments);
+  },
+
+  /**
+   * Starts the smooth scrolling effect
+   *
+   * @param position Object {x: NNN, y: NNN} where to scroll
+   * @param options Object fx-options
+   * @return Element this
+   */
+  scroll: function(value, options) {
+    return call_fx(this, 'scroll', [value, options||{}]);
+  },
+
+  /**
+   * wraps the old scroll to be able to run it with fxes
+   *
+   * If you send two hashes then will start a smooth scrolling
+   * otherwise will just jump over with the usual method
+   *
+   * @return Element this
+   */
+  scrollTo: function(value, options) {
+    return isHash(options) ? this.scroll(value, options) : this.$super.apply(this, arguments);
+  }
+});
+
+/**
+ * Calls the visual effect on the element
+ *
+ * @param Element context
+ * @param String fx-name
+ * @param Object fx-options
+ * @return Element context
+ */
+function call_fx(element, name, params) {
+  var args    = $A(params).compact(),
+      options = isHash(args.last()) ? args.pop() : {},
+      fx      = new Fx[name.capitalize()](element, options);
+
+  fx.start.apply(fx, args);
+
+  return element;
+}
+
+
+/**
+ * This class provides the basic effect for styles manipulation
+ *
+ * Copyright (C) 2008-2011 Nikolay Nemshilov
+ */
+
+/////////////////////////////////////////////////////////////////////////////
+// Native css-transitions based implementation
+/////////////////////////////////////////////////////////////////////////////
+
+var native_fx_prefix = ['WebkitT', 'OT', 'MozT', 'MsT', 't'].first(function(name) {
+  return name + 'ransition' in HTML.style;
+}),
+native_fx_transition = native_fx_prefix     + 'ransition',
+native_fx_property   = native_fx_transition + 'Property',
+native_fx_duration   = native_fx_transition + 'Duration',
+native_fx_function   = native_fx_transition + 'TimingFunction',
+
+// basic transition algorithm replacements
+native_fx_functions  = {
+  Sin: 'cubic-bezier(.3,0,.6,1)',
+  Cos: 'cubic-bezier(0,.3,.6,0)',
+  Log: 'cubic-bezier(0.6,.3,.8)',
+  Exp: 'cubic-bezier(.6,0,.8,.3)',
+  Lin: 'cubic-bezier(0,0,1,1)'
+};
+
+function native_fx_prepare(style) {
+  var options = this.options,
+      element = this.element,
+      element_style = element._.style,
+      old_style = Object.only(
+        element.computedStyles(),
+        native_fx_property,
+        native_fx_duration,
+        native_fx_function
+      );
+
+  function reset_transitions_style() {
+    for (var key in old_style) {
+      element_style[key] = old_style[key];
+    }
+  }
+
+  this
+    .onFinish(reset_transitions_style)
+    .onCancel(function() {
+      element_style[native_fx_property] = 'none';
+      setTimeout(reset_transitions_style, 1);
+    });
+
+  // setting up the transition
+  element_style[native_fx_property] = 'all';
+  element_style[native_fx_duration] = (Fx.Durations[options.duration] || options.duration) +'ms';
+  element_style[native_fx_function] = native_fx_functions[options.transition] || options.transition;
+
+  setTimeout(function() { element.setStyle(style); }, 0);
+}
+
+// NOTE: OPERA's css-transitions are a bit jerky so we disable them by default
+Fx.Options.engine = native_fx_prefix === undefined || Browser_Opera ? 'javascript' : 'native';
+
+////////////////////////////////////////////////////////////////////////////
+// Manual version
+////////////////////////////////////////////////////////////////////////////
+
+Fx.Morph = new Class(Fx, {
+// protected
+
+  // parepares the effect
+  prepare: function(style) {
+    if (this.options.engine === 'native' && native_fx_prefix !== undefined) {
+      this.render = this.transition = function() {};
+      native_fx_prepare.call(this, style);
+    } else {
+      var keys   = style_keys(style),
+          before = clone_style(this.element, keys),
+          after  = end_style(this.element, style, keys);
+
+      clean_styles(this.element, before, after);
+
+      this.before = parse_style(before);
+      this.after  = parse_style(after);
+    }
+  },
+
+  render: function(delta) {
+    var before, after, value, style = this.element._.style, key, i, l;
+    for (key in this.after) {
+      before = this.before[key];
+      after  = this.after[key];
+
+      for (i=0, l = after.length; i < l; i++) {
+        value = before[i] + (after[i] - before[i]) * delta;
+        if (after.r) {
+          value = Math.round(value);
+        }
+        after.t[i*2 + 1] = value;
+      }
+
+      style[key] = after.t.join('');
+    }
+  }
+});
+
+// a list of common style names to compact the code a bit
+var directions = $w('Top Left Right Bottom');
+
+// adds variants to the style names list
+function add_variants(keys, key, variants) {
+  for (var i=0; i < variants.length; i++) {
+    keys.push(key + variants[i]);
+  }
+}
+
+// creates an appropriate style-keys list out of the user styles
+function style_keys(style) {
+  var keys = [], border_types = ['Style', 'Color', 'Width'], key, i, j;
+
+  for (key in style) {
+    if (key.startsWith('border')) {
+      for (i=0; i < 3; i++) {
+        for (j=0; j < 4; j++) {
+          keys.push('border' + directions[j] + border_types[i]);
+        }
+      }
+    } else if (key === 'margin' || key === 'padding') {
+      add_variants(keys, key, directions);
+    } else if (key.startsWith('background')) {
+      add_variants(keys, 'background', ['Color', 'Position', 'PositionX', 'PositionY']);
+    } else if (key === 'opacity' && IE_OPACITY) {
+      keys.push('filter');
+    } else {
+      keys.push(key);
+    }
+  }
+
+  return keys;
+}
+
+
+// checks if the color is transparent
+function is_transparent(color) {
+  return color === 'transparent' || color === 'rgba(0, 0, 0, 0)';
+}
+
+// adjusts the border-styles
+function check_border_styles(element, before, after) {
+  for (var i=0; i < 4; i++) {
+    var
+      bd_style = 'border' + directions[i] + 'Style',
+      bd_width = 'border' + directions[i] + 'Width',
+      bd_color = 'border' + directions[i] + 'Color';
+
+    if (bd_style in before && before[bd_style] != after[bd_style]) {
+      var style = element._.style;
+
+      if (before[bd_style] == 'none') {
+        style[bd_width] = '0px';
+      }
+
+      style[bd_style] = after[bd_style];
+      if (is_transparent(before[bd_color])) {
+        style[bd_color] = element.getStyle('Color');
+      }
+    }
+  }
+}
+
+// parses the style hash into a processable format
+function parse_style(values) {
+  var result = {}, re = /[\d\.\-]+/g, m, key, value, i;
+
+  for (key in values) {
+    m = values[key].match(re);
+    value = m.map('toFloat');
+    value.t = values[key].split(re);
+    value.r = value.t[0] === 'rgb(';
+
+    if (value.t.length == 1) { value.t.unshift(''); }
+
+    for (i=0; i < value.length; i++) {
+      value.t.splice(i*2 + 1, 0, value[i]);
+    }
+    result[key] = value;
+  }
+
+  return result;
+}
+
+// cleans up and optimizies the styles
+function clean_styles(element, before, after) {
+  var key;
+
+  for (key in after) {
+    // checking the height/width options
+    if ((key == 'width' || key == 'height') && before[key] == 'auto') {
+      before[key] = element._['offset'+key.capitalize()] + 'px';
+    }
+  }
+
+  // IE opacity filter fix
+  if (IE_OPACITY && after.filter && !before.filter) {
+    before.filter = 'alpha(opacity=100)';
+  }
+
+  // adjusting the border style
+  check_border_styles(element, before, after);
+
+  // cleaing up the list
+  for (key in after) {
+    // proprocessing colors
+    if (after[key] !== before[key] && /color/i.test(key)) {
+      if (Browser_Opera) {
+        after[key] = after[key].replace(/"/g, '');
+        before[key] = before[key].replace(/"/g, '');
+      }
+
+      if (!is_transparent(after[key]))  { after[key]  = after[key].toRgb(); }
+      if (!is_transparent(before[key])) { before[key] = before[key].toRgb(); }
+
+      if (!after[key] || !before[key]) {  after[key] = before[key] = ''; }
+    }
+
+    // filling up the missing size
+    if (/\d/.test(after[key]) && !/\d/.test(before[key])) {
+      before[key] = after[key].replace(/[\d\.\-]+/g, '0');
+    }
+
+    // removing unprocessable keys
+    if (after[key] === before[key] || !/\d/.test(before[key]) || !/\d/.test(after[key])) {
+      delete(after[key]);
+      delete(before[key]);
+    }
+  }
+}
+
+// cloning the element current styles hash
+function clone_style(element, keys) {
+  var i=0, len = keys.length, style = element.computedStyles(), clean = {}, key;
+
+  for (; i < len; i++) {
+    key = keys[i];
+
+    if (key in style) {
+      clean[key] = ''+ style[key];
+
+      // libwebkit bug fix for in case of languages pack applied
+      if (key === 'opacity') {
+        clean[key] = clean[key].replace(',', '.');
+      }
+    }
+  }
+
+  return clean;
+}
+
+// calculating the end styles hash
+function end_style(element, style, keys) {
+  var dummy = element.clone()
+      .setStyle('position:absolute;z-index:-1;visibility:hidden')
+      .setWidth(element.size().x)
+      .setStyle(style), after;
+
+  if (element.parent()) {
+    element.insert(dummy, 'before');
+  }
+
+  after = clone_style(dummy, keys);
+  dummy.remove();
+
+  return after;
+}
+
+
+/**
+ * the elements hightlighting effect
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Fx.Highlight = new Class(Fx.Morph, {
+  extend: {
+    Options: Object.merge(Fx.Options, {
+      color:      '#FF8',
+      transition: 'Exp'
+    })
+  },
+
+// protected
+
+  /**
+   * starts the transition
+   *
+   * @param high String the hightlight color
+   * @param back String optional fallback color
+   * @return self
+   */
+  prepare: function(start, end) {
+    var element       = this.element,
+        element_style = element._.style,
+        style_name    = 'backgroundColor',
+        end_color     = end || element.getStyle(style_name);
+
+    if (is_transparent(end_color)) {
+      this.onFinish(function() { element_style[style_name] = 'transparent'; });
+
+      // trying to find the end color
+      end_color = [element].concat(element.parents())
+        .map('getStyle', style_name)
+        .reject(is_transparent)
+        .compact().first() || '#FFF';
+    }
+
+    element_style[style_name] = (start || this.options.color);
+
+    return this.$super({backgroundColor: end_color});
+  }
+});
+
+
+/**
+ * this is a superclass for the bidirectional effects
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Fx.Twin = new Class(Fx.Morph, {
+
+  /**
+   * hides the element if it meant to be switched off
+   *
+   * @return Fx self
+   */
+  finish: function() {
+    if (this.how === 'out') {
+      // calling 'prototype' to prevent circular calls from subclasses
+      Element.prototype.hide.call(this.element);
+    }
+
+    return this.$super();
+  },
+
+// protected
+
+  /**
+   * assigns the direction of the effect in or out
+   *
+   * @param String 'in', 'out' or 'toggle', 'toggle' by default
+   */
+  setHow: function(how) {
+    this.how = how || 'toggle';
+
+    if (this.how === 'toggle') {
+      this.how = this.element.visible() ? 'out' : 'in';
+    }
+  }
+
+});
+
+
+/**
+ * the slide effects wrapper
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Fx.Slide = new Class(Fx.Twin, {
+  extend: {
+    Options: Object.merge(Fx.Options, {
+      direction: 'top'
+    })
+  },
+
+// protected
+  prepare: function(how) {
+    this.setHow(how);
+
+    // calling 'prototype' to prevent circular calls from subclasses
+    var element = Element.prototype.show.call(this.element),
+        element_style = element._.style,
+        old_styles = Object.only(
+          element_style,
+          'overflow', 'width', 'height',
+          'marginTop', 'marginLeft'
+        );
+
+    function restore_styles() {
+      for (var key in old_styles) {
+        element_style[key] = old_styles[key];
+      }
+    }
+
+    this.onFinish(restore_styles).onCancel(restore_styles);
+
+    element_style.overflow = 'hidden';
+
+    return this.$super(fx_slide_prepare_styles(
+      element_style,
+      element.size(),
+      this.options.direction,
+      this.how
+    ));
+  }
+});
+
+function fx_slide_prepare_styles(element_style, size, direction, how) {
+  var style = {},
+      margin_left = element_style.marginLeft.toFloat() || 0,
+      margin_top  = element_style.marginTop.toFloat()  || 0,
+      to_right  = direction === 'right',
+      to_bottom = direction === 'bottom',
+      vertical  = direction === 'top' || to_bottom;
+
+  if (how === 'out') {
+    style[vertical ? 'height' : 'width'] = '0px';
+
+    if (to_right) {
+      style.marginLeft = margin_left + size.x+'px';
+    } else if (to_bottom) {
+      style.marginTop = margin_top + size.y +'px';
+    }
+  } else {
+    if (vertical) {
+      style.height = size.y + 'px';
+      element_style.height = '0px';
+    } else {
+      style.width = size.x + 'px';
+      element_style.width = '0px';
+    }
+
+    if (to_right) {
+      style.marginLeft = margin_left + 'px';
+      element_style.marginLeft = margin_left + size.x + 'px';
+    } else if (to_bottom) {
+      style.marginTop = margin_top + 'px';
+      element_style.marginTop = margin_top + size.y + 'px';
+    }
+  }
+
+  return style;
+}
+
+
+/**
+ * The opacity effects wrapper
+ *
+ * Copyright (C) 2008-2011 Nikolay V. Nemshilov
+ */
+Fx.Fade = new Class(Fx.Twin, {
+  prepare: function(how) {
+    this.setHow(how);
+
+    if (this.how === 'in') {
+      // calling 'prototype' to prevent circular calls from subclasses
+      Element.prototype.show.call(this.element.setStyle({opacity: 0}));
+    }
+
+    return this.$super({opacity: this.how === 'in' ? 1 : 0});
+  }
+});
+
+
+/**
+ * An abstract attributes based Fx
+ *
+ * Copyright (C) 2010 Nikolay Nemshilov
+ */
+Fx.Attr = new Class(Fx, {
+
+  prepare: function(attrs) {
+    this.before = {};
+    this.after  = attrs;
+    var key, element = this.element._;
+
+    for (key in attrs) {
+      this.before[key] = element[key];
+    }
+  },
+
+  render: function(delta) {
+    var key, element = this.element._, before = this.before;
+    for (key in before) {
+      element[key] = before[key] + (this.after[key] - before[key]) * delta;
+    }
+  }
+
+});
+
+/**
+ * A smooth scrolling visual effect
+ *
+ * Copyright (C) 2009-2011 Nikolay Nemshilov
+ */
+Fx.Scroll = new Class(Fx.Attr, {
+
+  initialize: function(element, options) {
+    element = $(element);
+    // swapping the actual scrollable when it's the window
+    this.$super(
+      element instanceof Window ?
+        element._.document[
+          'body' in element._.document ? 'body' : 'documentElement'
+        ] : element,
+      options
+    );
+  },
+
+  prepare: function(value) {
+    var attrs = {};
+
+    if ('x' in value) { attrs.scrollLeft = value.x; }
+    if ('y' in value) { attrs.scrollTop  = value.y; }
+
+    this.$super(attrs);
+  }
+
+});
+
+
+/**
+ * this module handles the work with cookies
+ *
+ * Credits:
+ *   Most things in the unit are take from
+ *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
+ *
+ * Copyright (C) 2008-2010 Nikolay V. Nemshilov
+ */
+var Cookie = RightJS.Cookie = new Class({
+  include: Options,
+
+  extend: {
+    // sets the cookie
+    set: function(name, value, options) {
+      return new this(name, options).set(value);
+    },
+    // gets the cookie
+    get: function(name) {
+      return new this(name).get();
+    },
+    // deletes the cookie
+    remove: function(name) {
+      return new this(name).remove();
+    },
+
+    // checks if the cookies are enabled
+    enabled: function() {
+      document.cookie = "__t=1";
+      return document.cookie.indexOf("__t=1")!=-1;
+    },
+
+    // some basic options
+    Options: {
+      secure:   false,
+      document: document
+    }
+  },
+
+  /**
+   * constructor
+   * @param String cookie name
+   * @param Object options
+   * @return void
+   */
+  initialize: function(name, options) {
+    this.name = name;
+    this.setOptions(options);
+  },
+
+  /**
+   * sets the cookie with the name
+   *
+   * @param mixed value
+   * @return Cookie this
+   */
+  set: function(data) {
+    var value = encodeURIComponent(data), options = this.options;
+    if (options.domain) { value += '; domain=' + options.domain; }
+    if (options.path)   { value += '; path=' + options.path; }
+    if (options.duration) {
+      var date = new Date();
+      date.setTime(date.getTime() + options.duration * 24 * 60 * 60 * 1000);
+      value += '; expires=' + date.toGMTString();
+    }
+    if (options.secure) { value += '; secure'; }
+    options.document.cookie = this.name + '=' + value;
+    return this;
+  },
+
+  /**
+   * searches for a cookie with the name
+   *
+   * @return mixed saved value or null if nothing found
+   */
+  get: function() {
+    var value = this.options.document.cookie.match(
+      '(?:^|;)\\s*' + RegExp.escape(this.name) + '=([^;]*)'
+    );
+    return value ? decodeURIComponent(value[1]) : null;
+  },
+
+  /**
+   * removes the cookie
+   *
+   * @return Cookie this
+   */
+  remove: function() {
+    this.options.duration = -1;
+    return this.set('');
+  }
+});
+
+
+// globalizing the top-level variables
+$ext(window, Object.without(RightJS, 'version', 'modules'));
+
+return RightJS;
+})(window, document, Object, Array, String, Function, Number, Math);
+/**
+ * The old browsers support patch loading script
+ * will be included in the core file when it's built
+ * with the no-olds option
+ *
+ * Basically it just checks all the script tags on the page
+ * finds the core inclusion tag and uses it's src attribute
+ * to dynamically load the olds patch
+ *
+ * Copyright (C) 2009-2011 Nikolay V. Nemshilov
+ */
+if (RightJS.Browser.OLD) {
+  (function(d) {
+    var script  = d.createElement('script'),
+        scripts = d.getElementsByTagName('script'),
+        rjs_spt = scripts[scripts.length - 1];
+
+    script.src = rjs_spt.src.replace(/(^|\/)(right)([^\/]+)$/, '$1$2-olds$3');
+
+    rjs_spt.parentNode.appendChild(script);
+  })(document);
+}

+ 1193 - 0
js/libraries/balupton-history/vendor/zepto.js

@@ -0,0 +1,1193 @@
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+(function(undefined){
+  if (String.prototype.trim === undefined) // fix for iOS 3.2
+    String.prototype.trim = function(){ return this.replace(/^\s+/, '').replace(/\s+$/, '') };
+
+  // For iOS 3.x
+  // from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reduce
+  if (Array.prototype.reduce === undefined)
+    Array.prototype.reduce = function(fun){
+      if(this === void 0 || this === null) throw new TypeError();
+      var t = Object(this), len = t.length >>> 0, k = 0, accumulator;
+      if(typeof fun != 'function') throw new TypeError();
+      if(len == 0 && arguments.length == 1) throw new TypeError();
+
+      if(arguments.length >= 2)
+       accumulator = arguments[1];
+      else
+        do{
+          if(k in t){
+            accumulator = t[k++];
+            break;
+          }
+          if(++k >= len) throw new TypeError();
+        } while (true);
+
+      while (k < len){
+        if(k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t);
+        k++;
+      }
+      return accumulator;
+    };
+
+})();
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+var Zepto = (function() {
+  var undefined, key, $$, classList, emptyArray = [], slice = emptyArray.slice,
+    document = window.document,
+    elementDisplay = {}, classCache = {},
+    getComputedStyle = document.defaultView.getComputedStyle,
+    cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
+    fragmentRE = /^\s*<(\w+)[^>]*>/,
+    elementTypes = [1, 9, 11],
+    adjacencyOperators = ['prepend', 'after', 'before', 'append'],
+    reverseAdjacencyOperators = ['append', 'prepend'],
+    table = document.createElement('table'),
+    tableRow = document.createElement('tr'),
+    containers = {
+      'tr': document.createElement('tbody'),
+      'tbody': table, 'thead': table, 'tfoot': table,
+      'td': tableRow, 'th': tableRow,
+      '*': document.createElement('div')
+    };
+
+  function isF(value) { return ({}).toString.call(value) == "[object Function]" }
+  function isO(value) { return value instanceof Object }
+  function isA(value) { return value instanceof Array }
+  function likeArray(obj) { return typeof obj.length == 'number' }
+
+  function compact(array) { return array.filter(function(item){ return item !== undefined && item !== null }) }
+  function flatten(array) { return array.length > 0 ? [].concat.apply([], array) : array }
+  function camelize(str)  { return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
+  function dasherize(str){
+    return str.replace(/::/g, '/')
+           .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
+           .replace(/([a-z\d])([A-Z])/g, '$1_$2')
+           .replace(/_/g, '-')
+           .toLowerCase();
+  }
+  function uniq(array)    { return array.filter(function(item,index,array){ return array.indexOf(item) == index }) }
+
+  function classRE(name){
+    return name in classCache ?
+      classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'));
+  }
+
+  function maybeAddPx(name, value) { return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value; }
+
+  function defaultDisplay(nodeName) {
+    var element, display;
+    if (!elementDisplay[nodeName]) {
+      element = document.createElement(nodeName);
+      document.body.appendChild(element);
+      display = getComputedStyle(element, '').getPropertyValue("display");
+      element.parentNode.removeChild(element);
+      display == "none" && (display = "block");
+      elementDisplay[nodeName] = display;
+    }
+    return elementDisplay[nodeName];
+  }
+
+  function fragment(html, name) {
+    if (name === undefined) fragmentRE.test(html) && RegExp.$1;
+    if (!(name in containers)) name = '*';
+    var container = containers[name];
+    container.innerHTML = '' + html;
+    return slice.call(container.childNodes);
+  }
+
+  function Z(dom, selector){
+    dom = dom || emptyArray;
+    dom.__proto__ = Z.prototype;
+    dom.selector = selector || '';
+    return dom;
+  }
+
+  function $(selector, context){
+    if (!selector) return Z();
+    if (context !== undefined) return $(context).find(selector);
+    else if (isF(selector)) return $(document).ready(selector);
+    else if (selector instanceof Z) return selector;
+    else {
+      var dom;
+      if (isA(selector)) dom = compact(selector);
+      else if (elementTypes.indexOf(selector.nodeType) >= 0 || selector === window)
+        dom = [selector], selector = null;
+      else if (fragmentRE.test(selector))
+        dom = fragment(selector, RegExp.$1), selector = null;
+      else if (selector.nodeType && selector.nodeType == 3) dom = [selector];
+      else dom = $$(document, selector);
+      return Z(dom, selector);
+    }
+  }
+
+  $.extend = function(target){
+    slice.call(arguments, 1).forEach(function(source) {
+      for (key in source) target[key] = source[key];
+    })
+    return target;
+  }
+  $.qsa = $$ = function(element, selector){ return slice.call(element.querySelectorAll(selector)) }
+
+  function filtered(nodes, selector){
+    return selector === undefined ? $(nodes) : $(nodes).filter(selector);
+  }
+
+  function funcArg(context, arg, idx, payload){
+   return isF(arg) ? arg.call(context, idx, payload) : arg;
+  }
+
+  $.isFunction = isF;
+  $.isObject = isO;
+  $.isArray = isA;
+
+  $.map = function(elements, callback) {
+    var value, values = [], i, key;
+    if (likeArray(elements))
+      for (i = 0; i < elements.length; i++) {
+        value = callback(elements[i], i);
+        if (value != null) values.push(value);
+      }
+    else
+      for (key in elements) {
+        value = callback(elements[key], key);
+        if (value != null) values.push(value);
+      }
+    return flatten(values);
+  }
+
+  $.each = function(elements, callback) {
+    var i, key;
+    if (likeArray(elements))
+      for(i = 0; i < elements.length; i++) {
+        if(callback(i, elements[i]) === false) return elements;
+      }
+    else
+      for(key in elements) {
+        if(callback(key, elements[key]) === false) return elements;
+      }
+    return elements;
+  }
+
+  $.fn = {
+    forEach: emptyArray.forEach,
+    reduce: emptyArray.reduce,
+    push: emptyArray.push,
+    indexOf: emptyArray.indexOf,
+    concat: emptyArray.concat,
+    map: function(fn){
+      return $.map(this, function(el, i){ return fn.call(el, i, el) });
+    },
+    slice: function(){
+      return $(slice.apply(this, arguments));
+    },
+    ready: function(callback){
+      if (document.readyState == 'complete' || document.readyState == 'loaded') callback();
+      document.addEventListener('DOMContentLoaded', callback, false);
+      return this;
+    },
+    get: function(idx){ return idx === undefined ? this : this[idx] },
+    size: function(){ return this.length },
+    remove: function () {
+      return this.each(function () {
+        if (this.parentNode != null) {
+          this.parentNode.removeChild(this);
+        }
+      });
+    },
+    each: function(callback){
+      this.forEach(function(el, idx){ callback.call(el, idx, el) });
+      return this;
+    },
+    filter: function(selector){
+      return $([].filter.call(this, function(element){
+        return $$(element.parentNode, selector).indexOf(element) >= 0;
+      }));
+    },
+    end: function(){
+      return this.prevObject || $();
+    },
+    add:function(selector,context){
+      return $(uniq(this.concat($(selector,context))));
+    },
+    is: function(selector){
+      return this.length > 0 && $(this[0]).filter(selector).length > 0;
+    },
+    not: function(selector){
+      var nodes=[];
+      if (isF(selector) && selector.call !== undefined)
+        this.each(function(idx){
+          if (!selector.call(this,idx)) nodes.push(this);
+        });
+      else {
+        var excludes = typeof selector == 'string' ? this.filter(selector) :
+          (likeArray(selector) && isF(selector.item)) ? slice.call(selector) : $(selector);
+        this.forEach(function(el){
+          if (excludes.indexOf(el) < 0) nodes.push(el);
+        });
+      }
+      return $(nodes);
+    },
+    eq: function(idx){
+      return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1);
+    },
+    first: function(){ return $(this[0]) },
+    last: function(){ return $(this[this.length - 1]) },
+    find: function(selector){
+      var result;
+      if (this.length == 1) result = $$(this[0], selector);
+      else result = this.map(function(){ return $$(this, selector) });
+      return $(result);
+    },
+    closest: function(selector, context){
+      var node = this[0], nodes = $$(context !== undefined ? context : document, selector);
+      if (nodes.length === 0) node = null;
+      while(node && node !== document && nodes.indexOf(node) < 0) node = node.parentNode;
+      return $(node !== document && node);
+    },
+    parents: function(selector){
+      var ancestors = [], nodes = this;
+      while (nodes.length > 0)
+        nodes = $.map(nodes, function(node){
+          if ((node = node.parentNode) && node !== document && ancestors.indexOf(node) < 0) {
+            ancestors.push(node);
+            return node;
+          }
+        });
+      return filtered(ancestors, selector);
+    },
+    parent: function(selector){
+      return filtered(uniq(this.pluck('parentNode')), selector);
+    },
+    children: function(selector){
+      return filtered(this.map(function(){ return slice.call(this.children) }), selector);
+    },
+    siblings: function(selector){
+      return filtered(this.map(function(i, el){
+        return slice.call(el.parentNode.children).filter(function(child){ return child!==el });
+      }), selector);
+    },
+    empty: function(){ return this.each(function(){ this.innerHTML = '' }) },
+    pluck: function(property){ return this.map(function(){ return this[property] }) },
+    show: function(){
+      return this.each(function() {
+        this.style.display == "none" && (this.style.display = null);
+        if (getComputedStyle(this, '').getPropertyValue("display") == "none") {
+          this.style.display = defaultDisplay(this.nodeName)
+        }
+      })
+    },
+    replaceWith: function(newContent) {
+      return this.each(function() {
+        var par=this.parentNode,next=this.nextSibling;
+        $(this).remove();
+        next ? $(next).before(newContent) : $(par).append(newContent);
+      });
+    },
+    wrap: function(newContent) {
+      return this.each(function() {
+        $(this).wrapAll($(newContent)[0].cloneNode(false));
+      });
+    },
+    wrapAll: function(newContent) {
+      if (this[0]) {
+        $(this[0]).before(newContent = $(newContent));
+        newContent.append(this);
+      }
+      return this;
+    },
+    unwrap: function(){
+      this.parent().each(function(){
+        $(this).replaceWith($(this).children());
+      });
+      return this;
+    },
+    hide: function(){
+      return this.css("display", "none")
+    },
+    toggle: function(setting){
+      return (setting === undefined ? this.css("display") == "none" : setting) ? this.show() : this.hide();
+    },
+    prev: function(){ return $(this.pluck('previousElementSibling')) },
+    next: function(){ return $(this.pluck('nextElementSibling')) },
+    html: function(html){
+      return html === undefined ?
+        (this.length > 0 ? this[0].innerHTML : null) :
+        this.each(function (idx) {
+          var originHtml = this.innerHTML;
+          $(this).empty().append( funcArg(this, html, idx, originHtml) );
+        });
+    },
+    text: function(text){
+      return text === undefined ?
+        (this.length > 0 ? this[0].textContent : null) :
+        this.each(function(){ this.textContent = text });
+    },
+    attr: function(name, value){
+      return (typeof name == 'string' && value === undefined) ?
+        (this.length > 0 && this[0].nodeName == 'INPUT' && this[0].type == 'text' && name == 'value') ? (this.val()) :
+        (this.length > 0 ? this[0].getAttribute(name) || (name in this[0] ? this[0][name] : undefined) : undefined) :
+        this.each(function(idx){
+          if (isO(name)) for (key in name) this.setAttribute(key, name[key])
+          else this.setAttribute(name, funcArg(this, value, idx, this.getAttribute(name)));
+        });
+    },
+    removeAttr: function(name) {
+      return this.each(function() { this.removeAttribute(name); });
+    },
+    data: function(name, value){
+      return this.attr('data-' + name, value);
+    },
+    val: function(value){
+      return (value === undefined) ?
+        (this.length > 0 ? this[0].value : null) :
+        this.each(function(){
+          this.value = value;
+        });
+    },
+    offset: function(){
+      if(this.length==0) return null;
+      var obj = this[0].getBoundingClientRect();
+      return {
+        left: obj.left + document.body.scrollLeft,
+        top: obj.top + document.body.scrollTop,
+        width: obj.width,
+        height: obj.height
+      };
+    },
+    css: function(property, value){
+      if (value === undefined && typeof property == 'string')
+        return this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property);
+      var css = '';
+      for (key in property) css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';';
+      if (typeof property == 'string') css = dasherize(property) + ":" + maybeAddPx(property, value);
+      return this.each(function() { this.style.cssText += ';' + css });
+    },
+    index: function(element){
+      return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]);
+    },
+    hasClass: function(name){
+      if (this.length < 1) return false;
+      else return classRE(name).test(this[0].className);
+    },
+    addClass: function(name){
+      return this.each(function(idx) {
+        classList = [];
+        var cls = this.className, newName = funcArg(this, name, idx, cls);
+        newName.split(/\s+/g).forEach(function(klass) {
+          if (!$(this).hasClass(klass)) {
+            classList.push(klass)
+          }
+        }, this);
+        classList.length && (this.className += (cls ? " " : "") + classList.join(" "))
+      });
+    },
+    removeClass: function(name){
+      return this.each(function(idx) {
+        if(name === undefined)
+          return this.className = '';
+        classList = this.className;
+        funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass) {
+          classList = classList.replace(classRE(klass), " ")
+        });
+        this.className = classList.trim()
+      });
+    },
+    toggleClass: function(name, when){
+      return this.each(function(idx){
+       var cls = this.className, newName = funcArg(this, name, idx, cls);
+       ((when !== undefined && !when) || $(this).hasClass(newName)) ?
+         $(this).removeClass(newName) : $(this).addClass(newName)
+      });
+    }
+  };
+
+  'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property){
+    var fn = $.fn[property];
+    $.fn[property] = function() {
+      var ret = fn.apply(this, arguments);
+      ret.prevObject = this;
+      return ret;
+    }
+  });
+
+  ['width', 'height'].forEach(function(property){
+    $.fn[property] = function(value) {
+      var offset;
+      if (value === undefined) { return (offset = this.offset()) && offset[property] }
+      else return this.css(property, value);
+    }
+  });
+
+  function insert(operator, target, node) {
+    var parent = (!operator || operator == 3) ? target : target.parentNode;
+    parent.insertBefore(node,
+      !operator ? parent.firstChild :         // prepend
+      operator == 1 ? target.nextSibling :    // after
+      operator == 2 ? target :                // before
+      null);                                  // append
+  }
+
+  function traverseNode (node, fun) {
+    fun(node);
+    for (key in node.childNodes) {
+      traverseNode(node.childNodes[key], fun);
+    }
+  }
+
+  adjacencyOperators.forEach(function(key, operator) {
+    $.fn[key] = function(html){
+      var nodes = typeof(html) == 'object' ? html : fragment(html);
+      if (!('length' in nodes)) nodes = [nodes];
+      if (nodes.length < 1) return this;
+      var size = this.length, copyByClone = size > 1, inReverse = operator < 2;
+
+      return this.each(function(index, target){
+        for (var i = 0; i < nodes.length; i++) {
+          var node = nodes[inReverse ? nodes.length-i-1 : i];
+          traverseNode(node, function (node) {
+            if (node.nodeName != null && node.nodeName.toUpperCase() === 'SCRIPT') {
+              window['eval'].call(window, node.innerHTML);
+            }
+          });
+          if (copyByClone && index < size - 1) node = node.cloneNode(true);
+          insert(operator, target, node);
+        }
+      });
+    };
+  });
+
+  reverseAdjacencyOperators.forEach(function(key) {
+    $.fn[key+'To'] = function(html){
+      if (typeof(html) != 'object') html = $(html);
+      html[key](this);
+      return this;
+    };
+  });
+
+  Z.prototype = $.fn;
+
+  return $;
+})();
+
+'$' in window || (window.$ = Zepto);
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+(function($){
+  var $$ = $.qsa, handlers = {}, _zid = 1;
+  function zid(element) {
+    return element._zid || (element._zid = _zid++);
+  }
+  function findHandlers(element, event, fn, selector) {
+    event = parse(event);
+    if (event.ns) var matcher = matcherFor(event.ns);
+    return (handlers[zid(element)] || []).filter(function(handler) {
+      return handler
+        && (!event.e  || handler.e == event.e)
+        && (!event.ns || matcher.test(handler.ns))
+        && (!fn       || handler.fn == fn)
+        && (!selector || handler.sel == selector);
+    });
+  }
+  function parse(event) {
+    var parts = ('' + event).split('.');
+    return {e: parts[0], ns: parts.slice(1).sort().join(' ')};
+  }
+  function matcherFor(ns) {
+    return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)');
+  }
+
+  function add(element, events, fn, selector, delegate){
+    var id = zid(element), set = (handlers[id] || (handlers[id] = []));
+    events.split(/\s/).forEach(function(event){
+      var callback = delegate || fn;
+      var proxyfn = function (event) {
+        var result = callback.apply(element, [event].concat(event.data));
+        if (result === false) {
+          event.preventDefault();
+        }
+        return result;
+      };
+      var handler = $.extend(parse(event), {fn: fn, proxy: proxyfn, sel: selector, del: delegate, i: set.length});
+      set.push(handler);
+      element.addEventListener(handler.e, proxyfn, false);
+    });
+  }
+  function remove(element, events, fn, selector){
+    var id = zid(element);
+    (events || '').split(/\s/).forEach(function(event){
+      findHandlers(element, event, fn, selector).forEach(function(handler){
+        delete handlers[id][handler.i];
+        element.removeEventListener(handler.e, handler.proxy, false);
+      });
+    });
+  }
+
+  $.event = { add: add, remove: remove }
+
+  $.fn.bind = function(event, callback){
+    return this.each(function(){
+      add(this, event, callback);
+    });
+  };
+  $.fn.unbind = function(event, callback){
+    return this.each(function(){
+      remove(this, event, callback);
+    });
+  };
+  $.fn.one = function(event, callback){
+    return this.each(function(){
+      var self = this;
+      add(this, event, function wrapper(evt){
+        callback.call(self, evt);
+        remove(self, event, arguments.callee);
+      });
+    });
+  };
+
+  var returnTrue = function(){return true},
+      returnFalse = function(){return false},
+      eventMethods = {
+        preventDefault: 'isDefaultPrevented',
+        stopImmediatePropagation: 'isImmediatePropagationStopped',
+        stopPropagation: 'isPropagationStopped'
+      };
+  function createProxy(event) {
+    var proxy = $.extend({originalEvent: event}, event);
+    $.each(eventMethods, function(name, predicate) {
+      proxy[name] = function(){
+        this[predicate] = returnTrue;
+        return event[name].apply(event, arguments);
+      };
+      proxy[predicate] = returnFalse;
+    })
+    return proxy;
+  }
+
+  $.fn.delegate = function(selector, event, callback){
+    return this.each(function(i, element){
+      add(element, event, callback, selector, function(e, data){
+        var target = e.target, nodes = $$(element, selector);
+        while (target && nodes.indexOf(target) < 0) target = target.parentNode;
+        if (target && !(target === element) && !(target === document)) {
+          callback.call(target, $.extend(createProxy(e), {
+            currentTarget: target, liveFired: element
+          }), data);
+        }
+      });
+    });
+  };
+  $.fn.undelegate = function(selector, event, callback){
+    return this.each(function(){
+      remove(this, event, callback, selector);
+    });
+  }
+
+  $.fn.live = function(event, callback){
+    $(document.body).delegate(this.selector, event, callback);
+    return this;
+  };
+  $.fn.die = function(event, callback){
+    $(document.body).undelegate(this.selector, event, callback);
+    return this;
+  };
+
+  $.fn.trigger = function(event, data){
+    if (typeof event == 'string') event = $.Event(event);
+    event.data = data;
+    return this.each(function(){ this.dispatchEvent(event) });
+  };
+
+  // triggers event handlers on current element just as if an event occurred,
+  // doesn't trigger an actual event, doesn't bubble
+  $.fn.triggerHandler = function(event, data){
+    var e, result;
+    this.each(function(i, element){
+      e = createProxy(typeof event == 'string' ? $.Event(event) : event);
+      e.data = data; e.target = element;
+      $.each(findHandlers(element, event.type || event), function(i, handler){
+        result = handler.proxy(e);
+        if (e.isImmediatePropagationStopped()) return false;
+      });
+    });
+    return result;
+  };
+
+  // shortcut methods for `.bind(event, fn)` for each event type
+  ('focusin focusout load resize scroll unload click dblclick '+
+  'mousedown mouseup mousemove mouseover mouseout '+
+  'change select keydown keypress keyup error').split(' ').forEach(function(event) {
+    $.fn[event] = function(callback){ return this.bind(event, callback) };
+  });
+
+  ['focus', 'blur'].forEach(function(name) {
+    $.fn[name] = function(callback) {
+      if (callback) this.bind(name, callback);
+      else if (this.length) try { this.get(0)[name]() } catch(e){};
+      return this;
+    };
+  });
+
+  $.Event = function(type, props) {
+    var event = document.createEvent('Events');
+    if (props) $.extend(event, props);
+    event.initEvent(type, !(props && props.bubbles === false), true);
+    return event;
+  };
+
+})(Zepto);
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+(function($){
+  function detect(ua){
+    var ua = ua, os = {},
+      android = ua.match(/(Android)\s+([\d.]+)/),
+      ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
+      iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
+      webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
+      touchpad = webos && ua.match(/TouchPad/),
+      blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/);
+    if (android) os.android = true, os.version = android[2];
+    if (iphone) os.ios = true, os.version = iphone[2].replace(/_/g, '.'), os.iphone = true;
+    if (ipad) os.ios = true, os.version = ipad[2].replace(/_/g, '.'), os.ipad = true;
+    if (webos) os.webos = true, os.version = webos[2];
+    if (touchpad) os.touchpad = true;
+    if (blackberry) os.blackberry = true, os.version = blackberry[2];
+    return os;
+  }
+
+  // ### $.os
+  //
+  // Object contains information about running environmental
+  //
+  // *Example:*
+  //
+  //     $.os.ios      // => true if running on Apple iOS
+  //     $.os.android  // => true if running on Android
+  //     $.os.webos    // => true if running on HP/Palm WebOS
+  //     $.os.touchpad // => true if running on a HP TouchPad
+  //     $.os.version  // => string with version number,
+  //                         "4.0", "3.1.1", "2.1", etc.
+  //     $.os.iphone   // => true if running on iPhone
+  //     $.os.ipad     // => true if running on iPad
+  //     $.os.blackberry // => true if running on BlackBerry
+  //
+  $.os = detect(navigator.userAgent);
+  $.__detect = detect;
+
+  var v = navigator.userAgent.match(/WebKit\/([\d.]+)/);
+  $.browser = v ? { webkit: true, version: v[1] } : { webkit: false };
+
+})(Zepto);
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+(function($, undefined){
+  var supportedTransforms = [
+    'scale', 'scaleX', 'scaleY',
+    'translate', 'translateX', 'translateY', 'translate3d',
+    'skew',      'skewX',      'skewY',
+    'rotate',    'rotateX',    'rotateY',    'rotateZ',    'rotate3d',
+    'matrix'
+  ];
+
+  $.fn.anim = function(properties, duration, ease, callback){
+    var transforms = [], cssProperties = {}, key, that = this, wrappedCallback;
+
+    for (key in properties)
+      if (supportedTransforms.indexOf(key)>=0)
+        transforms.push(key + '(' + properties[key] + ')');
+      else
+        cssProperties[key] = properties[key];
+
+    wrappedCallback = function(){
+      that.css({'-webkit-transition':'none'});
+      callback && callback();
+    }
+
+    if (duration > 0)
+      this.one('webkitTransitionEnd', wrappedCallback);
+    else
+      setTimeout(wrappedCallback, 0);
+
+    if (transforms.length > 0) {
+      cssProperties['-webkit-transform'] = transforms.join(' ')
+    }
+
+    cssProperties['-webkit-transition'] = 'all ' + (duration !== undefined ? duration : 0.5) + 's ' + (ease || '');
+
+    setTimeout(function () {
+      that.css(cssProperties);
+    }, 0);
+
+    return this;
+  }
+})(Zepto);
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+(function($){
+  var jsonpID = 0,
+      isObject = $.isObject,
+      key;
+
+  // Empty function, used as default callback
+  function empty() {}
+
+  // ### $.ajaxJSONP
+  //
+  // Load JSON from a server in a different domain (JSONP)
+  //
+  // *Arguments:*
+  //
+  //     options — object that configure the request,
+  //               see avaliable options below
+  //
+  // *Avaliable options:*
+  //
+  //     url     — url to which the request is sent
+  //     success — callback that is executed if the request succeeds
+  //
+  // *Example:*
+  //
+  //     $.ajaxJSONP({
+  //        url:     'http://example.com/projects?callback=?',
+  //        success: function (data) {
+  //            projects.push(json);
+  //        }
+  //     });
+  //
+  $.ajaxJSONP = function(options){
+    var jsonpString = 'jsonp' + ++jsonpID,
+        script = document.createElement('script');
+    window[jsonpString] = function(data){
+      options.success(data);
+      delete window[jsonpString];
+    };
+    script.src = options.url.replace(/=\?/, '=' + jsonpString);
+    $('head').append(script);
+  };
+
+  // ### $.ajaxSettings
+  //
+  // AJAX settings
+  //
+  $.ajaxSettings = {
+    // Default type of request
+    type: 'GET',
+    // Callback that is executed before request
+    beforeSend: empty,
+    // Callback that is executed if the request succeeds
+    success: empty,
+    // Callback that is executed the the server drops error
+    error: empty,
+    // Callback that is executed on request complete (both: error and success)
+    complete: empty,
+    // MIME types mapping
+    accepts: {
+      script: 'text/javascript, application/javascript',
+      json:   'application/json',
+      xml:    'application/xml, text/xml',
+      html:   'text/html',
+      text:   'text/plain'
+    }
+  };
+
+  // ### $.ajax
+  //
+  // Perform AJAX request
+  //
+  // *Arguments:*
+  //
+  //     options — object that configure the request,
+  //               see avaliable options below
+  //
+  // *Avaliable options:*
+  //
+  //     type ('GET')          — type of request GET / POST
+  //     url (window.location) — url to which the request is sent
+  //     data                  — data to send to server,
+  //                             can be string or object
+  //     dataType ('json')     — what response type you accept from
+  //                             the server:
+  //                             'json', 'xml', 'html', or 'text'
+  //     success               — callback that is executed if
+  //                             the request succeeds
+  //     error                 — callback that is executed if
+  //                             the server drops error
+  //
+  // *Example:*
+  //
+  //     $.ajax({
+  //        type:     'POST',
+  //        url:      '/projects',
+  //        data:     { name: 'Zepto.js' },
+  //        dataType: 'html',
+  //        success:  function (data) {
+  //            $('body').append(data);
+  //        },
+  //        error:    function (xhr, type) {
+  //            alert('Error!');
+  //        }
+  //     });
+  //
+  $.ajax = function(options){
+    options = options || {};
+    var settings = $.extend({}, options);
+    for (key in $.ajaxSettings) if (!settings[key]) settings[key] = $.ajaxSettings[key];
+
+    if (/=\?/.test(settings.url)) return $.ajaxJSONP(settings);
+
+    if (!settings.url) settings.url = window.location.toString();
+    if (settings.data && !settings.contentType) settings.contentType = 'application/x-www-form-urlencoded';
+    if (isObject(settings.data)) settings.data = $.param(settings.data);
+
+    if (settings.type.match(/get/i) && settings.data) {
+      var queryString = settings.data;
+      if (settings.url.match(/\?.*=/)) {
+        queryString = '&' + queryString;
+      } else if (queryString[0] != '?') {
+        queryString = '?' + queryString;
+      }
+      settings.url += queryString;
+    }
+
+    var mime = settings.accepts[settings.dataType],
+        xhr = new XMLHttpRequest();
+
+    settings.headers = $.extend({'X-Requested-With': 'XMLHttpRequest'}, settings.headers || {});
+    if (mime) settings.headers['Accept'] = mime;
+
+    xhr.onreadystatechange = function(){
+      if (xhr.readyState == 4) {
+        var result, error = false;
+        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 0) {
+          if (mime == 'application/json' && !(xhr.responseText == '')) {
+            try { result = JSON.parse(xhr.responseText); }
+            catch (e) { error = e; }
+          }
+          else result = xhr.responseText;
+          if (error) settings.error(xhr, 'parsererror', error);
+          else settings.success(result, 'success', xhr);
+        } else {
+          error = true;
+          settings.error(xhr, 'error');
+        }
+        settings.complete(xhr, error ? 'error' : 'success');
+      }
+    };
+
+    xhr.open(settings.type, settings.url, true);
+    if (settings.beforeSend(xhr, settings) === false) {
+      xhr.abort();
+      return false;
+    }
+
+    if (settings.contentType) settings.headers['Content-Type'] = settings.contentType;
+    for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name]);
+    xhr.send(settings.data);
+
+    return xhr;
+  };
+
+  // ### $.get
+  //
+  // Load data from the server using a GET request
+  //
+  // *Arguments:*
+  //
+  //     url     — url to which the request is sent
+  //     success — callback that is executed if the request succeeds
+  //
+  // *Example:*
+  //
+  //     $.get(
+  //        '/projects/42',
+  //        function (data) {
+  //            $('body').append(data);
+  //        }
+  //     );
+  //
+  $.get = function(url, success){ $.ajax({ url: url, success: success }) };
+
+  // ### $.post
+  //
+  // Load data from the server using POST request
+  //
+  // *Arguments:*
+  //
+  //     url        — url to which the request is sent
+  //     [data]     — data to send to server, can be string or object
+  //     [success]  — callback that is executed if the request succeeds
+  //     [dataType] — type of expected response
+  //                  'json', 'xml', 'html', or 'text'
+  //
+  // *Example:*
+  //
+  //     $.post(
+  //        '/projects',
+  //        { name: 'Zepto.js' },
+  //        function (data) {
+  //            $('body').append(data);
+  //        },
+  //        'html'
+  //     );
+  //
+  $.post = function(url, data, success, dataType){
+    if ($.isFunction(data)) dataType = dataType || success, success = data, data = null;
+    $.ajax({ type: 'POST', url: url, data: data, success: success, dataType: dataType });
+  };
+
+  // ### $.getJSON
+  //
+  // Load JSON from the server using GET request
+  //
+  // *Arguments:*
+  //
+  //     url     — url to which the request is sent
+  //     success — callback that is executed if the request succeeds
+  //
+  // *Example:*
+  //
+  //     $.getJSON(
+  //        '/projects/42',
+  //        function (json) {
+  //            projects.push(json);
+  //        }
+  //     );
+  //
+  $.getJSON = function(url, success){ $.ajax({ url: url, success: success, dataType: 'json' }) };
+
+  // ### $.fn.load
+  //
+  // Load data from the server into an element
+  //
+  // *Arguments:*
+  //
+  //     url     — url to which the request is sent
+  //     [success] — callback that is executed if the request succeeds
+  //
+  // *Examples:*
+  //
+  //     $('#project_container').get(
+  //        '/projects/42',
+  //        function () {
+  //            alert('Project was successfully loaded');
+  //        }
+  //     );
+  //
+  //     $('#project_comments').get(
+  //        '/projects/42 #comments',
+  //        function () {
+  //            alert('Comments was successfully loaded');
+  //        }
+  //     );
+  //
+  $.fn.load = function(url, success){
+    if (!this.length) return this;
+    var self = this, parts = url.split(/\s/), selector;
+    if (parts.length > 1) url = parts[0], selector = parts[1];
+    $.get(url, function(response){
+      self.html(selector ?
+        $(document.createElement('div')).html(response).find(selector).html()
+        : response);
+      success && success();
+    });
+    return this;
+  };
+
+  // ### $.param
+  //
+  // Encode object as a string for submission
+  //
+  // *Arguments:*
+  //
+  //     obj — object to serialize
+  //     [v] — root node
+  //
+  // *Example:*
+  //
+  //     $.param( { name: 'Zepto.js', version: '0.6' } );
+  //
+  $.param = function(obj, v){
+    var result = [], add = function(key, value){
+      result.push(encodeURIComponent(v ? v + '[' + key + ']' : key)
+        + '=' + encodeURIComponent(value));
+      },
+      isObjArray = $.isArray(obj);
+
+    for(key in obj)
+      if(isObject(obj[key]))
+        result.push($.param(obj[key], (v ? v + '[' + key + ']' : key)));
+      else
+        add(isObjArray ? '' : key, obj[key]);
+
+    return result.join('&').replace('%20', '+');
+  };
+})(Zepto);
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+(function ($) {
+
+  // ### $.fn.serializeArray
+  //
+  // Encode a set of form elements as an array of names and values
+  //
+  // *Example:*
+  //
+  //     $('#login_form').serializeArray();
+  //
+  //  returns
+  //
+  //     [
+  //         {
+  //             name: 'email',
+  //             value: 'koss@nocorp.me'
+  //         },
+  //         {
+  //             name: 'password',
+  //             value: '123456'
+  //         }
+  //     ]
+  //
+  $.fn.serializeArray = function () {
+    var result = [], el;
+    $( Array.prototype.slice.call(this.get(0).elements) ).each(function () {
+      el = $(this);
+      if ( (el.attr('type') !== 'radio' || el.is(':checked')) && !(el.attr('type') === 'checkbox' && !el.is(':checked'))) {
+        result.push({
+          name: el.attr('name'),
+          value: el.val()
+        });
+      }
+    });
+    return result;
+  };
+
+  // ### $.fn.serialize
+  //
+  //
+  // Encode a set of form elements as a string for submission
+  //
+  // *Example:*
+  //
+  //     $('#login_form').serialize();
+  //
+  //  returns
+  //
+  //     "email=koss%40nocorp.me&password=123456"
+  //
+  $.fn.serialize = function () {
+    var result = [];
+    this.serializeArray().forEach(function (elm) {
+      result.push( encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value) );
+    });
+    return result.join('&');
+  };
+
+  // ### $.fn.submit
+  //
+  // Bind or trigger the submit event for a form
+  //
+  // *Examples:*
+  //
+  // To bind a handler for the submit event:
+  //
+  //     $('#login_form').submit(function (e) {
+  //         alert('Form was submitted!');
+  //         e.preventDefault();
+  //     });
+  //
+  // To trigger form submit:
+  //
+  //     $('#login_form').submit();
+  //
+  $.fn.submit = function (callback) {
+    if (callback) this.bind('submit', callback)
+    else if (this.length) {
+      var event = $.Event('submit');
+      this.eq(0).trigger(event);
+      if (!event.defaultPrevented) this.get(0).submit()
+    }
+    return this;
+  }
+
+})(Zepto);
+//     Zepto.js
+//     (c) 2010, 2011 Thomas Fuchs
+//     Zepto.js may be freely distributed under the MIT license.
+
+(function($){
+  var touch = {}, touchTimeout;
+
+  function parentIfText(node){
+    return 'tagName' in node ? node : node.parentNode;
+  }
+
+  function swipeDirection(x1, x2, y1, y2){
+    var xDelta = Math.abs(x1 - x2), yDelta = Math.abs(y1 - y2);
+    if (xDelta >= yDelta) {
+      return (x1 - x2 > 0 ? 'Left' : 'Right');
+    } else {
+      return (y1 - y2 > 0 ? 'Up' : 'Down');
+    }
+  }
+
+  var longTapDelay = 750;
+  function longTap(){
+    if (touch.last && (Date.now() - touch.last >= longTapDelay)) {
+      $(touch.target).trigger('longTap');
+      touch = {};
+    }
+  }
+
+  $(document).ready(function(){
+    $(document.body).bind('touchstart', function(e){
+      var now = Date.now(), delta = now - (touch.last || now);
+      touch.target = parentIfText(e.touches[0].target);
+      touchTimeout && clearTimeout(touchTimeout);
+      touch.x1 = e.touches[0].pageX;
+      touch.y1 = e.touches[0].pageY;
+      if (delta > 0 && delta <= 250) touch.isDoubleTap = true;
+      touch.last = now;
+      setTimeout(longTap, longTapDelay);
+    }).bind('touchmove', function(e){
+      touch.x2 = e.touches[0].pageX;
+      touch.y2 = e.touches[0].pageY;
+    }).bind('touchend', function(e){
+      if (touch.isDoubleTap) {
+        $(touch.target).trigger('doubleTap');
+        touch = {};
+      } else if (touch.x2 > 0 || touch.y2 > 0) {
+        (Math.abs(touch.x1 - touch.x2) > 30 || Math.abs(touch.y1 - touch.y2) > 30)  &&
+          $(touch.target).trigger('swipe') &&
+          $(touch.target).trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)));
+        touch.x1 = touch.x2 = touch.y1 = touch.y2 = touch.last = 0;
+      } else if ('last' in touch) {
+        touchTimeout = setTimeout(function(){
+          touchTimeout = null;
+          $(touch.target).trigger('tap')
+          touch = {};
+        }, 250);
+      }
+    }).bind('touchcancel', function(){ touch = {} });
+  });
+
+  ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'longTap'].forEach(function(m){
+    $.fn[m] = function(callback){ return this.bind(m, callback) }
+  });
+})(Zepto);

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
js/libraries/jquery.history.js


Datei-Diff unterdrückt, da er zu groß ist
+ 4 - 0
js/materio_search_api_ajax-ck.js


+ 206 - 0
js/materio_search_api_ajax.js

@@ -0,0 +1,206 @@
+// @codekit-prepend "gui.js"
+
+(function($) {
+
+MaterioBaseMod = function(){
+
+  var _History = window.History,
+      _isloadingresults = false;
+      _$content = $('#content'); 
+      // TODO:  define $content by module settings  
+  /**
+  * init()
+  */
+  function init(){
+    trace('init module');
+
+    // $('#root').bind('resultsupdated', function(event) {
+    //   alert('HO');
+    // });
+    $(window).trigger('resultsupdated');
+    initSearchAjax();
+    initHistoryNav();
+    initViewMode();
+  };
+
+  /**
+  * searchAjax
+  */
+  function initSearchAjax(){
+    // trace('initSearchAjax');
+    $('#edit-searchfield').focus();
+
+
+    $('#materio-search-api-search-form').bind('submit', function(event) {
+      // trace('search submited', event);
+      var $this = $(this);
+      setTimeout(function(){
+        // trace('submit load');
+        loadResults(getSearchKeys());
+      },10);
+      return false;
+    });  
+
+
+    // /!\ AUTOCOMPLETE SELECT EVENT need a patch http://drupal.org/node/365241#comment-5374686
+    $("#edit-searchfield").bind('autocompleteSelect', function(event) {
+      // trace('autocompleteSelect - event', event);
+      // trace('val', $(this).val());
+      // var $form = $(this).parents('form');
+      // setTimeout(function(){
+        // $form.submit();        
+      // }, 10);
+      loadResults($(this).val());
+    });
+
+    
+    _$content
+      .bind('jsp-initialised', function(event, isScrollable){
+        // trace('isScrollable = '+isScrollable);
+        // TODO:  better to check scroll-y than isscrollable, load next page before the end of scroll  
+        if(!isScrollable)
+          loadNextResultsPage();
+          // TODO:  what happend when there is no more page
+      })
+      .bind('jsp-scroll-y', function(event, scrollPositionY, isAtTop, isAtBottom){
+          if(isAtBottom)
+            loadNextResultsPage();
+      });
+
+  };
+  
+  function loadResults(keys){
+    // trace('keys', keys);
+    if(keys !== undefined){
+      keys = keys.replace('/', ' ');
+      if(!_isloadingresults){
+        _isloadingresults = true;
+        // TODO:  record ajax path in a variable from materio_search_api_ajax_init
+        $.getJSON('/materio_search_api_ajax/search/'+keys, function(json){
+            // trace('json', json);
+            $(window).trigger('resultsloaded');      
+            _isloadingresults = false;
+            updateContent(json);
+        });
+      }
+    }
+    
+  };
+
+  function updateContent(json){
+    if(json.return){
+      // TODO:  set jscrollpane to top  
+
+      $('.inner-content',_$content).html(json.return);
+
+      // TODO:  this a DIRTY sheet of what could be refresh results transitions  
+      // var $newContent = $(json.return);
+      // trace('newContent', $newContent);
+      // $('.node-materiau',$newContent).hide();
+      // $('.node-materiau','#content').fadeOut(300);
+      // setTimeout(function(){
+      //   $('#content').html($newContent);
+      //   $('.node-materiau','#content').fadeIn(500);
+      // },300);
+          
+      
+      var event = jQuery.Event('resultsupdated');
+      event.container = $('.search-results', _$content);
+      $(window).trigger(event);
+
+      _History.pushState({content:json.return}, json.keys, '/'+json.search_path + '/' +json.keys);
+      // navigate({content:json.return}, '', '/'+json.page.path + '/' +json.keys);
+
+
+    }else{
+      alert('no results');
+    }
+  };
+
+  function getSearchKeys(){
+    return $('#materio-search-api-search-form').find('input[name*="searchfield"]').val();
+  };
+
+  /** 
+  * infinit scroll
+  */
+  function loadNextResultsPage(){
+    var $nextpage = $('ul.pager .pager-current', _$content).next(),
+        href = $('a', $nextpage).attr('href');
+    
+    if(href){
+      var keys = href.match(/explore\/([^\/|\?]+)/);
+      var page = href.match(/\?page=([0-9]+)/);
+
+      if(!_isloadingresults){
+        _isloadingresults = true;
+        $.getJSON('/materio_search_api_ajax/search/'+keys[1]+'/'+page[1], function(json){
+          // trace('json', json);
+          _isloadingresults = false;
+          addNextpage(json);
+        });
+      }
+    }
+
+
+  };
+
+  function addNextpage(json){
+    var $newcontent = $(json.return),
+        $newresults = $('.search-results article', $newcontent),
+        $newpager = $('ul.pager', $newcontent);
+    
+    $('.search-results', _$content).append($newresults);
+    $('ul.pager', _$content).replaceWith($newpager);
+
+    var event = jQuery.Event('resultscompleted');
+    event.container = $('.search-results', _$content);
+    $(window).trigger(event);
+  };
+
+  /**
+  * history navigation
+  */
+  function initHistoryNav(){
+    var state = _History.getState();
+    _History.log('initial:', state.data, state.title, state.url);    
+
+    _History.Adapter.bind(window,'statechange',function(){ 
+      var state = _History.getState(); 
+      _History.log('statechange:', state.data, state.title, state.url);
+      // TODO:  History : empty content if we go back to the homepage  
+      $('.inner-content',_$content).html(state.data.content);
+    });
+
+  };
+
+  /**
+  * viewmode
+  */
+  function initViewMode(){
+    $('.viewmode-link').click(function(event){
+      changeViewMode($(this).attr('rel'), $(this));
+    });
+  };
+
+  function changeViewMode(vm, $btn){
+    $.getJSON('/materiosearchapi/viewmode/change/'+vm, function(json){
+      // trace('viewmode json', json);
+      if (json.statut == "saved"){
+        loadResults(getSearchKeys());
+        $('.viewmode-link').removeClass('active');
+        $btn.addClass('active');
+      }
+        
+    });
+  };
+
+
+  init();
+
+
+};
+
+  var materiobasemod = new MaterioBaseMod();
+  
+})(jQuery);

+ 49 - 0
materio_search_api.admin.inc

@@ -0,0 +1,49 @@
+<?php
+
+function materio_search_api_settings(){
+	$indexes = search_api_index_load_multiple(false);
+	// dsm($indexes, '$indexes');
+
+	foreach ($indexes as $machine_name => $index) {
+		if($index->status == 1)
+			$index_options[$index->machine_name] = $index->name;
+	}		
+
+	$form['mainsearchindex'] = array(
+		'#type'=>'select',
+		'#options'=>$index_options,
+		'#default_value' => variable_get('mainsearchindex', -1),
+		'#title' => t('Main search api index'),
+	);
+
+	$form['autocompletesearchindex'] = array(
+		'#type'=>'select',
+		'#options'=>$index_options,
+		'#default_value' => variable_get('autocompletesearchindex', -1),
+		'#title' => t('Autocomplete search api index'),
+	);
+
+	// TODO:  select the activated viewmodes for change view mode and selected view mode  
+	$entity_infos = entity_get_info();
+  // dsm($entity_infos, 'entity_infos');
+  foreach ($entity_infos['node']['view modes'] as $viewmode => $value) {
+  	$viewmode_options[$viewmode] = $value['label'];
+  }
+
+	$form['availableviewmodes'] = array(
+		'#type'=>'select',
+		'#options'=>$viewmode_options,
+		'#default_value' => variable_get('availableviewmodes', -1),
+		'#title' => t('Availble View modes'),
+		'#multiple' => true,
+	);
+
+	$form['defaultviewmode'] = array(
+		'#type'=>'select',
+		'#options'=>$viewmode_options,
+		'#default_value' => variable_get('defaultviewmode', 'full'),
+		'#title' => t('Defalut View mode'),
+	);
+
+	return system_settings_form($form);
+}

+ 10 - 8
materiobasemod.info → materio_search_api.info

@@ -1,5 +1,5 @@
-name = materiobasemod
-description = "Materio base module"
+name = materio_search_api
+description = "Materio Search Api module"
 
 ; Core version (required)
 core = 7.x
@@ -11,17 +11,19 @@ package = Materio
 ; php = 5.2
 
 ; Loadable code files
-;files[] = materiobasemod.theme.inc
-;files[] = materiobasemod.forms.inc
-files[] = materiobasemod.pages.inc
-files[] = materiobasemod.module
+;files[] = materio_search_api.theme.inc
+;files[] = materio_search_api.forms.inc
+files[] = materio_search_api.pages.inc
+files[] = materio_search_api.admin.inc
+files[] = materio_search_api.module
 
 ; Module dependencies
-dependencies[] = taxonomy
+;dependencies[] = taxonomy
+dependencies[] = search_api
+dependencies[] = search_api_solr
 
 ; Configuration page
 ; configure = admin/config/materiobasemod
 
-
 ; For further information about configuration options, see
 ; - http://drupal.org/node/542202

+ 362 - 0
materio_search_api.module

@@ -0,0 +1,362 @@
+<?php
+/**
+ * @file
+ * This is the file description for Materiobasemod module.
+ *
+ * In this more verbose, multi-line description, you can specify what this
+ * file does exactly. Make sure to wrap your documentation in column 78 so
+ * that the file can be displayed nicely in default-sized consoles.
+ */
+// define(MATERIO_SEARCH_API_RESULTS_PATH, 'explore');
+
+/**
+ * Implements hook_permission().
+ */
+function materio_search_api_permission() {
+  return array(
+    'use materio search api' =>  array(
+      'title' => t('Use materio search api'),
+      'description' => t('Use materio search api.'),
+    ),
+    'use materio search api autocomplete' =>  array(
+      'title' => t('Use materio search api autocomplete'),
+      'description' => t('Use materio search api autocomplete.'),
+    ),
+    'use materio search api viewmode selection' =>  array(
+      'title' => t('Use materio search api viewmode selection'),
+      'description' => t('Use materio search api viewmode selection.'),
+    ),
+    'administer materio_search_api' => array(
+      'title' => t('administer Materio search api'),
+      'description' => t('Administer materio search api.'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function materio_search_api_menu() {
+  $items = array();
+
+  $base = array(
+    'type' => MENU_CALLBACK,
+    'file' => 'materio_search_api.pages.inc',
+  );
+
+  $items['admin/config/search/search_api/materiosearchapi'] = array(
+    'title' => 'Materio Search Api',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('materio_search_api_settings'),
+    'access arguments' => array('administer materio_search_api'),
+    'file' => 'materio_search_api.admin.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+
+  $items['materiosearchapi/autocomplete/dbselect'] = $base+array(
+    'access arguments' => array('use materio search api autocomplete'),
+    'page callback' => 'materio_search_api_autocomplete_dbselect',
+  );
+
+  $items['materiosearchapi/autocomplete/searchapi'] = $base+array(
+    'access arguments' => array('use materio search api autocomplete'),
+    'page callback' => 'materio_search_api_autocomplete_searchapi',
+  );
+
+  $items['explore'] = $base+array(
+    'access arguments' => array('use materio search api'),
+    'page callback' => 'materio_search_api_results_search',
+    //'page argument' => array(1,2,3),
+  );
+
+  $items['materiosearchapi/viewmode/change'] = $base+array(
+    'access arguments' => array('use materio search api'),
+    'page callback' => 'materio_search_api_viewmode_change',
+    'page argument' => array(3),
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function materio_search_api_block_info() {
+  // This example comes from node.module.
+  $blocks['materio_search_api_search'] = array(
+    'info' => t('Materio search api search'),
+    'cache' => DRUPAL_NO_CACHE
+  );
+
+  $blocks['materio_search_api_viewmode'] = array(
+    'info' => t('Materio search api view mode selection'),
+    'cache' => DRUPAL_NO_CACHE
+  );
+
+  return $blocks;
+}
+
+/**
+ * Implements hook_block_view().
+ */
+function materio_search_api_block_view($delta = '') {
+  // This example comes from node.module. Note that you can also return a
+  // renderable array rather than rendered HTML for 'content'.
+  $block = array();
+
+  switch ($delta) {
+    case 'materio_search_api_search':
+      if (user_access('use materio search api')) {
+        $block['subject'] = t('Search');
+        $block['content'] = theme('materio_search_api_search_block', array());
+      }
+      break;
+    case 'materio_search_api_viewmode':
+      if (user_access('use materio search api viewmode selection')) {
+        $block['subject'] = t('View mode');
+        $block['content'] = theme('materio_search_api_select_viewmode_block', array());
+      }
+      break;
+  }
+  return $block;
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function materio_search_api_entity_info_alter(&$entity_info) {
+  $entity_info['node']['view modes']['cardsmall'] = array(
+    'label' => t('Small cards'),
+    'custom settings' => TRUE,
+  );
+  
+  $entity_info['node']['view modes']['cardmedium'] = array(
+    'label' => t('Medium cards'),
+    'custom settings' => TRUE,
+  );
+  
+  $entity_info['node']['view modes']['cardbig'] = array(
+    'label' => t('Big cards'),
+    'custom settings' => TRUE,
+  );
+
+  $entity_info['node']['view modes']['cardfull'] = array(
+    'label' => t('Full cards'),
+    'custom settings' => TRUE,
+  );
+}
+
+/**
+* Implements hook_preprocess_node().
+*/
+function materio_search_api_preprocess_node(&$vars) {
+  // $vars['theme_hook_suggestions'][] = 'node__'.$vars['view_mode'];
+  // $vars['theme_hook_suggestions'][] = 'node__' . $vars['type'] . '__' . $vars['view_mode'];
+  // dsm($vars, '$vars');
+}
+
+/**
+* Implements hook_preprocess_field().
+*/
+function materio_search_api_preprocess_field(&$vars) {
+  //$vars['theme_hook_suggestions'][] = 'field__' . $vars['element']['#view_mode'];
+  //$vars['theme_hook_suggestions'][] = 'field__' . $vars['element']['#field_type'] . '__' . $vars['element']['#view_mode'];
+  //$vars['theme_hook_suggestions'][] = 'field__' . $vars['element']['#field_name'] . '__' . $vars['element']['#view_mode'];
+  // dsm($vars, '$vars');
+}
+
+/**
+ * materiobase_search_form()
+ */
+function materio_search_api_search_form($form, &$form_state){
+  $form = array();
+
+  $args = arg();
+  $path = array_shift($args);
+  $keys = implode('/', $args); 
+
+  $form['searchfield'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $path == 'explore' ? $keys : "", // TODO:  set the search page path global or a variable in settings  
+    // '#value' => $keys,
+    '#autocomplete_path' => 'materiosearchapi/autocomplete/searchapi',
+    //'#autocomplete_path' => 'materiosearchapi/autocomplete/dbselect',
+    '#size' => 30,
+    '#maxlength' => 1024,
+  );
+
+  $form['create'] = array(
+    '#type' => 'image_button',
+    '#src' => drupal_get_path('module', 'materio_search_api') . '/images/search.png',
+    '#value' => t('Search'),
+  );
+  return $form;
+}
+
+function materio_search_api_search_form_validate($form, &$form_state){
+  // dsm($form, '$form');
+  // dsm($form_state, '$form_state');
+  // dsm(strlen($form_state['values']['searchfield']));
+  if (strlen(trim($form_state['values']['searchfield'])) <= 1) {
+    form_set_error('searchfield', 'Please enter at least 2 characters keyword.');
+  }
+}
+
+function materio_search_api_search_form_submit($form, &$form_state){
+  $keys = $form_state['values']['searchfield'];
+  // drupal_go_to('base/explore/'.$keys);
+  $form_state['redirect'] = 'explore/'.$keys;
+}
+
+
+/**
+ * Implements hook_form_alter().
+ *
+ * Adds language fields to user forms.
+ */
+// TODO:  user can choose preferred view mode  
+// function materio_search_api_form_alter(&$form, &$form_state, $form_id) {
+//   if (user_access('use materio search api viewmode selection')) {
+//     if ($form_id == 'user_register_form' || ($form_id == 'user_profile_form' && $form['#user_category'] == 'account')) {
+//       materio_search_api_viewmode_selector_form($form, $form_state, $form['#user']);
+//     }
+//   }
+// }
+
+// function materio_search_api_viewmode_selector_form(&$form, &$form_state, $user) {
+
+// }
+
+/**
+* - - - - - - - - - - - -  THEME  - - - - - - - - - - - - 
+*/
+
+/**
+ * Implements hook_theme().
+ */
+function materio_search_api_theme($existing, $type, $theme, $path) {
+  return array(
+    'materio_search_api_search_block' => array(
+      'arguments' => array(),
+      'template' => 'materio-search-api-search-block',
+      'path' => drupal_get_path('module', 'materio_search_api').'/templates',
+    ),
+    'materio_search_api_select_viewmode_block' => array(
+      'arguments' => array(),
+      'template' => 'materio-search-api-select-viewmode-block',
+      'path' => drupal_get_path('module', 'materio_search_api').'/templates',
+    ),
+    'materio_search_api_results' => array(
+      'arguments' => array(), 
+      'template' => 'materio-search-api-results',
+      'path' => drupal_get_path('module', 'materio_search_api').'/templates',
+      'variables' => array(
+        'index' => NULL,
+        'results' => array('result count' => 0),
+        'items' => array(),
+        'view_mode' => 'teaser',
+        'keys' => '',
+        // 'page_machine_name' => NULL,
+        'spellcheck' => NULL,
+        'pager' => NULL,
+      ),
+    ),
+  );
+}
+
+/**
+ * template_preprocess_materiobase_search_block();
+ */
+function template_preprocess_materio_search_api_search_block(&$vars){
+  // dsm($vars, '$vars');
+  $vars['searchform'] = drupal_get_form("materio_search_api_search_form");
+}
+
+function template_preprocess_materio_search_api_select_viewmode_block(&$vars){
+  global $user;
+
+  $active = isset($user->data['materiosearchapi_viewmode']) ? $user->data['materiosearchapi_viewmode'] : variable_get('defaultviewmode', 'full');
+
+  $availableviewmodes = variable_get('availableviewmodes', -1);
+  // dsm($availableviewmodes);
+
+  $entity_infos = entity_get_info();
+  // dsm($entity_infos, 'entity_infos');
+  
+  $content = '<div class="btn-group btn-group-vertical">';
+  foreach ($entity_infos['node']['view modes'] as $viewmode => $value) {
+    if(in_array($viewmode, $availableviewmodes)){
+      $content .= '<div class="viewmode-link viewmode-'.$viewmode.($active == $viewmode ? " active" : '').'" rel="'.$viewmode.'"><span class="inner">'.$value['label'].'</span></div>';
+    }
+  }
+  $content .= '</div>';
+  $vars['content'] = $content;
+}
+
+/**
+ * Function for preprocessing the variables for the search_api_page_results
+ * template.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - $index: The index this search was executed on.
+ *   - $results: An array of search results, as returned by
+ *     SearchApiQueryInterface::execute().
+ *   - $keys: The keywords of the executed search.
+ *
+ * @see materio-search-api-results.tpl.php
+ */
+function template_preprocess_materio_search_api_results(array &$variables) {
+  // dsm($variables, '$variables');
+  $results = $variables['results'];
+  $keys = $variables['keys'];
+
+  $variables['items'] = $variables['index']->loadItems(array_keys($variables['results']['results']));
+  $variables['result_count'] = $results['result count'];
+  $variables['sec'] = round($results['performance']['complete'], 3);
+  $variables['search_performance'] = array(
+      '#theme' => 'search_performance',
+      '#markup' => format_plural(
+        $results['result count'],
+        'The search found 1 result in @sec seconds.',
+        'The search found @count results in @sec seconds.',
+        array('@sec' => $variables['sec'])
+      ),
+      '#prefix' => '<p class="search-performance">',
+      '#suffix' => '</p>',
+  );
+
+  // $variables['search_results'] = array(
+  //   '#theme' => 'search_results_list',
+  //   'results' => $variables['results']['results'],
+  //   'items' => $variables['items'],
+  //   'index' => $variables['index'],
+  //   'type' => 'ul',
+  // );
+
+  // dsm($variables, 'variables');
+}
+
+
+/**
+* - - - - - - - - - - - -  SEARCH API PAGE  - - - - - - - - - - - - 
+*/
+
+/**
+ * Implements hook_block_view_alter().
+ */
+function materio_search_api_block_view_alter(&$data, $block) {
+  // this alter prepopulate the search form provide by search_api_page
+  if ($block->module == 'search_api_page') {
+    $page = search_api_page_load($block->delta);
+    $item = menu_get_item();
+    
+    if (isset($page->path) && $page->path == $item['path']) {
+      $keys = arg(count(arg(NULL, $page->path)));
+      if ($keys) {
+        $data['content']['keys_' . $page->id]['#default_value'] = $keys;
+        $data['content']['keys_' . $page->id]['#value'] = $keys;
+      }
+    }
+  }
+}

+ 252 - 0
materio_search_api.pages.inc

@@ -0,0 +1,252 @@
+<?php
+
+/**
+ * materiobase_search_autocomplete_dbselect()
+ * 
+ * inspired by taxonomy_autocomplete()
+ * 
+ * OBSOLETE : this fonction use a direct dbselect request to provide results forautocomplete
+ * 
+ */
+function materio_search_api_autocomplete_dbselect($typed = ''){
+  // If the request has a '/' in the search text, then the menu system will have
+  // split it into multiple arguments, recover the intended $tags_typed.
+  $args = func_get_args();
+  $typed = implode('/', $args);
+  
+  /*
+    TODO riche serach engine + \\ etc gmail like
+  */
+  
+  if ($typed != '') {
+
+    // Part of the criteria for the query come from the field's own settings.
+    $vids = array();
+    $vocabularies = taxonomy_vocabulary_get_names();
+    foreach ($vocabularies as $voc) {
+      $vids[] = $voc->vid;
+    }
+
+    $query = db_select('taxonomy_term_data', 't');
+    $query->addTag('translatable');
+    $query->addTag('term_access');
+
+    // Select rows that match by term name.
+    $tags_return = $query
+      ->fields('t', array('tid', 'name'))
+      ->condition('t.vid', $vids)
+      ->condition('t.name', '%' . db_like($typed) . '%', 'LIKE')
+      ->range(0, 10)
+      ->execute()
+      ->fetchAllKeyed();
+
+    $term_matches = array();
+    foreach ($tags_return as $tid => $name) {
+      $n = $name;
+      // Term names containing commas or quotes must be wrapped in quotes.
+      // if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
+      //   $n = '"' . str_replace('"', '""', $name) . '"';
+      // }
+      $term_matches[$n] = check_plain($name);
+    }
+  }
+
+  drupal_json_output($term_matches);
+  
+}
+
+/**
+* materio_search_api_autocomplete_searchapi($typed = '')
+* 
+* GOOD one using searchapi (SOLR)
+*/
+function materio_search_api_autocomplete_searchapi($typed = ''){
+  // If the request has a '/' in the search text, then the menu system will have
+  // split it into multiple arguments, recover the intended $tags_typed.
+  $args = func_get_args();
+  $typed = implode('/', $args);
+  
+  // dsm($typed, 'typed');
+  if ($typed != '') {
+
+    // search for patterns like key -another key +lastkey
+    // and provide auto completion for the last key
+    preg_match_all('/\s?[\+|-]?[^\s]+/', $typed, $adv_search_q);
+    preg_match('/^(\+|-)?(.*)$/', trim(array_pop($adv_search_q[0])), $last);
+    $tosearch = isset($last[2]) ? $last[2] : $typed;
+
+    // build the query
+    $index_machine_name = variable_get('autocompletesearchindex', -1);
+    $query = search_api_query($index_machine_name);
+    $query_filter = $query->createFilter();
+    $query_filter->condition('name', $tosearch);
+    // $query_filter->condition('type', 'article');
+    $query->filter($query_filter);
+    $tags_return = $query->execute();
+    // dsm($tags_return, '$tags_return');
+
+    if($tags_return['result count']){
+      $term_matches = array();
+      $index = search_api_index_load($index_machine_name);
+      $delta = 0;
+      foreach ($index->loadItems(array_keys($tags_return['results'])) as $item) {
+        // dsm($item, '$item');
+        //$term_matches[$item->tid] = check_plain($item->name);
+        // $term_matches[check_plain($item->name)] = check_plain($item->name);
+        // TODO:  leave tags with nodes  
+        $term_matches[ trim(implode(' ', $adv_search_q[0]).' '.$last[1].$item->name)] = check_plain($item->name);
+
+        $delta++;
+        if($delta > 7)
+          break;
+      }  
+      drupal_json_output($term_matches);  
+    }else{
+      drupal_json_output(array());
+    }
+  }else{
+    return;
+  }
+
+  
+  // dsm($term_matches, 'term_matches');
+  // return 'debug mode of materio_search_api_autocomplete_searchapi';
+  
+}
+
+/**
+* materio_search_api_results_search()
+* 
+* 
+*/
+function materio_search_api_results_search(){//, $limit = 20, $page = 0
+  global $user;
+
+  //dsm("materio_search_api_results_search");
+  $args = func_get_args();
+  $typed = implode('/', $args);
+  
+  preg_match_all('/\?page=([0-9]+)/', $typed, $pages);
+  //dsm($pages, '$pages');
+  if($pages[0]){
+    $typed = str_replace($pages[0][0], '', $typed);
+    // $page = $pages[1][0];
+  }
+  // else{
+  //   $page = 0;
+  // }
+  
+
+  preg_match_all('/\s?[^\s]+\s?/', $typed, $words);
+  // dsm($words, "words");
+
+  // $escaper = array("+", "-", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", '"', "~", "*", "?", ":", '\\');
+  foreach ($words[0] as $word) {
+
+    // $word = preg_replace('/\b-/', '\-', trim($word));
+    // dsm($word);
+
+    $keys[] = $word;
+  }
+
+  if ($keys) {
+    try {
+      $limit = 15;
+      $offset = pager_find_page() * $limit; //$page*$limit;//
+
+      $index_machine_name = variable_get('mainsearchindex', -1);
+      $query = search_api_query($index_machine_name, array('parse mode'=>'direct'))
+        ->keys(implode(' ', $keys))
+        ->range($offset, $limit);
+
+      $results = $query->execute();
+    }
+    catch (SearchApiException $e) {
+      $ret['message'] = t('An error occurred while executing the search. Please try again or contact the site administrator if the problem persists.');
+      watchdog('materiobasemod', 'An error occurred while executing a search: !msg.', array('!msg' => $e->getMessage()), WATCHDOG_ERROR, l(t('search page'), $_GET['q']));
+    }
+
+    // dsm($results, 'results');
+
+    # Load spellcheck.
+    // if (isset($results['search_api_spellcheck'])) {
+    //   $ret['results']['#spellcheck'] = array(
+    //     '#theme' => 'search_api_spellcheck',
+    //     '#spellcheck' => $results['search_api_spellcheck'],
+    //   // Let the theme function know where the key is stored by passing its arg
+    //   // number. We can work this out from the number of args in the page path.
+    //     '#options' => array(
+    //       'arg' => array(count(arg(NULL, $page->path))),
+    //     ),
+    //     '#prefix' => '<p class="search-api-spellcheck suggestion">',
+    //     '#suffix' => '</p>',
+    //   );
+
+    // }
+
+
+    $ret['results']['#theme'] = 'materio_search_api_results';
+    /*
+      TODO choose index in module settings
+    */  
+    $ret['results']['#index'] = search_api_index_load('materiaux_breves');
+    $ret['results']['#results'] = $results;
+    /*
+      TODO choose view mode from user data
+    */ 
+
+    $ret['results']['#view_mode'] = isset($user->data['materiosearchapi_viewmode']) ? $user->data['materiosearchapi_viewmode'] : variable_get('defaultviewmode', 'full');
+    $ret['results']['#keys'] = $keys;
+
+
+    // Load pager.
+    // if ($results['result count'] > $page->options['per_page']) {
+      /*
+      TODO set per page limit as module settings
+      */
+      pager_default_initialize($results['result count'], $limit);
+      $ret['results']['#pager'] = theme('pager');
+    // }
+
+    if (!empty($results['ignored'])) {
+      drupal_set_message(
+        t('The following search keys are too short or too common and were therefore ignored: "@list".', 
+          array( '@list' => implode(t('", "'), $results['ignored']) ) ),
+       'warning'
+      );
+    }
+    if (!empty($results['warnings'])) {
+      foreach ($results['warnings'] as $warning) {
+        drupal_set_message($warning, 'warning');
+      }
+    }
+
+  }
+
+  return $ret;
+}
+
+
+
+function materio_search_api_viewmode_change($vm){
+  // dsm($vm);
+
+  global $user;
+  // dsm($user, 'user');
+
+  $entity_infos = entity_get_info();
+  // dsm($entity_infos, 'entity_infos');
+
+  if (in_array($vm, variable_get('availableviewmodes', array()))) {
+    user_save($user, array("data"=>array('materiosearchapi_viewmode' => $vm)));
+    $rep = array('statut'=>'saved');
+  }else{
+    $rep = array('statut'=>'viewmode not allowed');
+  }
+
+  //return 'debug mode for materio_search_api_viewmode_change';
+  drupal_json_output($rep);
+}
+
+
+

+ 6 - 6
materiobaseajax.info → materio_search_api_ajax.info

@@ -1,4 +1,4 @@
-name = Materiobaseajax
+name = Materio Search API Ajax
 description = "The description of this module"
 
 ; Core version (required)
@@ -11,18 +11,18 @@ package = Materio
 ; php = 5.2
 
 ; Loadable code files
-  files[] = materiobaseajax.module
-; files[] = materiobaseajax.admin.inc
-; files[] = materiobaseajax.class.inc
+  files[] = materio_search_api_ajax.module
+; files[] = materio_search_api_ajax.admin.inc
+; files[] = materio_search_api_ajax.class.inc
 
 ; Module dependencies
-; dependencies[] = mymodule
+; dependencies[] = materio_search_api
 ; dependencies[] = theirmodule (1.2)
 ; dependencies[] = anothermodule (>=2.4)
 ; dependencies[] = views (3.x)
 
 ; Configuration page
-; configure = admin/config/materiobaseajax
+; configure = admin/config/materio_search_api_ajax
 
 
 ; For further information about configuration options, see

+ 37 - 0
materio_search_api_ajax.module

@@ -0,0 +1,37 @@
+<?php
+/**
+ * @file
+ * This is the file description for materio_search_api_ajax module.
+ *
+ * In this more verbose, multi-line description, you can specify what this
+ * file does exactly. Make sure to wrap your documentation in column 78 so
+ * that the file can be displayed nicely in default-sized consoles.
+ */
+
+/**
+ * Implements hook_init().
+ */
+function materio_search_api_ajax_init() {
+  drupal_add_js(drupal_get_path('module', 'materio_search_api_ajax').'/js/libraries/jquery.history.js');
+  drupal_add_js(drupal_get_path('module', 'materio_search_api_ajax').'/js/materio_search_api_ajax-ck.js');
+}
+
+/**
+ * Implements hook_menu().
+ */
+function materio_search_api_ajax_menu() {
+  $items = array();
+
+  $items['materio_search_api_ajax/search/%'] = array(
+    'title' => 'Matrio base ajax',
+    'page callback' => 'materio_search_api_ajax_search',
+    'page arguments' => array(2,3),
+    'access callback' => TRUE,
+    'access arguments' => array('use materio search api'),
+    'file' => 'materio_search_api_ajax.pages.inc',
+    'type' => MENU_CALLBACK,
+  );
+
+
+  return $items;
+}

+ 60 - 0
materio_search_api_ajax.pages.inc

@@ -0,0 +1,60 @@
+<?php
+
+
+function materio_search_api_ajax_search($keys, $page = 0){
+  // TODO:  set research path configurable  
+  $debug = false;
+  $search_path = "explore";
+  
+  $_GET['page'] = $page;
+
+  $path = $search_path . '/' . $keys ;//. ($page ? '?page='.$page : '');
+  // dsm($menuhandler, 'menuhandler');
+
+  // check if request is ajax, if not rediret to search_api_page page with right keys
+  if (!$debug && (!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')) {
+    drupal_goto($path, array(), 301);
+    exit ;
+  }
+
+  // get results
+  menu_set_active_item($path);
+  $return = menu_execute_active_handler($path, FALSE);
+
+  //dsm($return, '$return');
+  
+  if (is_int($return)) {
+    switch ($return) {
+      case MENU_NOT_FOUND :
+        drupal_add_http_header('Status', '404 Not Found');
+        break;
+      case MENU_ACCESS_DENIED :
+        drupal_add_http_header('Status', '403 Forbidden');
+        break;
+      case MENU_SITE_OFFLINE :
+        drupal_add_http_header('Status', '503 Service unavailable');
+        break;
+    }
+  } elseif (isset($return)) {
+
+    if (is_array($return)) {
+      $return = drupal_render($return);
+    }
+      
+    $rep = array(
+      // 'id'=>$id,
+      'keys'=>$keys,
+      'search_path'=>$search_path,
+      'return'=>$return,
+    );
+    
+    if ($debug) {
+      dsm($rep, 'rep');
+      return "debug display";
+    }else{
+      drupal_json_output($rep);  
+    }
+    
+  }
+
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 4
materiobaseajax-ck.js


+ 0 - 37
materiobaseajax.js

@@ -1,37 +0,0 @@
-// @codekit-prepend "gui.js"
-(function($) {
-
-    $('#search-api-page-search-form-grid').bind('submit', function(event) {
-      trace('search submited', event);
-      var keys = $(this).find('input[name*="keys"]').val(),
-          id = $(this).find('input[name="id"]').val();
-      trace('keys', keys);
-      trace('form_id', id);
-
-      $.getJSON('/materiobaseajax/search/'+id+'/'+keys,
-        // {form_id: "theform_id", keys: "thekeys"},
-        function(json){
-          trace('json', json);
-          if(json.return){
-            $('#content').html(json.return);
-          }
-      });
-      
-      return false;
-    });
-    
-    // select event need a patch http://drupal.org/node/365241#comment-5374686
-    
-    // $("input.form-text", '#search-api-page-search-form-grid').bind('autocomplete_select', function(event) {
-    //   trace('val', $(this).val());
-    //   var $form = $(this).parent('form');
-    //   // setTimeout(function(){
-    //   //   $form.submit();        
-    //   // }, 10);
-    // });
-
-    function navigate(){
-      
-    };
-  
-})(jQuery);

+ 0 - 74
materiobaseajax.module

@@ -1,74 +0,0 @@
-<?php
-/**
- * @file
- * This is the file description for Materiobaseajax module.
- *
- * In this more verbose, multi-line description, you can specify what this
- * file does exactly. Make sure to wrap your documentation in column 78 so
- * that the file can be displayed nicely in default-sized consoles.
- */
-
-/**
- * Implements hook_init().
- */
-function materiobaseajax_init() {
-  drupal_add_js(drupal_get_path('module', 'materiobaseajax').'/materiobaseajax-ck.js');
-}
-
-/**
- * Implements hook_menu().
- */
-function materiobaseajax_menu() {
-  $items = array();
-
-  $items['materiobaseajax/search'] = array(
-    'title' => 'Matrio base ajax',
-    'page callback' => 'materiobaseajax_search_ajax',
-    'page arguments' => array(2, 3),
-    // 'access callback' => TRUE,
-    'access arguments' => array('access search_api_page'),
-    'file' => 'materiobaseajax.pages.inc',
-    'type' => MENU_CALLBACK,
-  );
-
-  return $items;
-}
-
-
-
-/**
- * Implements hook_search_api_ajax_settings().
- */
-function materiobaseajax_search_api_ajax_settings() {
-  $settings = array(
-  
-    // CSS id for main content (search results html)
-    // format: content => CSS id
-    'content' => '#content',
-    
-    // CSS id's for regions containing search blocks
-    // check your region names in mytheme.info
-    // format: region_name => CSS id
-    'regions' => array(
-      'headerblock_left' => '#headerblock-left',
-      // 'sidebar_second' => '#sidebar-second',
-    ),
-    
-    // OPTIONAL: if you want to provide an AJAX spinner
-    // this paht is for a default spinner path provided with this module
-    // @note: see the search_api_ajax.css
-    'spinner' => drupal_get_path('module', 'search_api_ajax') .'/spinner.gif',
-    
-    // OPTIONAL: if you want to use scroll-to-top functionality when paging
-    // scroll target div
-    // 'scrolltarget' => '#main-content',
-    // 'scrollspeed' => 1000,
-    
-    // OPTIONAL: if you want to fade search results when Ajaxing
-    // please set to 1 for TRUE
-    'fade' => 1,
-    'opacity' => 0.3,    
-  );
-  
-  return $settings;
-}

+ 0 - 22
materiobaseajax.pages.inc

@@ -1,22 +0,0 @@
-<?php
-
-
-function materiobaseajax_search_ajax($id, $keys){
-  
-  $page = search_api_page_load($id);
-  
-  $return = menu_execute_active_handler($page->path . '/' . $keys, FALSE);
-  
-  if (is_array($return)) {
-    $return = drupal_render($return);
-  }
-  
-  $rep = array(
-    'id'=>$id,
-    'keys'=>$keys,
-    'page'=>$page,
-    'return'=>$return,
-  );
-  
-  return drupal_json_output($rep);
-}

+ 0 - 176
materiobasemod.module

@@ -1,176 +0,0 @@
-<?php
-/**
- * @file
- * This is the file description for Materiobasemod module.
- *
- * In this more verbose, multi-line description, you can specify what this
- * file does exactly. Make sure to wrap your documentation in column 78 so
- * that the file can be displayed nicely in default-sized consoles.
- */
-
-
-/**
- * Implements hook_permission().
- */
-function materiobasemod_permission() {
-  return array(
-    'use materiobase search' =>  array(
-      'title' => t('Materio base search'),
-      'description' => t('Use materiobasemod search.'),
-    ),
-  );
-}
-
-/**
- * Implements hook_menu().
- */
-function materiobasemod_menu() {
-  $items = array();
-  $items['materiobase/search/autocomplete/dbselect'] = array(
-    'title' => 'Autocomplete materiobase search',
-    'page callback' => 'materiobase_search_autocomplete_dbselect',
-    'access arguments' => array('access content'),
-    'type' => MENU_CALLBACK,
-    'file' => 'materiobasemod.pages.inc',
-  );
-  $items['materiobase/search/autocomplete/searchapi'] = array(
-    'title' => 'Autocomplete materiobase search',
-    'page callback' => 'materiobase_search_autocomplete_searchapi',
-    'access arguments' => array('access content'),
-    'type' => MENU_CALLBACK,
-    'file' => 'materiobasemod.pages.inc',
-  );
-  
-  return $items;
-}
-
-/**
- * Implements hook_block_info().
- */
-function materiobasemod_block_info() {
-  // This example comes from node.module.
-  $blocks['base_search'] = array(
-    'info' => t('Materio base search'),
-    'cache' => DRUPAL_NO_CACHE
-  );
-
-  return $blocks;
-}
-
-/**
- * Implements hook_theme().
- */
-function materiobasemod_theme($existing, $type, $theme, $path) {
-  return array(
-    'materiobase_search_block' => array(
-      'arguments' => array(),
-      'template' => 'materiobase-search-block',
-      'path' => drupal_get_path('module', 'materiobasemod').'/templates',
-    ),
-  );
-}
-
-/**
- * Implements hook_block_view().
- */
-function materiobasemod_block_view($delta = '') {
-  // This example comes from node.module. Note that you can also return a
-  // renderable array rather than rendered HTML for 'content'.
-  $block = array();
-
-  switch ($delta) {
-    case 'base_search':
-      if (user_access('use materiobase search')) {
-        $block['subject'] = t('Search');
-        $block['content'] = theme('materiobase_search_block', array());
-      }
-      break;
-  }
-  return $block;
-}
-
-/**
- * Implements hook_block_view_alter().
- */
-function materiobasemod_block_view_alter(&$data, $block) {
-  if ($block->module == 'search_api_page') {
-    $page = search_api_page_load($block->delta);
-    $item = menu_get_item();
-    
-    if (isset($page->path) && $page->path == $item['path']) {
-      $keys = arg(count(arg(NULL, $page->path)));
-      if ($keys) {
-        $data['content']['keys_' . $page->id]['#default_value'] = $keys;
-        $data['content']['keys_' . $page->id]['#value'] = $keys;
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_entity_info_alter().
- */
-function materiobasemod_entity_info_alter(&$entity_info) {
-  $entity_info['node']['view modes']['cardsmall'] = array(
-    'label' => t('Small card for the grid'),
-    'custom settings' => TRUE,
-  );
-  
-  $entity_info['node']['view modes']['cardmedium'] = array(
-    'label' => t('Medium card for the grid'),
-    'custom settings' => TRUE,
-  );
-  
-  $entity_info['node']['view modes']['cardbig'] = array(
-    'label' => t('Big card for the grid'),
-    'custom settings' => TRUE,
-  );
-}
-
-/**
-* Implements hook_preprocess_node().
-*/
-function materiobasemod_preprocess_node(&$vars) {
-  $vars['theme_hook_suggestions'][] = 'node__'.$vars['view_mode'];
-  $vars['theme_hook_suggestions'][] = 'node__' . $vars['type'] . '__' . $vars['view_mode'];
-  // dsm($vars, '$vars');
-}
-
-function materiobasemod_preprocess_field(&$vars) {
-  $vars['theme_hook_suggestions'][] = 'field__' . $vars['element']['#view_mode'];
-  $vars['theme_hook_suggestions'][] = 'field__' . $vars['element']['#field_type'] . '__' . $vars['element']['#view_mode'];
-  $vars['theme_hook_suggestions'][] = 'field__' . $vars['element']['#field_name'] . '__' . $vars['element']['#view_mode'];
-  // dsm($vars, '$vars');
-}
-
-
-/**
- * materiobase_search_form()
- */
-function materiobase_search_form(){
-  $form = array();
-  $form['searchfield'] = array(
-    '#type' => 'textfield',
-    '#default_value' => '',
-    // '#autocomplete_path' => 'materiobase/search/autocomplete/searchapi',
-    '#autocomplete_path' => 'materiobase/search/autocomplete/dbselect',
-    '#size' => 30,
-    '#maxlength' => 1024,
-  //  '#element_validate' => array('taxonomy_autocomplete_validate'),
-  );
-  $form['create'] = array(
-    '#type' => 'submit',
-    '#value' => t('Search'),
-  );
-  return $form;
-}
-
-/**
- * template_preprocess_materiobase_search_block();
- */
-function template_preprocess_materiobase_search_block(&$vars){
-  
-  $vars['searchform'] = drupal_get_form("materiobase_search_form");
-  
-  
-}

+ 0 - 100
materiobasemod.pages.inc

@@ -1,100 +0,0 @@
-<?php
-
-/**
- * materiobase_search_autocomplete()
- * 
- * inspired by taxonomy_autocomplete()
- * 
- */
-function materiobase_search_autocomplete_dbselect($typed = ''){
-  // If the request has a '/' in the search text, then the menu system will have
-  // split it into multiple arguments, recover the intended $tags_typed.
-  $args = func_get_args();
-  $typed = implode('/', $args);
-  
-  /*
-    TODO riche serach engine + \\ etc gmail like
-  */
-  
-  if ($typed != '') {
-
-    // Part of the criteria for the query come from the field's own settings.
-    $vids = array();
-    $vocabularies = taxonomy_vocabulary_get_names();
-    foreach ($vocabularies as $voc) {
-      $vids[] = $voc->vid;
-    }
-
-    $query = db_select('taxonomy_term_data', 't');
-    $query->addTag('translatable');
-    $query->addTag('term_access');
-
-    // Select rows that match by term name.
-    $tags_return = $query
-      ->fields('t', array('tid', 'name'))
-      ->condition('t.vid', $vids)
-      ->condition('t.name', '%' . db_like($typed) . '%', 'LIKE')
-      ->range(0, 10)
-      ->execute()
-      ->fetchAllKeyed();
-
-    $term_matches = array();
-    foreach ($tags_return as $tid => $name) {
-      $n = $name;
-      // Term names containing commas or quotes must be wrapped in quotes.
-      // if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
-      //   $n = '"' . str_replace('"', '""', $name) . '"';
-      // }
-      $term_matches[$n] = check_plain($name);
-    }
-  }
-
-  drupal_json_output($term_matches);
-  
-}
-
-function materiobase_search_autocomplete_searchapi($typed = ''){
-  // If the request has a '/' in the search text, then the menu system will have
-  // split it into multiple arguments, recover the intended $tags_typed.
-  $args = func_get_args();
-  $typed = implode('/', $args);
-  
-  /*
-    TODO riche serach engine + \\ etc gmail like
-  */
-  
-  if ($typed != '') {
-
-
-    $query = search_api_query('referencement');
-
-    $query_filter = $query->createFilter();
-    $query_filter->condition('name', $typed);
-    // $query_filter->condition('type', 'article');
-
-    $query->filter($query_filter);
-
-    $tags_return = $query->execute();
-    
-    dsm($tags_return, '$tags_return');
-
-    $term_matches = array();
-    $index = search_api_index_load('referencement');
-    foreach ($index->loadItems(array_keys($tags_return['results'])) as $item) {
-      dsm($item, '$item');
-    }
-    
-    // 
-    // foreach ($tags_return as $tid => $name) {
-    //   $n = $name;
-    //   // Term names containing commas or quotes must be wrapped in quotes.
-    //   // if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
-    //   //   $n = '"' . str_replace('"', '""', $name) . '"';
-    //   // }
-    //   $term_matches[$n] = check_plain($name);
-    // }
-  }
-
-  drupal_json_output($term_matches);
-  
-}

+ 57 - 0
templates/materio-search-api-results.tpl.php

@@ -0,0 +1,57 @@
+<?php
+/**
+ * @file
+ * Default theme implementation for displaying search results.
+ *
+ * This template collects each invocation of theme_search_result(). This and
+ * the child template are dependent to one another sharing the markup for
+ * definition lists.
+ *
+ * Note that modules and themes may implement their own search type and theme
+ * function completely bypassing this template.
+ *
+ * Available variables:
+ * - $result_count: Number of results.
+ * - $spellcheck: Possible spelling suggestions from Search spellcheck module.
+ * - $search_results: All results rendered as list items in a single HTML string.
+ * - $items: All results as it is rendered through search-result.tpl.php.
+ * - $search_performance: The number of results and how long the query took.
+ * - $sec: The number of seconds it took to run the query.
+ * - $pager: Row of control buttons for navigating between pages of results.
+ * - $index:
+ * - $keys: The keywords of the executed search.
+ * - $page_machine_name: Machine name of the current Search API Page.
+ *
+ * View Mode is set in the Search page settings. If you select
+ * "Themed as search results", then the child template will be used for
+ * theming the individual result. Any other view mode will bypass the
+ * child template.
+ *
+ * @see template_preprocess_search_api_page_results()
+ */
+
+?>
+<?php if (!empty($result_count)) : ?>
+  <div class="materiobase-results <?php print ' view-mode-' . $variables['view_mode'];?>">
+    <?php if ($result_count) : ?>
+      <?php //print render($search_performance); ?>
+      <?php //print render($spellcheck); ?>
+      <div class="search-results">
+        <?php $eview = entity_view($index->item_type, $items, $variables['view_mode']); ?>
+        <?php //dsm($eview, 'eview'); ?>
+        <?php print render($eview); ?>
+        <?php 
+        // foreach ($eview[$index->item_type] as $entity){
+        //   $renders[] = render($entity);
+        // }
+        // dsm($renders, 'renders');
+
+        // print implode('<!-- whitespace -->', $renders);
+        ?>
+      </div>
+      <?php print $pager; ?>
+    <?php else : ?>
+      <h2><?php print t('Your search yielded no results.');?></h2>
+    <?php endif; ?>
+  </div>
+<?php endif; ?>

+ 3 - 0
templates/materio-search-api-search-block.tpl.php

@@ -0,0 +1,3 @@
+<?php 
+
+print render($searchform);

+ 3 - 0
templates/materio-search-api-select-viewmode-block.tpl.php

@@ -0,0 +1,3 @@
+<?php	
+
+print $content;

+ 0 - 3
templates/materiobase-search-block.tpl.php

@@ -1,3 +0,0 @@
-<?php 
-print "hello world !";
-print render($searchform);

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.