plugins: added debug_platform, code_editor
authorJordan Augé <jordan.auge@lip6.fr>
Fri, 2 Aug 2013 08:09:33 +0000 (10:09 +0200)
committerJordan Augé <jordan.auge@lip6.fr>
Fri, 2 Aug 2013 08:09:33 +0000 (10:09 +0200)
18 files changed:
debug_platform/__init__.py [new file with mode: 0644]
debug_platform/urls.py [new file with mode: 0644]
debug_platform/views.py [new file with mode: 0644]
plugins/code_editor/Actions.js [new file with mode: 0644]
plugins/code_editor/EditorCM.js [new file with mode: 0644]
plugins/code_editor/LayoutCM.js [new file with mode: 0644]
plugins/code_editor/__init__.py [new file with mode: 0644]
plugins/code_editor/code_editor.html [new file with mode: 0644]
plugins/code_editor/code_editor.js [new file with mode: 0644]
plugins/code_editor/img/handle-h.png [new file with mode: 0644]
plugins/code_editor/img/handle-v.png [new file with mode: 0644]
plugins/code_editor/img/initializing.png [new file with mode: 0644]
plugins/code_editor/moo-clientcide-1.3.js [new file with mode: 0644]
plugins/debug_platform/__init__.py [new file with mode: 0644]
plugins/debug_platform/debug_platform.css [new file with mode: 0644]
plugins/debug_platform/debug_platform.html [new file with mode: 0644]
plugins/debug_platform/debug_platform.js [new file with mode: 0644]
plugins/tabs/tabs.js [new file with mode: 0644]

diff --git a/debug_platform/__init__.py b/debug_platform/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/debug_platform/urls.py b/debug_platform/urls.py
new file mode 100644 (file)
index 0000000..3e4398c
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+#
+# debug/urls.py: URL mappings for the debug application
+# This file is part of the Manifold project.
+#
+# Authors:
+#   Jordan Augé <jordan.auge@lip6.fr>
+# Copyright 2013, UPMC Sorbonne Universités / LIP6
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+# 
+# You should have received a copy of the GNU General Public License along with
+# this program; see the file COPYING.  If not, write to the Free Software
+# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from django.conf.urls import patterns, include, url
+from debug_platform.views      import PlatformView
+from portal.views import DashboardView
+
+urlpatterns = patterns('',
+    #(r'platform/(?P<platform_name>[\w\.]+)/?$', 'debug.platform'),
+    url(r'^platform/?$', PlatformView.as_view(), name='debugplatform'),
+    #url(r'^platform/(?P<platform_name>[\w\.]+)/?$', PlatformView.as_view(), name='debug_platform'),
+)
diff --git a/debug_platform/views.py b/debug_platform/views.py
new file mode 100644 (file)
index 0000000..f56661b
--- /dev/null
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+#
+# portal/views.py: views for the portal application
+# This file is part of the Manifold project.
+#
+# Authors:
+#   Jordan Augé <jordan.auge@lip6.fr>
+#   Mohammed Yasin Rahman <mohammed-yasin.rahman@lip6.fr>
+# Copyright 2013, UPMC Sorbonne Universités / LIP6
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+# 
+# You should have received a copy of the GNU General Public License along with
+# this program; see the file COPYING.  If not, write to the Free Software
+# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from django.conf                 import settings
+from django.contrib.sites.models import Site, RequestSite
+from django.contrib              import messages
+from django.views.generic        import View
+from django.views.generic.base   import TemplateView
+from django.shortcuts            import render
+from manifold.core.query         import Query
+from unfold.page                 import Page
+from myslice.viewutils           import topmenu_items, the_user
+from django.http                 import HttpResponseRedirect
+from plugins.debug_platform      import DebugPlatform
+
+class PlatformView(TemplateView):
+    template_name = "view-unfold1.html"
+
+    def get_context_data(self, **kwargs):
+
+        page = Page(self.request)
+
+        debug_platform = DebugPlatform(page = page)
+
+        context = super(PlatformView, self).get_context_data(**kwargs)
+
+        context['ALL_STATIC'] = "all_static"
+        context['unfold1_main'] = debug_platform.render(self.request)
+
+        # XXX This is repeated in all pages
+        # more general variables expected in the template
+        context['title'] = 'Test view that combines various plugins'
+        # the menu items on the top
+        context['topmenu_items'] = topmenu_items('Dashboard', self.request) 
+        # so we can sho who is logged
+        context['username'] = the_user(self.request) 
+
+        prelude_env = page.prelude_env()
+        context.update(prelude_env)
+
+        return context
+
diff --git a/plugins/code_editor/Actions.js b/plugins/code_editor/Actions.js
new file mode 100644 (file)
index 0000000..08ea0c1
--- /dev/null
@@ -0,0 +1,798 @@
+// TODO: refactor
+// TODO: This code is now legacy code, none of it will make its way into Beta
+window.editorsModified = false;
+
+Request.JSON.implement({
+  initialize: function(options) {
+    this.parent(options);
+    this.setHeader('X-CSRFToken', Cookie.read('csrftoken'));
+  }
+});
+
+Track = {
+  ui: function(action){
+    if (typeof(_gaq) !== 'undefined'){
+      _gaq.push(['_trackEvent', 'UI',  action || null]);
+    }
+  }
+};
+
+/*
+ * Define actions on the run/save/clean buttons
+ */
+var MooShellActions = new Class({
+  Implements: [Options, Events],
+  options: {
+    // onRun: $empty,
+    // onClean: $empty,
+    formId: 'show-result',
+    saveAndReloadId: 'update',
+    saveAsNewId: 'savenew',
+    runId: 'run',
+    draftId: 'mobile',
+    jslintId: 'jslint',
+    tidyId: 'tidy',
+    showJsId: 'showjscode',
+    shareSelector: '#share_link_dropdown, #share_embedded_dropdown, #share_result_dropdown',
+    favId: 'mark_favourite',
+    entriesSelector: 'textarea',
+    resultLabel: 'result_label',
+    resultInput: 'select_link',
+    collaborateId: 'collaborate',
+    example_id: false,
+    exampleURL: '',
+    exampleSaveURL: '',
+    loadDependenciesURL: '',
+    tidy: {
+      'javascript': 'js',
+      'javascript 1.7': 'js',
+      'html': 'html',
+      'css': 'css'
+    },
+    jslint: {
+      evil: true,
+      passfail: false,
+      browser: true,
+      newcap: false
+    },
+    jslintLanguages: ['text/javascript', 'javascript', 'javascript 1.7'],
+    showJSLanguages: ['text/coffeescript', 'coffeescript']
+  },
+  /*
+   * Assign actions
+   */
+  initialize: function(options) {
+    this.setOptions(options);
+    var addBound = function(el, callback, bind) {
+      el = $(el)[0]; // jordan added [0] for jquery
+      if (el){
+        el.addEvent('click', callback.bind(bind));
+      }
+    };
+
+    Layout.addEvent('ready', function() {
+      addBound(this.options.saveAndReloadId, this.saveAndReload, this);
+      addBound(this.options.saveAsNewId, this.saveAsNew, this);
+      addBound(this.options.runId, this.run, this);
+      addBound(this.options.draftId, this.run, this);
+      addBound(this.options.jslintId, this.jsLint, this);
+      addBound(this.options.showJsId, this.showJs, this);
+      addBound(this.options.tidyId, this.prepareAndLaunchTidy, this);
+      addBound(this.options.favId, this.makeFavourite, this);
+      // addBound(this.options.collaborateId, this.launchTowtruck, this);
+
+      // show key shortcuts
+      var keyActions = document.getElements('a.keyActions');
+      keyActions.addEvents({
+        click: function(event){
+          this.showShortcutDialog(event);
+        }.bind(this)
+      });
+      document.id(document.body).addEvents({
+        keydown: function(event){
+          if (event.shift && event.key === '/'){
+            var elType = new Element(event.target);
+            if (elType.get('tag') === 'body'){
+              keyActions[0].fireEvent('click');
+            }
+          }
+        }
+      });
+
+      var share = $$(this.options.shareSelector);
+      if (share.length > 0) {
+        share.addEvent('click', function() {
+          this.select();
+        });
+      }
+
+      // actions run if shell loaded
+      this.form = document.id(this.options.formId);
+
+      if (this.options.exampleURL) {
+        //  this.run();
+        this.displayExampleURL();
+      }
+      // assign change language in panel
+      $$('.panelChoice').addEvent('change', this.switchLanguageAction.bind(this));
+      this.showHideJsLint();
+      this.showHideTidyUp();
+      this.showHideShowJs();
+    }.bind(this));
+  },
+
+  switchLanguageAction: function(e) {
+    if (!e) return;
+    var sel = $(e.target);
+    this.switchLanguage(sel);
+  },
+
+  /*
+   * Change language in panel
+   */
+  switchLanguage: function(sel) {
+    var panel_name = sel.get('data-panel'),
+        editorClass = Layout.editors[panel_name],
+        // Klass = MooShellEditor[panel_name.toUpperCase()],
+        language = sel.getElement('option:selected').get('text'),
+        type = sel.getElement('option:selected').get('data-mime-type');
+
+    editorClass.editor.setOption('mode', type);
+    // editorClass.updateCode();
+
+    // editor.getWindow().getElement('.CodeMirror-wrapping').destroy();
+    // Layout.editors[panel_name] = editor = false;
+    // new Klass($(sel.get('data-panel_id')), {
+    //   language: language.toLowerCase()
+    // });
+
+    Layout.editors[panel_name].setLabelName(language);
+    window['panel_' + panel_name] = language.toLowerCase();
+    this.showHideTidyUp();
+    this.showHideJsLint();
+    this.showHideShowJs();
+
+    Track.ui('Switch language: ' + language);
+  },
+
+  loadAsset: function(check, file, callback, scope) {
+    if (!check()) {
+      if (scope){
+        callback = callback.bind(scope);
+      }
+
+      Asset.javascript(file, {
+        onload: callback
+      });
+      return true;
+    }
+  },
+
+  prepareCoffee: function(callback) {
+    return this.loadAsset(function() {
+      return $defined(window.CoffeeScript);
+    }, '/js/coffeescript/coffeescript.js', callback, this);
+  },
+
+  showJs: function(e) {
+    if (e) e.stop();
+    if (this.prepareCoffee(this.showJs, this)) return;
+    var html = '<div class="modalWrap modal_Coffee">' +
+      '<div class="modalHeading"><h3>JavaScript Code</h3><span class="close">Close window</span></div>'+
+      '<div id="" class="modalBody">',
+      jscode, coffeecode;
+
+    if (panel_js != 'coffeescript') return;
+    coffeecode = Layout.editors.js.editor.getValue();
+    try {
+      jscode = CoffeeScript.compile(coffeecode);
+      html += '<pre>' + jscode + '</pre>';
+    } catch(error) {
+      html += '<p class="error">' + error + '</p>';
+    }
+    html += '</div></div>';
+    new StickyWin({
+      content: html,
+      relativeTo: $(document.body),
+      position: 'center',
+      edge: 'center',
+      closeClassName: 'close',
+      draggable: true,
+      dragHandleSelector: 'h3',
+      closeOnEsc: true,
+      destroyOnClose: true,
+      allowMultiple: false,
+      onDisplay: this.showModalFx
+    }).show();
+
+    Track.ui('Show JS');
+  },
+
+  prepareTidyUp: function(callback, scope) {
+    //return this.loadAsset(function() {
+    //  return $defined(window.Beautifier);
+    //}, '/js/beautifier.js', callback, scope);
+  },
+
+  showHideShowJs: function() {
+    var show = false,
+        showjs = $(this.options.showJsId)[0]; // jordan
+
+    if (showjs){
+      show = (Layout.editors.js.editor.getOption('mode').contains('coffeescript'));
+      if (show) {
+        showjs.getParent('li').show();
+      } else {
+        showjs.getParent('li').hide();
+      }
+    }
+  },
+
+  showHideJsLint: function() {
+
+    var hide = true,
+        lint = $(this.options.jslintId)[0]; // jordan added [0] for jquery
+
+    if (!lint) return;
+    Layout.editors.each(function(w){
+      if (this.options.jslintLanguages.contains(w.editor.getOption('mode'))) {
+        hide = false;
+      }
+    }, this);
+    if (hide) {
+      lint.getParent('li').hide();
+    } else {
+      lint.getParent('li').show();
+    }
+  },
+
+  showHideTidyUp: function() {
+/*
+    if (this.prepareTidyUp(this.showHideTidyUp, this)) return;
+    var hide = true,
+        tidy = $(this.options.tidyId);
+
+    if (!tidy) return;
+    Layout.editors.each(function(w){
+      language = this.options.tidy[w.options.language];
+      if (language && Beautifier[language]) {
+        hide = false;
+      }
+    }, this);
+    if (hide) {
+      tidy.getParent('li').hide();
+    } else {
+      tidy.getParent('li').show();
+    }
+*/
+  },
+
+  prepareAndLaunchTidy: function(e) {
+    e.stop();
+    if (this.prepareTidyUp(this.makeTidy.bind(this))) return;
+    this.makeTidy();
+  },
+
+  makeTidy: function(){
+/*
+    Layout.editors.each(function(editorInstance){
+      var code = editorInstance.editor.getValue(), language;
+      if (code) {
+        language = this.options.tidy[editorInstance.options.language];
+        if (language && Beautifier[language]) {
+          var fixed = Beautifier[language](code);
+          if (fixed) editorInstance.editor.setValue(fixed);
+          else editorInstance.editor.reindent();
+        }
+      }
+    }, this);
+*/
+  },
+
+  jsLint: function(e) {
+    e.stop();
+    if (JSHINT){
+      return this.JSLintValidate();
+      Track.ui('Validate JavaScript');
+    }
+    // if (!window.JSLINT) {
+    //   // never happens as apparently JSLINT needs to be loaded before MooTools
+    //   Asset.javascript('/js/jslint.min.js', {
+    //     onload: this.JSLintValidate.bind(this)
+    //   });
+    // } else {
+    //   return this.JSLintValidate();
+    // }
+  },
+
+  JSLintValidate: function() {
+    var editor = Layout.editors.js.editor;
+    var html = '<div class="modalWrap modal_jslint">' +
+      '<div class="modalHeading"><h3>JSLint {title}</h3><span class="close">Close window</span></div>'+
+      '<div class="modalBody">{content}</div></div>';
+    var sticky = function(subs){
+      return new StickyWin({
+        content: html.substitute(subs),
+        relativeTo: $(document.body),
+        position: 'center',
+        edge: 'center',
+        closeClassName: 'close',
+        draggable: true,
+        dragHandleSelector: 'h3',
+        closeOnEsc: true,
+        destroyOnClose: true,
+        allowMultiple: false,
+        onDisplay: this.showModalFx
+      }).show();
+    };
+
+    if (this.options.jslintLanguages.contains(panel_js)){
+      var editorValue = editor.getValue();
+
+      // clear all markers from the gutter, we'll highlight errors again in the next step
+      for (var line = 0; editor.lineCount() >= line; line++){
+        editor.setGutterMarker(line, 'note-gutter', null);
+      }
+
+      if (editorValue.trim() === ''){
+        sticky.call(this, {
+          title: 'Valid!',
+          content: '<p>Good work! Your JavaScript code is perfectly valid.</p>'
+        });
+      } else {
+        if (JSHINT(editorValue)){
+          sticky.call(this, {
+            title: 'Valid!',
+            content: '<p>Good work! Your JavaScript code is perfectly valid.</p>'
+          });
+        } else {
+          Array.each(JSHINT.errors, function(error, index){
+            errorEl = Element('span', {
+              'class': 'CodeMirror-line-error',
+              'data-title': error.reason,
+              'text': '●'
+            });
+            editor.setGutterMarker(error.line - 1, 'note-gutter', errorEl);
+          }, this);
+        }
+      }
+    } else {
+      sticky.call(this, {
+        title: 'Sorry No JavaScript!',
+        content: '<p>You\'re using ' + panel_js + '</p>'
+      });
+    }
+  },
+
+  // mark shell as favourite
+  makeFavourite: function(e) {
+    e.stop();
+    new Request.JSON({
+      'url': makefavouritepath,
+      'data': {shell_id: this.options.example_id},
+      'onSuccess': function(response) {
+
+        // #TODO: reload page after successful save
+        window.location.href = response.url;
+        //$('mark_favourite').addClass('isFavourite').getElements('span')[0].set('text', 'Base');
+      }
+    }).send();
+    Track.ui('Mark as favourite');
+  },
+
+  launchTowtruck: function(event){
+    if (event){
+      event.stop();
+    }
+    TowTruck(this);
+    Track.ui('Launch TowTruck');
+  },
+
+  // save and create new pastie
+  saveAsNew: function(e) {
+    e.stop();
+
+    // reset change state so the confirmation doesn't appear on saving
+    window.editorsModified = false;
+    Layout.updateFromMirror();
+    $('id_slug').value='';
+    $('id_version').value='0';
+    new Request.JSON({
+      'url': this.options.exampleSaveUrl,
+      'onSuccess': function(json) {
+        Layout.decodeEditors();
+        if (!json.error) {
+
+          // reload page after successful save
+          window.location = json.pastie_url_relative;
+        } else {
+          alert('ERROR: ' + json.error);
+        }
+      }
+    }).send(this.form);
+
+    Track.ui('Save as new fiddle');
+  },
+
+  // update existing (create shell with new version)
+  saveAndReload: function(e) {
+    if (e) e.stop();
+
+    // reset change state so the confirmation doesn't appear on updating
+    window.editorsModified = false;
+    Layout.updateFromMirror();
+    new Request.JSON({
+      'url': this.options.exampleSaveUrl,
+      'onSuccess': function(json) {
+
+        // reload page after successful save
+        Layout.decodeEditors();
+        window.location = json.pastie_url_relative;
+      }
+    }).send(this.form);
+
+    Track.ui('Update fiddle');
+  },
+
+  // run - submit the form (targets to the iframe)
+  run: function(e) {
+    var draftonly = false;
+    if (e) e.stop();
+    if (e && ($(e.target).getParent().get('id') === 'mobile' || $(e.target).get('id') === 'mobile')) {
+      draftonly = new Element('input', {
+        'hidden': true,
+        'name': 'draftonly',
+        'id': 'draftonly',
+        'value': true
+      });
+      draftonly.inject(this.form);
+    }
+    Layout.updateFromMirror();
+    this.form.submit();
+    if (draftonly) {
+      draftonly.destroy();
+    }
+    this.fireEvent('run');
+
+    Track.ui('Run fiddle');
+  },
+
+  loadDraft: function(e) {
+    if (e) e.stop();
+    if (username) {
+      window.open('/draft/', 'jsfiddle_draft');
+    } else {
+      window.location = '/user/login/';
+    }
+
+    Track.ui('Load draft');
+  },
+
+  // This is a method to be called by keyboard shortcut
+  toggleSidebar: function(e) {
+     if (e) e.stop();
+     Layout.sidebar.toggle();
+
+     Track.ui('Toggle sidebar');
+  },
+
+  showShortcutDialog: function(e) {
+    if (e) e.stop();
+    var html = '<div class="modalWrap modal_kbd">' +
+                '<div class="modalHeading"><h3>Keyboard shortcuts</h3><span class="close">Close window</span></div>'+
+                '<div id="kbd" class="modalBody">' +
+                '<ul>' +
+                '<li><kbd>CTRL</kbd> + <kbd>Return</kbd> <span>Run fiddle</span></li>' +
+                '<li><kbd>CTRL</kbd> + <kbd>S</kbd> <span>Save fiddle (Save or Update)</span></li>' +
+                '<li><kbd>CTRL</kbd> + <kbd>Shift</kbd> + <kbd>Return</kbd> <span>Load draft</span></li>' +
+                // '<li><kbd>Control</kbd> + <kbd>&uarr;</kbd> or <kbd>Control</kbd> + <kbd>&darr;</kbd> <span>Switch editor windows</span></li>' +
+                '<li><kbd>CTRL</kbd> + <kbd>Shift</kbd> + <kbd>&uarr;</kbd> <span>Toggle sidebar</span></li>' +
+                '<li><kbd>CTRL</kbd> + <kbd>K</kbd> <span>Collaboration with TowTruck (very Alpha, don\'t rely on it too much)</span></li>' +
+                '</ul>' +
+                '</div></div>';
+
+    new StickyWin({
+      content: html,
+      relativeTo: $(document.body),
+      position: 'center',
+      edge: 'center',
+      closeClassName: 'close',
+      draggable: true,
+      dragHandleSelector: 'h3',
+      closeOnEsc: true,
+      destroyOnClose: true,
+      allowMultiple: false,
+      onDisplay: this.showModalFx
+    }).show();
+
+    Track.ui('Show shortcuts modal');
+  },
+
+  showModalFx: function(){
+      $$('.modalWrap')[0].addClass('show');
+  },
+
+  switchTo: function(index) {
+    Layout.current_editor = Layout.editors_order[index];
+    Layout.editors[Layout.current_editor].editor.focus();
+  },
+
+  switchNext: function() {
+    // find current and switch to the next
+    var index = Layout.editors_order.indexOf(Layout.current_editor);
+    var nextindex = (index + 1) % 3;
+    this.switchTo(nextindex);
+  },
+
+  switchPrev: function() {
+    // find current and switch to previous
+    var index = Layout.editors_order.indexOf(Layout.current_editor);
+    var nextindex = (index - 1) % 3;
+    if (nextindex < 0) nextindex = 2;
+    this.switchTo(nextindex);
+  },
+
+  // rename iframe label to present the current URL
+  displayExampleURL: function() {
+    var resultInput = document.id(this.options.resultInput);
+    if (resultInput) {
+      if (Browser.Engine.gecko) {
+        resultInput.setStyle('padding-top', '4px');
+      }
+      // resultInput.select();
+    }
+  },
+
+  loadLibraryVersions: function(group_id) {
+    if (group_id) {
+      new Request.JSON({
+        url: this.options.loadLibraryVersionsURL.substitute({group_id: group_id}),
+        onSuccess: function(response) {
+          $('js_lib').empty();
+          $('js_dependency').empty();
+          response.libraries.each( function(lib) {
+            new Element('option', {
+              value: lib.id,
+              text: "{group_name} {version}".substitute(lib)
+            }).inject($('js_lib'));
+            if (lib.selected) $('js_lib').set('value',lib.id);
+          });
+          response.dependencies.each(function (dep) {
+            new Element('li', {
+              html: [
+                "<input id='dep_{id}' type='checkbox' name='js_dependency[{id}]' value='{id}'/>",
+                "<label for='dep_{id}'>{name}</label>"
+                ].join('').substitute(dep)
+            }).inject($('js_dependency'));
+            if (dep.selected) $('dep_'+dep.id).set('checked', true);
+          });
+        }
+      }).send();
+    } else {
+      // XXX: would be good to send an error somehow
+    }
+  },
+
+  loadDependencies: function(lib_id) {
+    if (lib_id) {
+      new Request.JSON({
+        url: this.options.loadDependenciesURL.substitute({lib_id: lib_id}),
+        method: 'get',
+        onSuccess: function(response) {
+          $('js_dependency').empty();
+          response.each(function (dep) {
+            new Element('li', {
+              html: [
+                "<input id='dep_{id}' type='checkbox' name='js_dependency[{id}]' value='{id}'/>",
+                "<label for='dep_{id}'>{name}</label>"
+                ].join('').substitute(dep)
+            }).inject($('js_dependency'));
+            if (dep.selected) $('dep_'+dep.id).set('checked', true);
+          });
+        }
+      }).send();
+    } else {
+      // XXX: would be good to send an error somehow
+    }
+  }
+});
+
+/**
+*
+*  Base64 encode / decode
+*  http://www.webtoolkit.info/
+*
+**/
+
+var Base64 = {
+
+  // private property
+  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+
+  // public method for encoding
+  encode : function (input) {
+    var output = "";
+    input = input || "";
+    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+    var i = 0;
+
+    input = Base64._utf8_encode(input);
+
+    while (i < input.length) {
+
+      chr1 = input.charCodeAt(i++);
+      chr2 = input.charCodeAt(i++);
+      chr3 = input.charCodeAt(i++);
+
+      enc1 = chr1 >> 2;
+      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+      enc4 = chr3 & 63;
+
+      if (isNaN(chr2)) {
+        enc3 = enc4 = 64;
+      } else if (isNaN(chr3)) {
+        enc4 = 64;
+      }
+
+      output = output +
+      this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+      this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+
+    }
+
+    return output;
+  },
+
+  // public method for decoding
+  decode : function (input) {
+    var output = "";
+    var chr1, chr2, chr3;
+    var enc1, enc2, enc3, enc4;
+    var i = 0;
+
+    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+    while (i < input.length) {
+
+      enc1 = this._keyStr.indexOf(input.charAt(i++));
+      enc2 = this._keyStr.indexOf(input.charAt(i++));
+      enc3 = this._keyStr.indexOf(input.charAt(i++));
+      enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+      chr1 = (enc1 << 2) | (enc2 >> 4);
+      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+      chr3 = ((enc3 & 3) << 6) | enc4;
+
+      output = output + String.fromCharCode(chr1);
+
+      if (enc3 != 64) {
+        output = output + String.fromCharCode(chr2);
+      }
+      if (enc4 != 64) {
+        output = output + String.fromCharCode(chr3);
+      }
+
+    }
+
+    output = Base64._utf8_decode(output);
+
+    return output;
+
+  },
+
+  // private method for UTF-8 encoding
+  _utf8_encode : function (string) {
+    var utftext = "";
+    string = string.replace(/\r\n/g,"\n");
+
+    for (var n = 0; n < string.length; n++) {
+
+      var c = string.charCodeAt(n);
+
+      if (c < 128) {
+        utftext += String.fromCharCode(c);
+      }
+      else if((c > 127) && (c < 2048)) {
+        utftext += String.fromCharCode((c >> 6) | 192);
+        utftext += String.fromCharCode((c & 63) | 128);
+      }
+      else {
+        utftext += String.fromCharCode((c >> 12) | 224);
+        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+        utftext += String.fromCharCode((c & 63) | 128);
+      }
+
+    }
+    return utftext;
+  },
+
+  // private method for UTF-8 decoding
+  _utf8_decode : function (utftext) {
+    var string = "";
+    var i = 0;
+    var c = c1 = c2 = 0;
+
+    while ( i < utftext.length ) {
+
+      c = utftext.charCodeAt(i);
+
+      if (c < 128) {
+        string += String.fromCharCode(c);
+        i++;
+      }
+      else if((c > 191) && (c < 224)) {
+        c2 = utftext.charCodeAt(i+1);
+        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+        i += 2;
+      }
+      else {
+        c2 = utftext.charCodeAt(i+1);
+        c3 = utftext.charCodeAt(i+2);
+        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+        i += 3;
+      }
+
+    }
+
+    return string;
+  }
+
+};
+
+var Dropdown = new Class({
+
+  initialize: function(){
+    this.dropdown = {
+      cont: document.getElements('.dropdownCont'),
+      trigger: document.getElements('.dropdown a.aiButton')
+    };
+
+    this.setDefaults();
+  },
+
+  setDefaults: function(){
+    this.dropdown.cont.fade('hide');
+    this.dropdown.cont.set('tween', {
+      duration: 200
+    });
+
+    this.dropdown.trigger.each(function(trigger){
+      trigger.addEvents({
+        click: this.toggle.bindWithEvent(trigger, this)
+      });
+    }, this);
+
+    $(document.body).addEvents({
+      click: function(e){
+        if (!$(e.target).getParent('.dropdownCont')){
+          this.hide();
+          // this.dropdown.trigger.getElement('span').removeClass('selected')
+        }
+      }.bind(this)
+    });
+  },
+
+  toggle: function(event, parent){
+    var trigger = Element(event.target);
+    event.stop();
+    parent.dropdown.cont.fade('out');
+    // trigger.removeClass('selected')
+
+    if (this.getNext('.dropdownCont').getStyles('opacity')['opacity'] === 0){
+      this.getNext('.dropdownCont').fade('in');
+      // trigger.addClass('selected');
+    }
+  },
+
+  hide: function(){
+    this.dropdown.cont.fade('out');
+  }
+});
+
+// mootools events don't work with beforeunload for some reason
+window.onbeforeunload = function(){
+  if (window.editorsModified){
+    return "You've modified your fiddle, reloading the page will reset all changes."
+  }
+};
diff --git a/plugins/code_editor/EditorCM.js b/plugins/code_editor/EditorCM.js
new file mode 100644 (file)
index 0000000..0c5f98e
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ Class: MooshellEditor
+ Editor using CodeMirror
+ http://codemirror.net
+ */
+
+// jordan
+var codemirrorpath = "";
+// end jorda
+
+var disallowedPlatforms = ['ios', 'android', 'ipod'];
+var default_code_mirror_options = {
+  gutters: ["note-gutter", "CodeMirror-linenumbers"],
+  tabSize: 4,
+  indentUnit: 4,
+  matchBrackets: true,
+  lineNumbers: true,
+  lineWrapping: true,
+  tabMode: 'spaces' // or 'shift'
+};
+
+var MooShellEditor = new Class({
+  Implements: [Options, Events],
+
+  parameter: 'Editor',
+
+  options: {
+    useCodeMirror: true,
+    codeMirrorOptions: default_code_mirror_options,
+    syntaxHighlighting: []
+  },
+
+  window_names: {
+    'javascript': 'JavaScript',
+    'html': 'HTML',
+    'css': 'CSS',
+    'scss': 'SCSS',
+    'coffeescript': 'CoffeeScript',
+    'javascript 1.7': 'JavaScript 1.7'
+  },
+
+  initialize: function(el, options) {
+    this.validationTooltip;
+
+    // switch off CodeMirror for IE
+    //if (Browser.Engine.trident) options.useCodeMirror = false;
+    this.element = $(el)[0]; // jordan added [0] for jquery
+    if (!this.options.syntaxHighlighting.contains(options.language)) {
+      this.forceDefaultCodeMirrorOptions();
+    }
+    this.setOptions(options);
+    var is_disallowed = (disallowedPlatforms.contains(Browser.Platform.name));
+    if (this.options.useCodeMirror && !is_disallowed) {
+      // hide textarea
+      this.element.hide();
+      // prepare settings
+      if (!this.options.codeMirrorOptions.stylesheet && this.options.stylesheet) {
+        this.options.codeMirrorOptions.stylesheet = this.options.stylesheet.map(function(path) {
+          return mediapath + path;
+        });
+      }
+      if (!this.options.codeMirrorOptions.path) {
+        this.options.codeMirrorOptions.path = codemirrorpath + 'js/';
+      }
+      if (!this.options.codeMirrorOptions.content) {
+        this.options.codeMirrorOptions.content = this.element.get('value');
+      }
+
+      var parentNode = this.element.getParent();
+      var options = { value: this.element.value };
+      options = Object.append(options, this.options.codeMirrorOptions);
+      this.editor = CodeMirror(parentNode, options);
+
+      // run this after initialization
+      if (!this.options.codeMirrorOptions.initCallback){
+        if (this.options.codeMirrorOptions.autofocus) {
+          // set current editor
+          Layout.current_editor = this.options.name;
+        }
+      }
+
+      // set editor options that can only be set once the editor is initialized
+      var cur = this.editor.getLineHandle(this.editor.getCursor().line);
+      this.setEditorEvents({
+        focus: function(){
+          Layout.current_editor = this.options.name;
+          // this.editor.removeLineClass(cur, 'background', 'activeline');
+          // this.editor.hlLine = this.editor.addLineClass(0, "background", "activeline");
+        },
+        cursorActivity: function(){
+          // var cur = this.editor.getLineHandle(this.editor.getCursor().line);
+          // if (cur != this.editor.hlLine) {
+          //   this.editor.removeLineClass(this.editor.hlLine, 'background', 'activeline');
+          //   this.editor.hlLine = this.editor.addLineClass(cur, 'background', 'activeline');
+          // }
+        },
+        blur: function(){
+          // this.editor.removeLineClass(this.editor.hlLine, 'background', 'activeline');
+        },
+        change: function(){
+          this.validateEditorInput.call(this, parentNode);
+          window.editorsModified = true;
+        }.bind(this)
+      });
+
+      // disable new line insertion when saving fiddle
+      CodeMirror.keyMap.default['Ctrl-Enter'] = function(){
+        return false;
+      };
+
+      // don't let Emmet capture this
+      delete CodeMirror.keyMap.default['Cmd-L'];
+    }
+
+    this.editorLabelFX = new Fx.Tween(this.getLabel(), {
+      property: 'opacity',
+      link: 'cancel'
+    });
+
+    this.getWindow().addEvents({
+      mouseenter: function() {
+        this.editorLabelFX.start(0);
+      }.bind(this),
+      mouseleave: function() {
+        this.editorLabelFX.start(0.8);
+      }.bind(this)
+    });
+
+// jordan commented
+//    mooshell.addEvents({
+//      'run': this.b64decode.bind(this)
+//    });
+
+    Layout.registerEditor(this);
+    this.setLabelName(this.options.language || this.options.name);
+  },
+
+  validateEditorInput: function(parentNode){
+    var currentValue = this.getCode();
+    var warnings = [];
+
+    // destroy tooltip if it already exists for this editor
+    if (this.validationTooltip){
+      this.validationTooltip.destroy();
+    }
+
+    // create the container
+    this.validationTooltip = Element('ul', {
+      'class': 'warningTooltip'
+    });
+
+    // collect warnings
+    Object.each(this.options.disallowed, function(value, key){
+      if (currentValue.test(key, 'i')){
+        warnings.push('<li>' + value + '</li>');
+      }
+    });
+
+    // inject container
+    this.validationTooltip = this.validationTooltip.inject(parentNode);
+
+    // squash and apply warnings
+    this.validationTooltip.set({
+      html: warnings.join('')
+    });
+  },
+
+  getEditor: function() {
+    return this.editor || this.element;
+  },
+
+  getWindow: function() {
+    if (!this.window) {
+      this.window = this.element.getParent('.window');
+    }
+    return this.window;
+  },
+
+  getLabel: function() {
+    return this.getWindow().getElement('.window_label');
+  },
+
+  b64decode: function() {
+    this.element.set('value', this.before_decode);
+  },
+
+  getCode: function() {
+    return (this.editor) ? this.editor.getValue() : this.element.get('value');
+  },
+
+  updateFromMirror: function() {
+    this.before_decode = this.getCode();
+    this.element.set('value', Base64.encode(this.before_decode));
+  },
+
+  updateCode: function() {
+    this.element.set('value', this.getCode());
+  },
+
+  clean: function() {
+    this.element.set('value', '');
+    this.cleanEditor();
+  },
+
+  cleanEditor: function() {
+    if (this.editor) this.editor.setCode('');
+  },
+
+  hide: function() {
+    this.getWindow().hide();
+  },
+
+  show: function() {
+    this.getWindow().show();
+  },
+
+  setEditorOptions: function(options){
+    Object.each(options, function(fn, key){
+      this.editor.setOption(key, fn.bind(this));
+    }, this);
+  },
+
+  setEditorEvents: function(e){
+    Object.each(e, function(fn, key){
+      this.editor.on(key, fn.bind(this));
+    }, this);
+  },
+
+  setLanguage: function(language) {
+    // Todo: This is hacky
+    this.setLabelName(language);
+  },
+
+  setLabelName: function(language) {
+    this.getLabel().set('text', this.window_names[language] || language);
+  },
+
+  setStyle: function(key, value) {
+    if (this.editor) return $(this.editor.frame).setStyle(key, value);
+    return this.element.setStyle(key, value);
+  },
+
+  setStyles: function(options) {
+    if (this.editor) return $(this.editor.frame).setStyles(options);
+    return this.element.setStyles(options);
+  },
+
+  setWidth: function(width) {
+    this.getWindow().setStyle('width', width);
+  },
+
+  setHeight: function(height) {
+    this.getWindow().setStyle('height', height);
+  },
+
+  getPosition: function() {
+    if (this.editor) return $(this.editor.frame).getPosition();
+    return this.element.getPosition();
+  },
+
+  forceDefaultCodeMirrorOptions: function() {
+    this.options.codeMirrorOptions = default_code_mirror_options;
+  }
+});
+
+
+/*
+ * JS specific settings
+ */
+MooShellEditor.JS = new Class({
+  Extends: MooShellEditor,
+
+  options: {
+    name: 'js',
+    language: 'javascript',
+    useCodeMirror: true,
+    codeMirrorOptions: {
+      autofocus: true,
+      mode: 'javascript'
+    },
+    syntaxHighlighting: ['javascript', 'javascript 1.7'],
+    disallowed: {
+      '<script': "Input plain JavaScript code, no HTML.",
+      'document.write': "<code>document.write</code> is disallowed in JSFiddle envioriment and might break your fiddle."
+    }
+  },
+
+  initialize: function(el, options) {
+    this.setOptions(options);
+    this.parent(el, this.options);
+  }
+});
+
+/*
+ * CSS specific settings
+ */
+MooShellEditor.CSS = new Class({
+  Extends: MooShellEditor,
+
+  options: {
+    name: 'css',
+    language: 'css',
+    useCodeMirror: true,
+    codeMirrorOptions: {
+      mode: 'css'
+    },
+    syntaxHighlighting: ['css', 'scss']
+  },
+
+  initialize: function(el, options) {
+    this.setOptions(options);
+    this.parent(el, this.options);
+  }
+});
+
+/*
+ * HTML specific settings
+ */
+MooShellEditor.HTML = new Class({
+  Extends: MooShellEditor,
+
+  options: {
+    name: 'html',
+    language: 'html',
+    useCodeMirror: true,
+    codeMirrorOptions: {
+      mode: 'xml'
+    },
+    syntaxHighlighting: ['html'],
+    disallowed: {
+      '<html': "No need for the <code>HTML</code> tag, it's already in the output.",
+      '<meta': "No need for the <code>META</code> tags.",
+      '<head*.?>': "No need for the <code>HEAD</code> tag, it's already in the output.",
+      '<doctype': "Select <code>DOCTYPE</code> from the <strong>Info</strong> panel on the left, instead of adding a tag.",
+      '<script/?.*text\/javascript': "For JavaScript use the panel below or the <strong>Resources</strong> panel for external files.",
+      '<link/?.*rel/?.*stylesheet': "For external CSS files use the <strong>Resources</strong> panel on the left.",
+      '<style': "For CSS use the panel on the right.",
+    }
+  },
+
+  initialize: function(el, options) {
+    this.setOptions(options);
+    this.parent(el, this.options);
+  }
+});
diff --git a/plugins/code_editor/LayoutCM.js b/plugins/code_editor/LayoutCM.js
new file mode 100644 (file)
index 0000000..8d89aab
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ Layout using CodeMirror
+ */
+
+Element.implement({
+  getInnerWidth: function() {
+    return this.getSize().x -
+      this.getStyle('padding-left').toInt() -
+      this.getStyle('padding-right').toInt() -
+      this.getStyle('border-left-width').toInt() -
+      this.getStyle('border-right-width').toInt();
+  },
+  getInnerHeight: function() {
+    return this.getSize().y -
+      this.getStyle('padding-top').toInt() -
+      this.getStyle('padding-bottom').toInt() -
+      this.getStyle('border-top-width').toInt() -
+      this.getStyle('border-bottom-width').toInt();
+  }
+});
+
+var keyMods = {
+  'shift': false,
+  'shiftKey': false,
+  'control': false,
+  'ctrlKey': false
+}; // these mods will be checked
+
+var Layout = {
+  editors: $H({}),
+  editors_order: ['html', 'css', 'js'],
+  reservedKeys: [ // list of [modifier,keycode,callbackname]
+    ['ctrlKey', 13, 'run'], ['control', 13, 'run'],   // c+ret+run
+    ['ctrlKey', 38, 'switchPrev'],                    // c+upArrow
+    ['ctrlKey', 40, 'switchNext'],                     // c+dnArrow
+    ['ctrlKey+shiftKey', 13, 'loadDraft'], ['control+shift', 13, 'loadDraft'],
+    ['ctrlKey+shiftKey', 38, 'toggleSidebar'], ['control+shift', 38, 'toggleSidebar'],
+    ['ctrlKey+shiftKey', 76, 'showShortcutDialog'], ['control+shift', 76, 'showShortcutDialog'],
+    ['ctrlKey', 83, 'saveAndReload'], ['control', 83, 'saveAndReload'],
+    ['ctrlKey', 75, 'launchTowtruck'], ['control', 75, 'launchTowtruck']
+    // future
+    // ['ctrlKey', f, 'searchBox'], ['control', f, 'searchBox']
+  ],
+  isMobile: (navigator.userAgent.match(/(iPhone|iPod|iPad)/) ||
+             navigator.userAgent.match(/BlackBerry/) ||
+             navigator.userAgent.match(/Android/)),
+  render: function () {
+    // instantiate sidebar
+//    this.sidebar = new Sidebar({
+//      DOM: 'sidebar'
+//    });
+    window.addEvents({
+      'resize': this.resize.bind(this),
+      'keydown': function(keyEvent) {
+        // console.log(keyEvent)
+        if (this.isReservedKey(false, keyEvent)) {
+          this.routeReservedKey(keyEvent);
+        }
+      }.bind(this)
+    });
+//    this.sidebar.addEvents({
+//      'accordion_resized': this.resize.bind(this)
+//    });
+    // set editor labels
+    var result = document.id('result');
+    $$('.window_label').setStyle('opacity', 0.8);
+    if (result) {
+      result.getElement('.window_label').setStyle('opacity', 0.3);
+      this.result = result.getElement('iframe');
+    }
+    // resize
+    this.resize();
+    this.resize.bind(this).delay(20);
+    // change behaviour for IE
+    if (!Browser.Engine.trident4) {
+      this.createDragInstances();
+    }
+    this.createErrorTooltips();
+    // send an event
+    this.fireEvent('ready');
+    // jordan:
+    this.refreshEditors();
+  },
+
+  createErrorTooltips: function(){
+    var tooltip = Element('span', {
+      'class': 'CodeMirror-error-tooltip'
+    }).inject(document.body);
+    document.id('content').addEvents({
+      'mouseover:relay(.CodeMirror-line-error)': function(el){
+        tooltip.set({
+          html: this.get('data-title'),
+          styles: {
+            display: 'block'
+          }
+        });
+        tooltip.position({
+          relativeTo: this,
+          edge: 'centerBottom',
+          offset: {
+            y: -15
+          }
+        });
+      },
+      'mouseout:relay(.CodeMirror-line-error)': function(){
+        tooltip.set({
+          html: '',
+          styles: {
+            top: 0,
+            left: 0,
+            display: 'none'
+          }
+        });
+      }
+    });
+  },
+
+  routeReservedKey: function(keyEvent) {
+    this.reservedKeys.each(function(keyDef){
+      if (this.matchKey(keyEvent, keyDef)) {
+        mooshell[keyDef.getLast()].bind(mooshell).call();
+      }
+    }, this);
+  },
+
+  matchKey: function(keyEvent, keyDef) {
+    if (!keyEvent) return false;
+    var key = keyEvent.keyCode || keyEvent.code;
+    // check if the right key is pressed
+    if (!keyDef.contains(key)) return false;
+    // check for the modifications
+    var pass = true;
+    if (keyDef.length > 1) {
+      var mods = {};
+      keyDef[0].split('+').each(function(mod) {
+        mods[mod] = true;
+      });
+      // adding other mods
+      $each(keyMods, function(value, mod) {
+        if (!mods[mod]) mods[mod] = false;
+      });
+      // check all possibilities
+      $each(mods, function(required, mod) {
+        if (!!keyEvent[mod] != required) pass = false;
+      });
+    }
+    return pass;
+  },
+
+  isReservedKey: function(keyCode, keyEvent) {
+    return (this.reservedKeys.some(function(keyDef) {
+      return this.matchKey(keyEvent, keyDef);
+    }, this));
+  },
+
+  findLayoutElements: function() {
+    // look up some elements, and cache the findings
+    this.content = document.id('content');
+    this.columns = this.content.getChildren('.column');
+    this.windows = this.content.getElements('.window');
+    this.shims = this.content.getElements('.column .shim');
+    this.handlers = $H({
+      'vertical': this.content.getElementById('handler_vertical'),
+      'left': this.columns[0].getElement('.handler_horizontal'),
+      'right': this.columns[1].getElement('.handler_horizontal')
+    });
+  },
+
+  registerEditor: function(editor) {
+    this.editors[editor.options.name] = editor;
+    this.resize();
+  },
+
+  decodeEditors: function() {
+    this.editors.each(function(ed) {
+      ed.b64decode();
+    });
+  },
+
+  updateFromMirror: function() {
+    this.editors.each(function(ed) {
+      ed.updateFromMirror();
+    });
+  },
+
+  cleanMirrors: function() {
+    this.editors.each(function(ed) {
+      ed.clean();
+    });
+  },
+
+  createDragInstances: function() {
+    var onDrag_horizontal = function(h) {
+      var windows = h.getParent().getElements('.window');
+      var top = (h.getPosition(this.content).y + h.getHeight() / 2) / this.content.getHeight() * 100;
+      windows[0].setStyle('height', top + '%');
+      windows[1].setStyle('height', 100 - top + '%');
+      this.refreshEditors();
+    }.bind(this);
+
+    var onDrag_vertical = function(h) {
+      var left = (h.getPosition(this.content).x + h.getWidth() / 2) / this.content.getWidth() * 100;
+      this.columns[0].setStyle('width', left + '%');
+      this.columns[1].setStyle('width', 100 - left + '%');
+    }.bind(this);
+
+    this.handlers.each(function(h) {
+      var isHorizontal = h.hasClass('handler_horizontal');
+      h.dragInstance = new Drag(h, {
+        'modifiers': isHorizontal ? {'x': null, 'y': 'top'} : {'x': 'left', 'y': null},
+        'limit': {
+          'x': [100, this.content.getWidth() - 100],
+          'y': [100, this.content.getHeight() - 100]
+        },
+        'onBeforeStart': function() { this.shims.show(); }.bind(this),
+        'onDrag': isHorizontal ? onDrag_horizontal : onDrag_vertical,
+        'onCancel': function() { this.shims.hide(); }.bind(this),
+        'onComplete': function() { this.shims.hide(); }.bind(this)
+      });
+    }, this);
+
+    // Save window sizes to cookie onUnload.
+    window.addEvent('unload', function() {
+      var sizes = {
+        'w': [],
+        'h': []
+      };
+      this.columns.each(function(col, i) {
+        var width = col.getStyle('width');
+        sizes.w[i] = width.contains('%') ? width : null;
+      });
+      this.windows.each(function(win, i) {
+        var height = win.getStyle('height');
+        sizes.h[i] = height.contains('%') ? height : null;
+      });
+      Cookie.write('window_sizes', JSON.encode(sizes), {'domain': location.host});
+    }.bind(this));
+
+    // Read window sizes from cookie.
+    this.setWindowSizes();
+  },
+
+  setWindowSizes: function(sizes) {
+    // sizes === undefined --> read from cookie
+    // sizes == null/false --> reset sizes + delete cookie
+    // sizes == true       --> use sizes
+    if (typeof sizes === 'undefined') {
+      sizes = Cookie.read('window_sizes');
+      if (sizes) {
+        sizes = JSON.decode(sizes);
+      }
+    }
+    if (sizes) {
+      if ($type(sizes.w) === 'array') {
+        sizes.w.each(function(width, i) {
+          this.columns[i].setStyle('width', width);
+        }, this);
+      }
+      if ($type(sizes.h) == 'array') {
+        sizes.h.each(function(height, i) {
+          this.windows[i].setStyle('height', height);
+        }, this);
+      }
+    } else {
+      this.columns.setStyle('width', null);
+      this.windows.setStyle('height', null);
+      Cookie.dispose('window_sizes', {'domain': location.host});
+    }
+    this.resize();
+  },
+
+  resize: function(e) {
+    if (!this.content) {
+      this.findLayoutElements();
+    }
+
+    var win_size = window.getSize();
+    var av_height = win_size.y -
+      this.columns[0].getPosition().y +
+      this.windows[0].getStyle('top').toInt() +
+      this.windows[1].getStyle('bottom').toInt();
+
+    this.content.setStyle('height', av_height);
+
+    // set handler positions
+    this.handlers.vertical.setStyle('left', this.windows[0].getCoordinates(this.content).right);
+    this.handlers.left.setStyle('top', this.windows[0].getCoordinates(this.content).bottom);
+    this.handlers.right.setStyle('top', this.windows[2].getCoordinates(this.content).bottom);
+
+    this.fireEvent('resize');
+  },
+
+  refreshEditors: function(){
+    Object.each(this.editors, function(editorInstance){
+      editorInstance.editor.refresh();
+    });
+  }
+};
+
+// add events to Layout object
+$extend(Layout, new Events());
diff --git a/plugins/code_editor/__init__.py b/plugins/code_editor/__init__.py
new file mode 100644 (file)
index 0000000..c3f7528
--- /dev/null
@@ -0,0 +1,29 @@
+from unfold.plugin import Plugin
+
+class CodeEditor(Plugin):
+
+    def template_file(self):
+        return "code_editor.html"
+
+    def requirements (self):
+        reqs = {
+            'js_files' : [
+                'js/code_editor.js',
+                'js/moo-clientcide-1.3.js',
+                'js/codemirror.js',
+                'js/sql.js',
+                'js/Actions.js',
+                #'js/EditorCM.js',
+                'js/LayoutCM.js',
+            ] ,
+            'css_files': [
+                'css/codemirror.css',
+            ]
+        }
+        return reqs
+
+    def json_settings_list (self):
+        return ['plugin_uuid', 'domid', 'lineNumbers']
+
+    def export_json_settings (self):
+        return True
diff --git a/plugins/code_editor/code_editor.html b/plugins/code_editor/code_editor.html
new file mode 100644 (file)
index 0000000..c39802a
--- /dev/null
@@ -0,0 +1 @@
+<textarea id="{{ domid }}-textarea" rows="10" cols="40" name="code_html"></textarea>
diff --git a/plugins/code_editor/code_editor.js b/plugins/code_editor/code_editor.js
new file mode 100644 (file)
index 0000000..5e88873
--- /dev/null
@@ -0,0 +1,433 @@
+/**
+ * Description: CodeEditor plugin
+ * Copyright (c) 2012 UPMC Sorbonne Universite - INRIA
+ * License: GPLv3
+ */
+
+/*
+ * It's a best practice to pass jQuery to an IIFE (Immediately Invoked Function
+ * Expression) that maps it to the dollar sign so it can't be overwritten by
+ * another library in the scope of its execution.
+ */
+
+(function($){
+
+     var default_code_mirror_options = {
+       gutters: ["note-gutter", "CodeMirror-linenumbers"],
+       tabSize: 4,
+       indentUnit: 4,
+       matchBrackets: true,
+       lineNumbers: true,
+       lineWrapping: true,
+       tabMode: 'spaces' // or 'shift'
+     };
+
+    
+    // routing calls
+    jQuery.fn.CodeEditor = function( method ) {
+               if ( methods[method] ) {
+                       return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+               } else if ( typeof method === 'object' || ! method ) {
+                       return methods.init.apply( this, arguments );
+               } else {
+                       jQuery.error( 'Method ' +  method + ' does not exist on jQuery.CodeEditor' );
+               }    
+    };
+
+    /***************************************************************************
+     * Public methods
+     ***************************************************************************/
+
+    var methods = {
+
+        /**
+         * @brief Plugin initialization
+         * @param options : an associative array of setting values
+         * @return : a jQuery collection of objects on which the plugin is
+         *     applied, which allows to maintain chainability of calls
+         */
+        init : function ( options ) {
+
+            /* Default settings */
+            var options = $.extend( {
+                useCodeMirror: true,
+                codeMirrorOptions: default_code_mirror_options,
+                syntaxHighlighting: []
+            }, options);
+
+            return this.each(function() {
+
+                var $this = $(this);
+
+                /* An object that will hold private variables and methods */
+                var plugin = new CodeEditor(options);
+                $this.data('Manifold', plugin);
+
+                /* Events */
+                $this.on('show.CodeEditor', methods.show);
+
+            }); // this.each
+        }, // init
+
+        /**
+         * @brief Plugin destruction
+         * @return : a jQuery collection of objects on which the plugin is
+         *     applied, which allows to maintain chainability of calls
+         */
+        destroy : function( ) {
+
+            return this.each(function() {
+                var $this = $(this);
+                var hazelnut = $this.data('Manifold');
+
+                // Unbind all events using namespacing
+                $(window).unbind('Manifold');
+
+                // Remove associated data
+                hazelnut.remove();
+                $this.removeData('Manifold');
+            });
+        }, // destroy
+
+        show : function( ) {
+            var $this=$(this);
+            // xxx wtf. why [1] ? would expect 0...
+            if (debug)
+                messages.debug("Hitting suspicious line in hazelnut.show");
+            var oTable = $($('.dataTable', $this)[1]).dataTable();
+            oTable.fnAdjustColumnSizing()
+
+            /* Refresh dataTabeles if click on the menu to display it : fix dataTables 1.9.x Bug */
+            $(this).each(function(i,elt) {
+                if (jQuery(elt).hasClass('dataTables')) {
+                    var myDiv=jQuery('#hazelnut-' + this.id).parent();
+                    if(myDiv.height()==0) {
+                        var oTable=$('#hazelnut-' + this.id).dataTable();
+                        oTable.fnDraw();
+                    }
+                }
+            });
+        } // show
+
+    }; // var methods;
+
+    /***************************************************************************
+     * Plugin object
+     ***************************************************************************/
+
+    function CodeEditor(options)
+    {
+      
+        this.initialize = function() {
+            this.validationTooltip;
+        
+            // switch off CodeMirror for IE
+            //if (Browser.Engine.trident) options.useCodeMirror = false;
+            this.element = $('#' + this.options.plugin_uuid + '-textarea')[0]; // jordan added [0] for jquery
+            if (!this.options.syntaxHighlighting.contains(this.options.language)) {
+                this.forceDefaultCodeMirrorOptions();
+            }
+            //this.setOptions(this.options);
+            var is_disallowed = (disallowedPlatforms.contains(Browser.Platform.name));
+            if (this.options.useCodeMirror && !is_disallowed) {
+                // hide textarea
+                this.element.hide();
+                // prepare settings
+                if (!this.options.codeMirrorOptions.stylesheet && this.options.stylesheet) {
+                    this.options.codeMirrorOptions.stylesheet = this.options.stylesheet.map(function(path) {
+                        return mediapath + path;
+                    });
+                }
+                if (!this.options.codeMirrorOptions.path) {
+                    var codemirrorpath = ''; // jordan
+                    this.options.codeMirrorOptions.path = codemirrorpath + 'js/';
+                }
+                if (!this.options.codeMirrorOptions.content) {
+                    this.options.codeMirrorOptions.content = this.element.get('value');
+                }
+          
+                var parentNode = this.element.getParent();
+                var options = { value: this.element.value };
+                options = Object.append(options, this.options.codeMirrorOptions);
+                this.editor = CodeMirror(parentNode, options);
+          
+                // run this after initialization
+                if (!this.options.codeMirrorOptions.initCallback){
+                    if (this.options.codeMirrorOptions.autofocus) {
+                        // set current editor
+                        Layout.current_editor = this.options.name;
+                    }
+                }
+          
+                // set editor options that can only be set once the editor is initialized
+                var cur = this.editor.getLineHandle(this.editor.getCursor().line);
+                this.setEditorEvents({
+                    focus: function(){
+                        Layout.current_editor = this.options.name;
+                        // this.editor.removeLineClass(cur, 'background', 'activeline');
+                        // this.editor.hlLine = this.editor.addLineClass(0, "background", "activeline");
+                    },
+                    cursorActivity: function(){
+                        // var cur = this.editor.getLineHandle(this.editor.getCursor().line);
+                        // if (cur != this.editor.hlLine) {
+                        //   this.editor.removeLineClass(this.editor.hlLine, 'background', 'activeline');
+                        //   this.editor.hlLine = this.editor.addLineClass(cur, 'background', 'activeline');
+                        // }
+                    },
+                    blur: function(){
+                        // this.editor.removeLineClass(this.editor.hlLine, 'background', 'activeline');
+                    },
+                    change: function(){
+                        this.validateEditorInput.call(this, parentNode);
+                        window.editorsModified = true;
+                    }.bind(this)
+                });
+          
+                // disable new line insertion when saving fiddle
+                CodeMirror.keyMap.default['Ctrl-Enter'] = function(){
+                    return false;
+                };
+        
+                // don't let Emmet capture this
+                delete CodeMirror.keyMap.default['Cmd-L'];
+            }
+        
+            this.editorLabelFX = new Fx.Tween(this.getLabel(), {
+                property: 'opacity',
+                link: 'cancel'
+            });
+        
+            this.getWindow().addEvents({
+                mouseenter: function() {
+                    this.editorLabelFX.start(0);
+                }.bind(this),
+                mouseleave: function() {
+                    this.editorLabelFX.start(0.8);
+                }.bind(this)
+            });
+        
+            // jordan commented
+            //    mooshell.addEvents({
+            //      'run': this.b64decode.bind(this)
+            //    });
+        
+            Layout.registerEditor(this);
+            this.setLabelName(this.options.language || this.options.name);
+        },
+      
+        /**
+         *
+         */
+        this.validateEditorInput = function(parentNode){
+            var currentValue = this.getCode();
+            var warnings = [];
+      
+            // destroy tooltip if it already exists for this editor
+            if (this.validationTooltip){
+                this.validationTooltip.destroy();
+            }
+      
+            // create the container
+            this.validationTooltip = Element('ul', {
+                'class': 'warningTooltip'
+            });
+      
+            // collect warnings
+            Object.each(this.options.disallowed, function(value, key){
+                if (currentValue.test(key, 'i')){
+                    warnings.push('<li>' + value + '</li>');
+                }
+            });
+      
+            // inject container
+            this.validationTooltip = this.validationTooltip.inject(parentNode);
+      
+            // squash and apply warnings
+            this.validationTooltip.set({
+                html: warnings.join('')
+            });
+        },
+      
+        /**
+         *
+         */
+        this.getEditor = function() {
+            return this.editor || this.element;
+        },
+      
+        /**
+         *
+         */
+        this.getWindow = function() {
+            if (!this.window) {
+                this.window = this.element.getParent('.window');
+            }
+            return this.window;
+        },
+      
+        /**
+         *
+         */
+        this.getLabel = function() {
+            return this.getWindow().getElement('.window_label');
+        },
+      
+        /**
+         *
+         */
+        this.b64decode = function() {
+            this.element.set('value', this.before_decode);
+        },
+      
+        /**
+         *
+         */
+        this.getCode = function() {
+            return (this.editor) ? this.editor.getValue() : this.element.get('value');
+        },
+      
+        /**
+         *
+         */
+        this.updateFromMirror = function() {
+            this.before_decode = this.getCode();
+            this.element.set('value', Base64.encode(this.before_decode));
+        },
+      
+        /**
+         *
+         */
+        this.updateCode = function() {
+            this.element.set('value', this.getCode());
+        },
+      
+        /**
+         *
+         */
+        this.clean = function() {
+            this.element.set('value', '');
+            this.cleanEditor();
+        },
+      
+        /**
+         *
+         */
+        this.cleanEditor = function() {
+            if (this.editor) this.editor.setCode('');
+        },
+      
+        /**
+         *
+         */
+        this.hide = function() {
+            this.getWindow().hide();
+        },
+      
+        this.show = function() {
+            this.getWindow().show();
+        },
+      
+        /**
+         *
+         */
+        this.setEditorOptions = function(options){
+            Object.each(options, function(fn, key){
+                this.editor.setOption(key, fn.bind(this));
+            }, this);
+        },
+      
+        /**
+         *
+         */
+        this.setEditorEvents = function(e){
+            Object.each(e, function(fn, key){
+                this.editor.on(key, fn.bind(this));
+            }, this);
+        },
+      
+        /**
+         *
+         */
+        this.setLanguage = function(language) {
+            // Todo: This is hacky
+            this.setLabelName(language);
+        },
+      
+        /**
+         *
+         */
+        this.setLabelName = function(language) {
+            this.getLabel().set('text', this.window_names[language] || language);
+        },
+      
+        /**
+         *
+         */
+        this.setStyle = function(key, value) {
+            if (this.editor) return $(this.editor.frame).setStyle(key, value);
+            return this.element.setStyle(key, value);
+        },
+      
+        /**
+         *
+         */
+        this.setStyles = function(options) {
+            if (this.editor) return $(this.editor.frame).setStyles(options);
+            return this.element.setStyles(options);
+        },
+      
+        /**
+         *
+         */
+        this.setWidth = function(width) {
+            this.getWindow().setStyle('width', width);
+        },
+      
+        /**
+         *
+         */
+        this.setHeight = function(height) {
+            this.getWindow().setStyle('height', height);
+        },
+      
+        /**
+         *
+         */
+        this.getPosition = function() {
+            if (this.editor) return $(this.editor.frame).getPosition();
+            return this.element.getPosition();
+        },
+      
+        /**
+         *
+         */
+        this.forceDefaultCodeMirrorOptions = function() {
+            this.options.codeMirrorOptions = default_code_mirror_options;
+        }
+
+        // BEGIN CONSTRUCTOR
+
+        /* member variables */
+        this.options = options;
+
+        var object = this;
+
+        // adapted from EditorCM.js from jsfiddle.net
+
+        var disallowedPlatforms = ['ios', 'android', 'ipod'];
+
+        this.window_names = {
+          'javascript': 'JavaScript',
+          'html': 'HTML',
+          'css': 'CSS',
+          'scss': 'SCSS',
+          'coffeescript': 'CoffeeScript',
+          'javascript 1.7': 'JavaScript 1.7'
+        };
+
+        this.initialize();
+        // END CONSTRUCTOR
+    } // function DebugPlatform
+
+})( jQuery );
+
diff --git a/plugins/code_editor/img/handle-h.png b/plugins/code_editor/img/handle-h.png
new file mode 100644 (file)
index 0000000..d743e52
Binary files /dev/null and b/plugins/code_editor/img/handle-h.png differ
diff --git a/plugins/code_editor/img/handle-v.png b/plugins/code_editor/img/handle-v.png
new file mode 100644 (file)
index 0000000..0e1a959
Binary files /dev/null and b/plugins/code_editor/img/handle-v.png differ
diff --git a/plugins/code_editor/img/initializing.png b/plugins/code_editor/img/initializing.png
new file mode 100644 (file)
index 0000000..2eed2a6
Binary files /dev/null and b/plugins/code_editor/img/initializing.png differ
diff --git a/plugins/code_editor/moo-clientcide-1.3.js b/plugins/code_editor/moo-clientcide-1.3.js
new file mode 100644 (file)
index 0000000..793059b
--- /dev/null
@@ -0,0 +1,15425 @@
+
+//This library: http://dev.clientcide.com/depender/build?download=true&version=Clientcide+3.1.0&require=Core%2FBrowser&require=Core%2FClass.Extras&require=Core%2FClass&require=Core%2FCore&require=Core%2FElement.Delegation&require=Core%2FElement.Dimensions&require=Core%2FElement.Event&require=Core%2FElement.Style&require=Core%2FElement&require=Core%2FFx.CSS&require=Core%2FFx.Morph&require=Core%2FFx.Transitions&require=Core%2FFx.Tween&require=Core%2FFx&require=Core%2FRequest.HTML&require=Core%2FRequest.JSON&require=Core%2FRequest&require=Core%2FSlick.Finder&require=Core%2FSlick.Parser&require=Core%2FArray&require=Core%2FEvent&require=Core%2FFunction&require=Core%2FNumber&require=Core%2FObject&require=Core%2FString&require=Core%2FCookie&require=Core%2FDOMReady&require=Core%2FJSON&require=Core%2FSwiff&require=Clientcide%2FBehavior.StickyWin&require=Clientcide%2FCollapsible&require=Clientcide%2FMultipleOpenAccordion&require=Clientcide%2FTabSwapper&require=Clientcide%2FStickyWin.Ajax&require=Clientcide%2FStickyWin.Alert&require=Clientcide%2FStickyWin.Confirm&require=Clientcide%2FStickyWin.Drag&require=Clientcide%2FStickyWin.Fx&require=Clientcide%2FStickyWin.Modal&require=Clientcide%2FStickyWin.UI&require=More%2FElement.Forms&require=More%2FOverText&require=More%2FFx.Accordion&require=More%2FFx.Move&require=More%2FFx.Reveal&require=More%2FFx.Scroll&require=More%2FFx.Slide&require=More%2FKeyboard.Extras&require=More%2FSpinner&require=More%2FMore&require=More%2FRequest.JSONP&require=More%2FRequest.Periodical&require=More%2FArray.Extras&require=More%2FDate.Extras&require=More%2FNumber.Format&require=More%2FURI.Relative&require=More%2FAssets&require=More%2FColor&require=More%2FHash.Cookie
+//Contents: Core:Source/Core/Core.js, Core:Source/Types/Object.js, Core:Source/Types/Array.js, Core:Source/Types/Function.js, Core:Source/Types/Number.js, Core:Source/Types/String.js, Core:Source/Browser/Browser.js, Core:Source/Slick/Slick.Parser.js, Core:Source/Slick/Slick.Finder.js, Core:Source/Element/Element.js, Core:Source/Class/Class.js, Core:Source/Class/Class.Extras.js, Core:Source/Request/Request.js, Core:Source/Utilities/JSON.js, Core:Source/Request/Request.JSON.js, Core:Source/Types/DOMEvent.js, Core:Source/Element/Element.Event.js, Core:Source/Element/Element.Delegation.js, Core:Source/Fx/Fx.js, Core:Source/Element/Element.Style.js, Core:Source/Fx/Fx.CSS.js, Core:Source/Fx/Fx.Morph.js, Core:Source/Utilities/Swiff.js, Core:Source/Fx/Fx.Transitions.js, More:Source/More/More.js, More:Source/Class/Class.Binds.js, More:Source/Class/Class.Occlude.js, Core:Source/Element/Element.Dimensions.js, More:Source/Element/Element.Measure.js, More:Source/Element/Element.Position.js, More:Source/Element/Element.Shortcuts.js, More:Source/Forms/OverText.js, More:Source/Utilities/IframeShim.js, More:Source/Interface/Mask.js, Core:Source/Utilities/DOMReady.js, More:Source/Element/Element.Pin.js, More:Source/Types/Object.Extras.js, Clientcide:Source/Core/Clientcide.js, Clientcide:Source/Core/dbug.js, Clientcide:Source/UI/StyleWriter.js, Clientcide:Source/UI/StickyWin.js, Clientcide:Source/UI/StickyWin.Modal.js, More:Source/Types/String.Extras.js, Clientcide:Source/UI/StickyWin.UI.js, Clientcide:Source/UI/StickyWin.Alert.js, Clientcide:Source/UI/StickyWin.Confirm.js, More:Source/Fx/Fx.Move.js, More:Source/Fx/Fx.Scroll.js, More:Source/Class/Class.Refactor.js, Core:Source/Fx/Fx.Tween.js, Clientcide:Source/UI/StickyWin.Fx.js, More:Source/Types/Array.Extras.js, Core:Source/Request/Request.HTML.js, Clientcide:Source/Layout/TabSwapper.js, More:Source/Element/Element.Forms.js, More:Source/Fx/Fx.Reveal.js, Clientcide:Source/Layout/Collapsible.js, More:Source/Utilities/Table.js, Behavior:Source/Element.Data.js, Behavior:Source/BehaviorAPI.js, Behavior:Source/Behavior.js, More:Source/Drag/Drag.js, More:Source/Drag/Drag.Move.js, Clientcide:Source/UI/StickyWin.Drag.js, Clientcide:Source/Behaviors/Behavior.StickyWin.js, More:Source/Utilities/Color.js, More:Source/Class/Events.Pseudos.js, More:Source/Element/Element.Event.Pseudos.js, More:Source/Element/Element.Event.Pseudos.Keys.js, More:Source/Interface/Keyboard.js, More:Source/Interface/Keyboard.Extras.js, More:Source/Locale/Locale.js, More:Source/Locale/Locale.en-US.Number.js, More:Source/Types/Number.Format.js, More:Source/Request/Request.Periodical.js, More:Source/Request/Request.JSONP.js, More:Source/Types/String.QueryString.js, More:Source/Types/URI.js, More:Source/Types/URI.Relative.js, Core:Source/Utilities/Cookie.js, More:Source/Locale/Locale.en-US.Date.js, More:Source/Types/Date.js, More:Source/Types/Date.Extras.js, More:Source/Fx/Fx.Elements.js, More:Source/Fx/Fx.Accordion.js, More:Source/Types/Hash.js, More:Source/Utilities/Hash.Cookie.js, More:Source/Utilities/Assets.js, More:Source/Interface/Spinner.js, More:Source/Fx/Fx.Slide.js, Clientcide:Source/UI/StickyWin.UI.Pointy.js, Clientcide:Source/UI/StickyWin.PointyTip.js, Clientcide:Source/UI/StickyWin.Ajax.js, Clientcide:Source/Layout/MultipleOpenAccordion.js
+
+// Begin: Source/Core/Core.js
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2012 [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.5',
+       build: 'ab8ea8824dc3b24b6666867a2c4ed58ebb762cf0'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+       if (item == null) return 'null';
+       if (item.$family != null) 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;
+       }
+       /*<ltIE8>*/
+       if (!item.hasOwnProperty) return false;
+       /*</ltIE8>*/
+       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 (typeof a != 'string') args = a;
+               else if (arguments.length > 1) args = arguments;
+               else if (usePlural) args = [a];
+               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();
+                       //<1.2compat>
+                       object.type = typeCheck;
+                       //</1.2compat>
+               }
+       }
+
+       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) object.implement(key, proto.protect());
+       }
+
+       if (isType){
+               var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+               object.forEachMethod = function(fn){
+                       if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+                               fn.call(prototype, prototype[methods[i]], methods[i]);
+                       }
+                       for (var key in prototype) fn.call(prototype, prototype[key], key)
+               };
+       }
+
+       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);
+});
+
+//<1.2compat>
+
+var Hash = this.Hash = new Type('Hash', function(object){
+       if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+       for (var key in object) this[key] = object[key];
+       return this;
+});
+
+Hash.implement({
+
+       forEach: function(fn, bind){
+               Object.forEach(this, fn, bind);
+       },
+
+       getClean: function(){
+               var clean = {};
+               for (var key in this){
+                       if (this.hasOwnProperty(key)) clean[key] = this[key];
+               }
+               return clean;
+       },
+
+       getLength: function(){
+               var length = 0;
+               for (var key in this){
+                       if (this.hasOwnProperty(key)) length++;
+               }
+               return length;
+       }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Object.type = Type.isObject;
+
+var Native = this.Native = function(properties){
+       return new Type(properties.name, properties.initialize);
+};
+
+Native.type = Type.type;
+
+Native.implement = function(objects, methods){
+       for (var i = 0; i < objects.length; i++) objects[i].implement(methods);
+       return Native;
+};
+
+var arrayType = Array.type;
+Array.type = function(item){
+       return instanceOf(item, Array) || arrayType(item);
+};
+
+this.$A = function(item){
+       return Array.from(item).slice();
+};
+
+this.$arguments = function(i){
+       return function(){
+               return arguments[i];
+       };
+};
+
+this.$chk = function(obj){
+       return !!(obj || obj === 0);
+};
+
+this.$clear = function(timer){
+       clearTimeout(timer);
+       clearInterval(timer);
+       return null;
+};
+
+this.$defined = function(obj){
+       return (obj != null);
+};
+
+this.$each = function(iterable, fn, bind){
+       var type = typeOf(iterable);
+       ((type == 'arguments' || type == 'collection' || type == 'array' || type == 'elements') ? Array : Object).each(iterable, fn, bind);
+};
+
+this.$empty = function(){};
+
+this.$extend = function(original, extended){
+       return Object.append(original, extended);
+};
+
+this.$H = function(object){
+       return new Hash(object);
+};
+
+this.$merge = function(){
+       var args = Array.slice(arguments);
+       args.unshift({});
+       return Object.merge.apply(null, args);
+};
+
+this.$lambda = Function.from;
+this.$mixin = Object.merge;
+this.$random = Number.random;
+this.$splat = Array.from;
+this.$time = Date.now;
+
+this.$type = function(object){
+       var type = typeOf(object);
+       if (type == 'elements') return 'array';
+       return (type == 'null') ? false : type;
+};
+
+this.$unlink = function(object){
+       switch (typeOf(object)){
+               case 'object': return Object.clone(object);
+               case 'array': return Array.clone(object);
+               case 'hash': return new Hash(object);
+               default: return object;
+       }
+};
+
+//</1.2compat>
+
+})();
+
+
+// Begin: Source/Types/Object.js
+/*
+---
+
+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('&');
+       }
+
+});
+
+})();
+
+//<1.2compat>
+
+Hash.implement({
+
+       has: Object.prototype.hasOwnProperty,
+
+       keyOf: function(value){
+               return Object.keyOf(this, value);
+       },
+
+       hasValue: function(value){
+               return Object.contains(this, value);
+       },
+
+       extend: function(properties){
+               Hash.each(properties || {}, function(value, key){
+                       Hash.set(this, key, value);
+               }, this);
+               return this;
+       },
+
+       combine: function(properties){
+               Hash.each(properties || {}, function(value, key){
+                       Hash.include(this, key, value);
+               }, this);
+               return this;
+       },
+
+       erase: function(key){
+               if (this.hasOwnProperty(key)) delete this[key];
+               return this;
+       },
+
+       get: function(key){
+               return (this.hasOwnProperty(key)) ? this[key] : null;
+       },
+
+       set: function(key, value){
+               if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+               return this;
+       },
+
+       empty: function(){
+               Hash.each(this, function(value, key){
+                       delete this[key];
+               }, this);
+               return this;
+       },
+
+       include: function(key, value){
+               if (this[key] == null) this[key] = value;
+               return this;
+       },
+
+       map: function(fn, bind){
+               return new Hash(Object.map(this, fn, bind));
+       },
+
+       filter: function(fn, bind){
+               return new Hash(Object.filter(this, fn, bind));
+       },
+
+       every: function(fn, bind){
+               return Object.every(this, fn, bind);
+       },
+
+       some: function(fn, bind){
+               return Object.some(this, fn, bind);
+       },
+
+       getKeys: function(){
+               return Object.keys(this);
+       },
+
+       getValues: function(){
+               return Object.values(this);
+       },
+
+       toQueryString: function(base){
+               return Object.toQueryString(this, base);
+       }
+
+});
+
+Hash.extend = Object.append;
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+//</1.2compat>
+
+
+// Begin: Source/Types/Array.js
+/*
+---
+
+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 value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+                       value = this[i];
+                       if (fn.call(bind, value, i, this)) results.push(value);
+               }
+               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('');
+       }
+
+});
+
+//<1.2compat>
+
+Array.alias('extend', 'append');
+
+var $pick = function(){
+       return Array.from(arguments).pick();
+};
+
+//</1.2compat>
+
+
+// Begin: Source/Types/Function.js
+/*
+---
+
+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);
+       }
+
+});
+
+//<1.2compat>
+
+delete Function.prototype.bind;
+
+Function.implement({
+
+       create: function(options){
+               var self = this;
+               options = options || {};
+               return function(event){
+                       var args = options.arguments;
+                       args = (args != null) ? Array.from(args) : Array.slice(arguments, (options.event) ? 1 : 0);
+                       if (options.event) args = [event || window.event].extend(args);
+                       var returns = function(){
+                               return self.apply(options.bind || null, args);
+                       };
+                       if (options.delay) return setTimeout(returns, options.delay);
+                       if (options.periodical) return setInterval(returns, options.periodical);
+                       if (options.attempt) return Function.attempt(returns);
+                       return returns();
+               };
+       },
+
+       bind: function(bind, args){
+               var self = this;
+               if (args != null) args = Array.from(args);
+               return function(){
+                       return self.apply(bind, args || arguments);
+               };
+       },
+
+       bindWithEvent: function(bind, args){
+               var self = this;
+               if (args != null) args = Array.from(args);
+               return function(event){
+                       return self.apply(bind, (args == null) ? arguments : [event].concat(args));
+               };
+       },
+
+       run: function(args, bind){
+               return this.apply(bind, Array.from(args));
+       }
+
+});
+
+if (Object.create == Function.prototype.create) Object.create = null;
+
+var $try = Function.attempt;
+
+//</1.2compat>
+
+
+// Begin: Source/Types/Number.js
+/*
+---
+
+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']);
+
+
+// Begin: Source/Types/String.js
+/*
+---
+
+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] : '';
+               });
+       }
+
+});
+
+
+// Begin: Source/Browser/Browser.js
+/*
+---
+
+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 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>*/
+
+//<1.2compat>
+
+if (Browser.Platform.ios) Browser.Platform.ipod = true;
+
+Browser.Engine = {};
+
+var setEngine = function(name, version){
+       Browser.Engine.name = name;
+       Browser.Engine[name + version] = true;
+       Browser.Engine.version = version;
+};
+
+if (Browser.ie){
+       Browser.Engine.trident = true;
+
+       switch (Browser.version){
+               case 6: setEngine('trident', 4); break;
+               case 7: setEngine('trident', 5); break;
+               case 8: setEngine('trident', 6);
+       }
+}
+
+if (Browser.firefox){
+       Browser.Engine.gecko = true;
+
+       if (Browser.version >= 3) setEngine('gecko', 19);
+       else setEngine('gecko', 18);
+}
+
+if (Browser.safari || Browser.chrome){
+       Browser.Engine.webkit = true;
+
+       switch (Browser.version){
+               case 2: setEngine('webkit', 419); break;
+               case 3: setEngine('webkit', 420); break;
+               case 4: setEngine('webkit', 525);
+       }
+}
+
+if (Browser.opera){
+       Browser.Engine.presto = true;
+
+       if (Browser.version >= 9.6) setEngine('presto', 960);
+       else if (Browser.version >= 9.5) setEngine('presto', 950);
+       else setEngine('presto', 925);
+}
+
+if (Browser.name == 'unknown'){
+       switch ((ua.match(/(?:webkit|khtml|gecko)/) || [])[0]){
+               case 'webkit':
+               case 'khtml':
+                       Browser.Engine.webkit = true;
+               break;
+               case 'gecko':
+                       Browser.Engine.gecko = true;
+       }
+}
+
+this.$exec = Browser.exec;
+
+//</1.2compat>
+
+})();
+
+
+// Begin: Source/Slick/Slick.Parser.js
+/*
+---
+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);
+
+
+// Begin: Source/Slick/Slick.Finder.js
+/*
+---
+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?
+       var nativeRootContains = root && this.isNativeCode(root.contains),
+               nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+       features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+               return context.contains(node);
+       } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+               // IE8 does not have .contains on document.
+               return context === node || ((context === document) ? document.documentElement : 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 = this.getAttribute(node, 'class');
+               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 = {
+
+       '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.7';
+
+// 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);
+
+
+// Begin: Source/Element/Element.js
+/*
+---
+
+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;
+       // IE8 and IE9 require the wrapping.
+       Element.prototype._fireEvent = (function(fireEvent){
+               return function(type, event){
+                       return fireEvent.call(this, type, event);
+               };
+       })(Element.prototype.fireEvent);
+}
+
+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 = {
+               '$constructor': Element,
+               '$family': Function.from('element').hide()
+       };
+
+       Element.mirror(function(name, method){
+               Element.Prototype[name] = method;
+       });
+}
+
+Element.Constructors = {};
+
+//<1.2compat>
+
+Element.Constructors = new Hash;
+
+//</1.2compat>
+
+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()
+
+});
+
+//<1.2compat>
+
+Elements.alias('extend', 'append');
+
+//</1.2compat>
+
+(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());
+
+Array.forEachMethod(function(method, name){
+       Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+    createElementAcceptsHTML = (document.createElement('<input name=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);
+       }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+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){
+                               Slick.uidOf(el);
+                               if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+                                       var fireEvent = el.fireEvent;
+                                       // wrapping needed in IE7, or else crash
+                                       el._fireEvent = function(type, event){
+                                               return fireEvent(type, event);
+                                       };
+                                       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.uniqueNumber) 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);
+
+//<1.2compat>
+
+Element.implement('hasChild', function(element){
+       return this !== element && this.contains(element);
+});
+
+(function(search, find, match){
+
+       this.Selectors = {};
+       var pseudos = this.Selectors.Pseudo = new Hash();
+
+       var addSlickPseudos = function(){
+               for (var name in pseudos) if (pseudos.hasOwnProperty(name)){
+                       Slick.definePseudo(name, pseudos[name]);
+                       delete pseudos[name];
+               }
+       };
+
+       Slick.search = function(context, expression, append){
+               addSlickPseudos();
+               return search.call(this, context, expression, append);
+       };
+
+       Slick.find = function(context, expression){
+               addSlickPseudos();
+               return find.call(this, context, expression);
+       };
+
+       Slick.match = function(node, selector){
+               addSlickPseudos();
+               return match.call(this, node, selector);
+       };
+
+})(Slick.search, Slick.find, Slick.match);
+
+//</1.2compat>
+
+// 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);
+       }
+
+});
+
+//<1.2compat>
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+       var elements = new Elements;
+       if (arguments.length == 1 && typeof selector == 'string') return Slick.search(this.document, selector, elements);
+       var args = Array.flatten(arguments);
+       for (var i = 0, l = args.length; i < l; i++){
+               var item = args[i];
+               switch (typeOf(item)){
+                       case 'element': elements.push(item); break;
+                       case 'string': Slick.search(this.document, item, elements);
+               }
+       }
+       return elements;
+});
+
+//</1.2compat>
+
+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);
+});
+
+// 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;
+
+//<1.2compat>
+
+Object.each(inserters, function(inserter, where){
+
+       where = where.capitalize();
+
+       var methods = {};
+
+       methods['inject' + where] = function(el){
+               inserter(this, document.id(el, true));
+               return this;
+       };
+
+       methods['grab' + where] = function(el){
+               inserter(document.id(el, true), this);
+               return this;
+       };
+
+       Element.implement(methods);
+
+});
+
+//</1.2compat>
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+       'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+       'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+       properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+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);
+       },
+
+       'value': function(node, value){
+               node.value = (value != null) ? value : '';
+       }
+
+});
+
+propertyGetters['class'] = function(node){
+       return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+       node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+       var value = node.value;
+       node.type = type;
+       node.value = value;
+};
+input = null;
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+       div.random = 'attribute';
+       return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+/* <ltIE9> */
+
+Element.implement({
+
+       setProperty: function(name, value){
+               var setter = propertySetters[name.toLowerCase()];
+               if (setter){
+                       setter(this, value);
+               } else {
+                       /* <ltIE9> */
+                       if (pollutesGetAttribute) var attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+                       /* </ltIE9> */
+
+                       if (value == null){
+                               this.removeAttribute(name);
+                               /* <ltIE9> */
+                               if (pollutesGetAttribute) delete attributeWhiteList[name];
+                               /* </ltIE9> */
+                       } else {
+                               this.setAttribute(name, '' + value);
+                               /* <ltIE9> */
+                               if (pollutesGetAttribute) attributeWhiteList[name] = true;
+                               /* </ltIE9> */
+                       }
+               }
+               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);
+               /* <ltIE9> */
+               if (pollutesGetAttribute){
+                       var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+                       if (!attr) return null;
+                       if (attr.expando && !attributeWhiteList[name]){
+                               var outer = this.outerHTML;
+                               // segment by the opening tag and find mention of attribute name
+                               if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+                               attributeWhiteList[name] = true;
+                       }
+               }
+               /* </ltIE9> */
+               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){
+               return this.setProperty(name, null);
+       },
+
+       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.uniqueNumber;
+       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('uniqueNumber');
+                               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[Slick.uidOf(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(Slick.uidOf(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(Slick.uidOf(this));
+               storage[property] = value;
+               return this;
+       },
+
+       eliminate: function(property){
+               var storage = get(Slick.uidOf(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 = {};
+
+//<1.2compat>
+
+Element.Properties = new Hash;
+
+//</1.2compat>
+
+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();
+       }
+
+};
+
+Element.Properties.html = {
+
+       set: function(html){
+               if (html == null) html = '';
+               else if (typeOf(html) == 'array') html = html.join('');
+               this.innerHTML = html;
+       },
+
+       erase: function(){
+               this.innerHTML = '';
+       }
+
+};
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+var supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+       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]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+var supportsTableInnerHTML = Function.attempt(function(){
+       var table = document.createElement('table');
+       table.innerHTML = '<tr><td></td></tr>';
+       return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+var supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+       Element.Properties.html.set = (function(set){
+
+               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;
+
+               return function(html){
+                       var wrap = translations[this.get('tag')];
+                       if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+                       if (!wrap) return set.call(this, html);
+
+                       var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+                       if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+                       wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+                       while (level--) target = target.firstChild;
+                       this.empty().adopt(target.childNodes);
+                       if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+                       wrapper = null;
+               };
+
+       })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<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');
+       }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+       set: function(id){
+               this.id = this.getAttributeNode('id').value = id;
+       },
+       get: function(){
+               return this.id || null;
+       },
+       erase: function(){
+               this.id = this.getAttributeNode('id').value = '';
+       }
+};
+/*</IE>*/
+
+})();
+
+
+// Begin: Source/Class/Class.js
+/*
+---
+
+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);
+       }
+};
+
+})();
+
+
+// Begin: Source/Class/Class.Extras.js
+/*
+---
+
+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);
+
+               /*<1.2compat>*/
+               if (fn == $empty) return this;
+               /*</1.2compat>*/
+
+               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;
+       }
+
+});
+
+})();
+
+
+// Begin: Source/Request/Request.js
+/*
+---
+
+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();
+               else 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;
+       }
+
+});
+
+})();
+
+
+// Begin: Source/Utilities/JSON.js
+/*
+---
+
+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 = {};
+
+//<1.2compat>
+
+JSON = new Hash({
+       stringify: JSON.stringify,
+       parse: JSON.parse
+});
+
+//</1.2compat>
+
+(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 + ')');
+};
+
+})();
+
+
+// Begin: Source/Request/Request.JSON.js
+/*
+---
+
+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);
+       }
+
+});
+
+
+// Begin: Source/Types/DOMEvent.js
+/*
+---
+
+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]/*<1.3compat>*/ || Object.keyOf(Event.Keys, code)/*</1.3compat>*/;
+               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 == 'DOMMouseScroll' || 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'
+});
+
+})();
+
+/*<1.3compat>*/
+var Event = DOMEvent;
+Event.Keys = {};
+/*</1.3compat>*/
+
+/*<1.2compat>*/
+
+Event.Keys = new Hash(Event.Keys);
+
+/*</1.2compat>*/
+
+
+// Begin: Source/Element/Element.Event.js
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+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, input: 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
+};
+
+Element.Events = {mousewheel: {
+       base: (Browser.firefox) ? 'DOMMouseScroll' : 'mousewheel'
+}};
+
+if ('onmouseenter' in document.documentElement){
+       Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+} else {
+       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
+       };
+
+       Element.Events.mouseleave = {
+               base: 'mouseout',
+               condition: check
+       };
+}
+
+/*<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' || (event.event.propertyName == 'checked' && this.checked);
+               }
+       }
+}
+/*</ltIE9>*/
+
+//<1.2compat>
+
+Element.Events = new Hash(Element.Events);
+
+//</1.2compat>
+
+})();
+
+
+// Begin: Source/Element/Element.Delegation.js
+/*
+---
+
+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, 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, target, uid){
+                       var 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, target);
+                       };
+                       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, target){
+                       var events = {blur: function(){
+                               this.removeEvents(events);
+                       }};
+                       events[type] = function(event){
+                               bubbleUp(self, match, fn, event, target);
+                       };
+                       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 + ')' : '');
+               });
+               old.call(this, type, fn);
+               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, target){
+                       if (!target && event && event.target) target = event.target;
+                       if (target) _map.listen(self, match, fn, event, target, uid);
+               } : function(event, target){
+                       if (!target && event && event.target) target = event.target;
+                       if (target) bubbleUp(self, match, fn, event, target);
+               };
+
+               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)
+});
+
+})();
+
+
+// Begin: Source/Fx/Fx.js
+/*
+---
+
+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]);
+               }
+       }
+};
+
+})();
+
+
+// Begin: Source/Element/Element.Style.js
+/*
+---
+
+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;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+var el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+el = null;
+//</ltIE9>
+
+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 || opacity == null ? 'visible' : 'hidden';
+};
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+       element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+       var style = element.style;
+       if (!element.currentStyle || !element.currentStyle.hasLayout) style.zoom = 1;
+       if (opacity == null || opacity == 1) opacity = '';
+       else opacity = 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')';
+       var filter = style.filter || element.getComputedStyle('filter') || '';
+       style.filter = reAlpha.test(filter) ? filter.replace(reAlpha, opacity) : filter + opacity;
+       if (!style.filter) style.removeAttribute('filter');
+} : 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'){
+                       if (value != null) value = parseFloat(value);
+                       setOpacity(this, 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;
+               //<ltIE9>
+               if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+                       this.style.removeAttribute(property);
+               }
+               //</ltIE9>
+               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){
+                       if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+                               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.ie && (/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+                               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: '@'
+};
+
+//<1.3compat>
+
+Element.implement({
+
+       setOpacity: function(value){
+               setOpacity(this, value);
+               return this;
+       },
+
+       getOpacity: function(){
+               return getOpacity(this);
+       }
+
+});
+
+Element.Properties.opacity = {
+
+       set: function(opacity){
+               setOpacity(this, opacity);
+               setVisibility(this, opacity);
+       },
+
+       get: function(){
+               return getOpacity(this);
+       }
+
+};
+
+//</1.3compat>
+
+//<1.2compat>
+
+Element.Styles = new Hash(Element.Styles);
+
+//</1.2compat>
+
+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(@, @, @)';
+});
+
+})();
+
+
+// Begin: Source/Fx/Fx.CSS.js
+/*
+---
+
+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);
+               var from = values[0], to = values[1];
+               if (to == null){
+                       to = from;
+                       from = element.getStyle(property);
+                       var unit = this.options.unit;
+                       // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+                       if (unit && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+                               element.setStyle(property, to + unit);
+                               var value = element.getComputedStyle(property);
+                               // IE and Opera support pixelLeft or pixelWidth
+                               if (!(/px$/.test(value))){
+                                       value = element.style[('pixel-' + property).camelCase()];
+                                       if (value == null){
+                                               // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+                                               var left = element.style.left;
+                                               element.style.left = to + unit;
+                                               value = element.style.pixelLeft;
+                                               element.style.left = left;
+                                       }
+                               }
+                               from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+                               element.setStyle(property, from + unit);
+                       }
+               }
+               return {from: this.parse(from), to: this.parse(to)};
+       },
+
+       //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;
+               }
+       }
+
+};
+
+//<1.2compat>
+
+Fx.CSS.Parsers = new Hash(Fx.CSS.Parsers);
+
+//</1.2compat>
+
+
+// Begin: Source/Fx/Fx.Morph.js
+/*
+---
+
+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;
+       }
+
+});
+
+
+// Begin: Source/Utilities/Swiff.js
+/*
+---
+
+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);
+};
+
+})();
+
+
+// Begin: Source/Fx/Fx.Transitions.js
+/*
+---
+
+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;
+       }
+
+};
+
+//<1.2compat>
+
+Fx.Transitions = new Hash(Fx.Transitions);
+
+//</1.2compat>
+
+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);
+       });
+});
+
+
+// Begin: Source/More/More.js
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+  - Guillermo Rauch
+  - Thomas Aylott
+  - Scott Kyle
+  - Arian Stolwijk
+  - Tim Wienk
+  - Christoph Pojer
+  - Aaron Newton
+  - Jacob Thornton
+
+requires:
+  - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+       version: '1.4.3.1dev',
+       build: '%build%'
+};
+
+
+// Begin: Source/Class/Class.Binds.js
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Class
+  - /MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+       if (!this.prototype.initialize) this.implement('initialize', function(){});
+       return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+       return function(){
+               Array.from(this.Binds).each(function(name){
+                       var original = this[name];
+                       if (original) this[name] = original.bind(this);
+               }, this);
+               return initialize.apply(this, arguments);
+       };
+};
+
+
+// Begin: Source/Class/Class.Occlude.js
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Class
+  - Core/Element
+  - /MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+       occlude: function(property, element){
+               element = document.id(element || this.element);
+               var instance = element.retrieve(property || this.property);
+               if (instance && !this.occluded)
+                       return (this.occluded = instance);
+
+               this.occluded = false;
+               element.store(property || this.property, this);
+               return this.occluded;
+       }
+
+});
+
+
+// Begin: Source/Element/Element.Dimensions.js
+/*
+---
+
+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;
+       }
+
+});
+
+
+// Begin: Source/Element/Element.Measure.js
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Element.Style
+  - Core/Element.Dimensions
+  - /MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+       var list = [];
+       Object.each(planes, function(directions){
+               Object.each(directions, function(edge){
+                       styles.each(function(style){
+                               list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+                       });
+               });
+       });
+       return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+       var total = 0;
+       Object.each(styles, function(value, style){
+               if (style.test(edge)) total = total + value.toInt();
+       });
+       return total;
+};
+
+var isVisible = function(el){
+       return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+       measure: function(fn){
+               if (isVisible(this)) return fn.call(this);
+               var parent = this.getParent(),
+                       toMeasure = [];
+               while (!isVisible(parent) && parent != document.body){
+                       toMeasure.push(parent.expose());
+                       parent = parent.getParent();
+               }
+               var restore = this.expose(),
+                       result = fn.call(this);
+               restore();
+               toMeasure.each(function(restore){
+                       restore();
+               });
+               return result;
+       },
+
+       expose: function(){
+               if (this.getStyle('display') != 'none') return function(){};
+               var before = this.style.cssText;
+               this.setStyles({
+                       display: 'block',
+                       position: 'absolute',
+                       visibility: 'hidden'
+               });
+               return function(){
+                       this.style.cssText = before;
+               }.bind(this);
+       },
+
+       getDimensions: function(options){
+               options = Object.merge({computeSize: false}, options);
+               var dim = {x: 0, y: 0};
+
+               var getSize = function(el, options){
+                       return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+               };
+
+               var parent = this.getParent('body');
+
+               if (parent && this.getStyle('display') == 'none'){
+                       dim = this.measure(function(){
+                               return getSize(this, options);
+                       });
+               } else if (parent){
+                       try { //safari sometimes crashes here, so catch it
+                               dim = getSize(this, options);
+                       }catch(e){}
+               }
+
+               return Object.append(dim, (dim.x || dim.x === 0) ? {
+                               width: dim.x,
+                               height: dim.y
+                       } : {
+                               x: dim.width,
+                               y: dim.height
+                       }
+               );
+       },
+
+       getComputedSize: function(options){
+               //<1.2compat>
+               //legacy support for my stupid spelling error
+               if (options && options.plains) options.planes = options.plains;
+               //</1.2compat>
+
+               options = Object.merge({
+                       styles: ['padding','border'],
+                       planes: {
+                               height: ['top','bottom'],
+                               width: ['left','right']
+                       },
+                       mode: 'both'
+               }, options);
+
+               var styles = {},
+                       size = {width: 0, height: 0},
+                       dimensions;
+
+               if (options.mode == 'vertical'){
+                       delete size.width;
+                       delete options.planes.width;
+               } else if (options.mode == 'horizontal'){
+                       delete size.height;
+                       delete options.planes.height;
+               }
+
+               getStylesList(options.styles, options.planes).each(function(style){
+                       styles[style] = this.getStyle(style).toInt();
+               }, this);
+
+               Object.each(options.planes, function(edges, plane){
+
+                       var capitalized = plane.capitalize(),
+                               style = this.getStyle(plane);
+
+                       if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+                       style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+                       size['total' + capitalized] = style;
+
+                       edges.each(function(edge){
+                               var edgesize = calculateEdgeSize(edge, styles);
+                               size['computed' + edge.capitalize()] = edgesize;
+                               size['total' + capitalized] += edgesize;
+                       });
+
+               }, this);
+
+               return Object.append(size, styles);
+       }
+
+});
+
+})();
+
+
+// Begin: Source/Element/Element.Position.js
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+  - Jacob Thornton
+
+requires:
+  - Core/Options
+  - Core/Element.Dimensions
+  - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+       options: {/*
+               edge: false,
+               returnPos: false,
+               minimum: {x: 0, y: 0},
+               maximum: {x: 0, y: 0},
+               relFixedPosition: false,
+               ignoreMargins: false,
+               ignoreScroll: false,
+               allowNegative: false,*/
+               relativeTo: document.body,
+               position: {
+                       x: 'center', //left, center, right
+                       y: 'center' //top, center, bottom
+               },
+               offset: {x: 0, y: 0}
+       },
+
+       getOptions: function(element, options){
+               options = Object.merge({}, local.options, options);
+               local.setPositionOption(options);
+               local.setEdgeOption(options);
+               local.setOffsetOption(element, options);
+               local.setDimensionsOption(element, options);
+               return options;
+       },
+
+       setPositionOption: function(options){
+               options.position = local.getCoordinateFromValue(options.position);
+       },
+
+       setEdgeOption: function(options){
+               var edgeOption = local.getCoordinateFromValue(options.edge);
+               options.edge = edgeOption ? edgeOption :
+                       (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+                       {x: 'left', y: 'top'};
+       },
+
+       setOffsetOption: function(element, options){
+               var parentOffset = {x: 0, y: 0},
+                       offsetParent = element.measure(function(){
+                               return document.id(this.getOffsetParent());
+                       }),
+                       parentScroll = offsetParent.getScroll();
+
+               if (!offsetParent || offsetParent == element.getDocument().body) return;
+               parentOffset = offsetParent.measure(function(){
+                       var position = this.getPosition();
+                       if (this.getStyle('position') == 'fixed'){
+                               var scroll = window.getScroll();
+                               position.x += scroll.x;
+                               position.y += scroll.y;
+                       }
+                       return position;
+               });
+
+               options.offset = {
+                       parentPositioned: offsetParent != document.id(options.relativeTo),
+                       x: options.offset.x - parentOffset.x + parentScroll.x,
+                       y: options.offset.y - parentOffset.y + parentScroll.y
+               };
+       },
+
+       setDimensionsOption: function(element, options){
+               options.dimensions = element.getDimensions({
+                       computeSize: true,
+                       styles: ['padding', 'border', 'margin']
+               });
+       },
+
+       getPosition: function(element, options){
+               var position = {};
+               options = local.getOptions(element, options);
+               var relativeTo = document.id(options.relativeTo) || document.body;
+
+               local.setPositionCoordinates(options, position, relativeTo);
+               if (options.edge) local.toEdge(position, options);
+
+               var offset = options.offset;
+               position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+               position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+               local.toMinMax(position, options);
+
+               if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+               if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+               if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+               position.left = Math.ceil(position.left);
+               position.top = Math.ceil(position.top);
+               delete position.x;
+               delete position.y;
+
+               return position;
+       },
+
+       setPositionCoordinates: function(options, position, relativeTo){
+               var offsetY = options.offset.y,
+                       offsetX = options.offset.x,
+                       calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+                       top = calc.y,
+                       left = calc.x,
+                       winSize = window.getSize();
+
+               switch(options.position.x){
+                       case 'left': position.x = left + offsetX; break;
+                       case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+                       default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+               }
+
+               switch(options.position.y){
+                       case 'top': position.y = top + offsetY; break;
+                       case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+                       default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+               }
+       },
+
+       toMinMax: function(position, options){
+               var xy = {left: 'x', top: 'y'}, value;
+               ['minimum', 'maximum'].each(function(minmax){
+                       ['left', 'top'].each(function(lr){
+                               value = options[minmax] ? options[minmax][xy[lr]] : null;
+                               if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+                       });
+               });
+       },
+
+       toRelFixedPosition: function(relativeTo, position){
+               var winScroll = window.getScroll();
+               position.top += winScroll.y;
+               position.left += winScroll.x;
+       },
+
+       toIgnoreScroll: function(relativeTo, position){
+               var relScroll = relativeTo.getScroll();
+               position.top -= relScroll.y;
+               position.left -= relScroll.x;
+       },
+
+       toIgnoreMargins: function(position, options){
+               position.left += options.edge.x == 'right'
+                       ? options.dimensions['margin-right']
+                       : (options.edge.x != 'center'
+                               ? -options.dimensions['margin-left']
+                               : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+               position.top += options.edge.y == 'bottom'
+                       ? options.dimensions['margin-bottom']
+                       : (options.edge.y != 'center'
+                               ? -options.dimensions['margin-top']
+                               : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+       },
+
+       toEdge: function(position, options){
+               var edgeOffset = {},
+                       dimensions = options.dimensions,
+                       edge = options.edge;
+
+               switch(edge.x){
+                       case 'left': edgeOffset.x = 0; break;
+                       case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+                       // center
+                       default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+               }
+
+               switch(edge.y){
+                       case 'top': edgeOffset.y = 0; break;
+                       case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+                       // center
+                       default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+               }
+
+               position.x += edgeOffset.x;
+               position.y += edgeOffset.y;
+       },
+
+       getCoordinateFromValue: function(option){
+               if (typeOf(option) != 'string') return option;
+               option = option.toLowerCase();
+
+               return {
+                       x: option.test('left') ? 'left'
+                               : (option.test('right') ? 'right' : 'center'),
+                       y: option.test(/upper|top/) ? 'top'
+                               : (option.test('bottom') ? 'bottom' : 'center')
+               };
+       }
+
+};
+
+Element.implement({
+
+       position: function(options){
+               if (options && (options.x != null || options.y != null)){
+                       return (original ? original.apply(this, arguments) : this);
+               }
+               var position = this.setStyle('position', 'absolute').calculatePosition(options);
+               return (options && options.returnPos) ? position : this.setStyles(position);
+       },
+
+       calculatePosition: function(options){
+               return local.getPosition(this, options);
+       }
+
+});
+
+})(Element.prototype.position);
+
+
+// Begin: Source/Element/Element.Shortcuts.js
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Element.Style
+  - /MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+       isDisplayed: function(){
+               return this.getStyle('display') != 'none';
+       },
+
+       isVisible: function(){
+               var w = this.offsetWidth,
+                       h = this.offsetHeight;
+               return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+       },
+
+       toggle: function(){
+               return this[this.isDisplayed() ? 'hide' : 'show']();
+       },
+
+       hide: function(){
+               var d;
+               try {
+                       //IE fails here if the element is not in the dom
+                       d = this.getStyle('display');
+               } catch(e){}
+               if (d == 'none') return this;
+               return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+       },
+
+       show: function(display){
+               if (!display && this.isDisplayed()) return this;
+               display = display || this.retrieve('element:_originalDisplay') || 'block';
+               return this.setStyle('display', (display == 'none') ? 'block' : display);
+       },
+
+       swapClass: function(remove, add){
+               return this.removeClass(remove).addClass(add);
+       }
+
+});
+
+Document.implement({
+
+       clearSelection: function(){
+               if (window.getSelection){
+                       var selection = window.getSelection();
+                       if (selection && selection.removeAllRanges) selection.removeAllRanges();
+               } else if (document.selection && document.selection.empty){
+                       try {
+                               //IE fails here if selected element is not in dom
+                               document.selection.empty();
+                       } catch(e){}
+               }
+       }
+
+});
+
+
+// Begin: Source/Forms/OverText.js
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Options
+  - Core/Events
+  - Core/Element.Event
+  - Class.Binds
+  - Class.Occlude
+  - Element.Position
+  - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+       Implements: [Options, Events, Class.Occlude],
+
+       Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+       options: {/*
+               textOverride: null,
+               onFocus: function(){},
+               onTextHide: function(textEl, inputEl){},
+               onTextShow: function(textEl, inputEl){}, */
+               element: 'label',
+               labelClass: 'overTxtLabel',
+               positionOptions: {
+                       position: 'upperLeft',
+                       edge: 'upperLeft',
+                       offset: {
+                               x: 4,
+                               y: 2
+                       }
+               },
+               poll: false,
+               pollInterval: 250,
+               wrap: false
+       },
+
+       property: 'OverText',
+
+       initialize: function(element, options){
+               element = this.element = document.id(element);
+
+               if (this.occlude()) return this.occluded;
+               this.setOptions(options);
+
+               this.attach(element);
+               OverText.instances.push(this);
+
+               if (this.options.poll) this.poll();
+       },
+
+       toElement: function(){
+               return this.element;
+       },
+
+       attach: function(){
+               var element = this.element,
+                       options = this.options,
+                       value = options.textOverride || element.get('alt') || element.get('title');
+
+               if (!value) return this;
+
+               var text = this.text = new Element(options.element, {
+                       'class': options.labelClass,
+                       styles: {
+                               lineHeight: 'normal',
+                               position: 'absolute',
+                               cursor: 'text'
+                       },
+                       html: value,
+                       events: {
+                               click: this.hide.pass(options.element == 'label', this)
+                       }
+               }).inject(element, 'after');
+
+               if (options.element == 'label'){
+                       if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+                       text.set('for', element.get('id'));
+               }
+
+               if (options.wrap){
+                       this.textHolder = new Element('div.overTxtWrapper', {
+                               styles: {
+                                       lineHeight: 'normal',
+                                       position: 'relative'
+                               }
+                       }).grab(text).inject(element, 'before');
+               }
+
+               return this.enable();
+       },
+
+       destroy: function(){
+               this.element.eliminate(this.property); // Class.Occlude storage
+               this.disable();
+               if (this.text) this.text.destroy();
+               if (this.textHolder) this.textHolder.destroy();
+               return this;
+       },
+
+       disable: function(){
+               this.element.removeEvents({
+                       focus: this.focus,
+                       blur: this.assert,
+                       change: this.assert
+               });
+               window.removeEvent('resize', this.reposition);
+               this.hide(true, true);
+               return this;
+       },
+
+       enable: function(){
+               this.element.addEvents({
+                       focus: this.focus,
+                       blur: this.assert,
+                       change: this.assert
+               });
+               window.addEvent('resize', this.reposition);
+               this.reposition();
+               return this;
+       },
+
+       wrap: function(){
+               if (this.options.element == 'label'){
+                       if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+                       this.text.set('for', this.element.get('id'));
+               }
+       },
+
+       startPolling: function(){
+               this.pollingPaused = false;
+               return this.poll();
+       },
+
+       poll: function(stop){
+               //start immediately
+               //pause on focus
+               //resumeon blur
+               if (this.poller && !stop) return this;
+               if (stop){
+                       clearInterval(this.poller);
+               } else {
+                       this.poller = (function(){
+                               if (!this.pollingPaused) this.assert(true);
+                       }).periodical(this.options.pollInterval, this);
+               }
+
+               return this;
+       },
+
+       stopPolling: function(){
+               this.pollingPaused = true;
+               return this.poll(true);
+       },
+
+       focus: function(){
+               if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+               return this.hide();
+       },
+
+       hide: function(suppressFocus, force){
+               if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+                       this.text.hide();
+                       this.fireEvent('textHide', [this.text, this.element]);
+                       this.pollingPaused = true;
+                       if (!suppressFocus){
+                               try {
+                                       this.element.fireEvent('focus');
+                                       this.element.focus();
+                               } catch(e){} //IE barfs if you call focus on hidden elements
+                       }
+               }
+               return this;
+       },
+
+       show: function(){
+               if (document.id(this.text) && !this.text.isDisplayed()){
+                       this.text.show();
+                       this.reposition();
+                       this.fireEvent('textShow', [this.text, this.element]);
+                       this.pollingPaused = false;
+               }
+               return this;
+       },
+
+       test: function(){
+               return !this.element.get('value');
+       },
+
+       assert: function(suppressFocus){
+               return this[this.test() ? 'show' : 'hide'](suppressFocus);
+       },
+
+       reposition: function(){
+               this.assert(true);
+               if (!this.element.isVisible()) return this.stopPolling().hide();
+               if (this.text && this.test()){
+                       this.text.position(Object.merge(this.options.positionOptions, {
+                               relativeTo: this.element
+                       }));
+               }
+               return this;
+       }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+       each: function(fn){
+               return OverText.instances.each(function(ot, i){
+                       if (ot.element && ot.text) fn.call(OverText, ot, i);
+               });
+       },
+
+       update: function(){
+
+               return OverText.each(function(ot){
+                       return ot.reposition();
+               });
+
+       },
+
+       hideAll: function(){
+
+               return OverText.each(function(ot){
+                       return ot.hide(true, true);
+               });
+
+       },
+
+       showAll: function(){
+               return OverText.each(function(ot){
+                       return ot.show();
+               });
+       }
+
+});
+
+
+
+// Begin: Source/Utilities/IframeShim.js
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Element.Event
+  - Core/Element.Style
+  - Core/Options
+  - Core/Events
+  - /Element.Position
+  - /Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+var IframeShim = new Class({
+
+       Implements: [Options, Events, Class.Occlude],
+
+       options: {
+               className: 'iframeShim',
+               src: 'javascript:false;document.write("");',
+               display: false,
+               zIndex: null,
+               margin: 0,
+               offset: {x: 0, y: 0},
+               browsers: (Browser.ie6 || (Browser.firefox && Browser.version < 3 && Browser.Platform.mac))
+       },
+
+       property: 'IframeShim',
+
+       initialize: function(element, options){
+               this.element = document.id(element);
+               if (this.occlude()) return this.occluded;
+               this.setOptions(options);
+               this.makeShim();
+               return this;
+       },
+
+       makeShim: function(){
+               if (this.options.browsers){
+                       var zIndex = this.element.getStyle('zIndex').toInt();
+
+                       if (!zIndex){
+                               zIndex = 1;
+                               var pos = this.element.getStyle('position');
+                               if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+                               this.element.setStyle('zIndex', zIndex);
+                       }
+                       zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+                       if (zIndex < 0) zIndex = 1;
+                       this.shim = new Element('iframe', {
+                               src: this.options.src,
+                               scrolling: 'no',
+                               frameborder: 0,
+                               styles: {
+                                       zIndex: zIndex,
+                                       position: 'absolute',
+                                       border: 'none',
+                                       filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+                               },
+                               'class': this.options.className
+                       }).store('IframeShim', this);
+                       var inject = (function(){
+                               this.shim.inject(this.element, 'after');
+                               this[this.options.display ? 'show' : 'hide']();
+                               this.fireEvent('inject');
+                       }).bind(this);
+                       if (!IframeShim.ready) window.addEvent('load', inject);
+                       else inject();
+               } else {
+                       this.position = this.hide = this.show = this.dispose = Function.from(this);
+               }
+       },
+
+       position: function(){
+               if (!IframeShim.ready || !this.shim) return this;
+               var size = this.element.measure(function(){
+                       return this.getSize();
+               });
+               if (this.options.margin != undefined){
+                       size.x = size.x - (this.options.margin * 2);
+                       size.y = size.y - (this.options.margin * 2);
+                       this.options.offset.x += this.options.margin;
+                       this.options.offset.y += this.options.margin;
+               }
+               this.shim.set({width: size.x, height: size.y}).position({
+                       relativeTo: this.element,
+                       offset: this.options.offset
+               });
+               return this;
+       },
+
+       hide: function(){
+               if (this.shim) this.shim.setStyle('display', 'none');
+               return this;
+       },
+
+       show: function(){
+               if (this.shim) this.shim.setStyle('display', 'block');
+               return this.position();
+       },
+
+       dispose: function(){
+               if (this.shim) this.shim.dispose();
+               return this;
+       },
+
+       destroy: function(){
+               if (this.shim) this.shim.destroy();
+               return this;
+       }
+
+});
+
+window.addEvent('load', function(){
+       IframeShim.ready = true;
+});
+
+
+// Begin: Source/Interface/Mask.js
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Options
+  - Core/Events
+  - Core/Element.Event
+  - /Class.Binds
+  - /Element.Position
+  - /IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+       Implements: [Options, Events],
+
+       Binds: ['position'],
+
+       options: {/*
+               onShow: function(){},
+               onHide: function(){},
+               onDestroy: function(){},
+               onClick: function(event){},
+               inject: {
+                       where: 'after',
+                       target: null,
+               },
+               hideOnClick: false,
+               id: null,
+               destroyOnHide: false,*/
+               style: {},
+               'class': 'mask',
+               maskMargins: false,
+               useIframeShim: true,
+               iframeShimOptions: {}
+       },
+
+       initialize: function(target, options){
+               this.target = document.id(target) || document.id(document.body);
+               this.target.store('mask', this);
+               this.setOptions(options);
+               this.render();
+               this.inject();
+       },
+
+       render: function(){
+               this.element = new Element('div', {
+                       'class': this.options['class'],
+                       id: this.options.id || 'mask-' + String.uniqueID(),
+                       styles: Object.merge({}, this.options.style, {
+                               display: 'none'
+                       }),
+                       events: {
+                               click: function(event){
+                                       this.fireEvent('click', event);
+                                       if (this.options.hideOnClick) this.hide();
+                               }.bind(this)
+                       }
+               });
+
+               this.hidden = true;
+       },
+
+       toElement: function(){
+               return this.element;
+       },
+
+       inject: function(target, where){
+               where = where || (this.options.inject ? this.options.inject.where : '') || this.target == document.body ? 'inside' : 'after';
+               target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+               this.element.inject(target, where);
+
+               if (this.options.useIframeShim){
+                       this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+                       this.addEvents({
+                               show: this.shim.show.bind(this.shim),
+                               hide: this.shim.hide.bind(this.shim),
+                               destroy: this.shim.destroy.bind(this.shim)
+                       });
+               }
+       },
+
+       position: function(){
+               this.resize(this.options.width, this.options.height);
+
+               this.element.position({
+                       relativeTo: this.target,
+                       position: 'topLeft',
+                       ignoreMargins: !this.options.maskMargins,
+                       ignoreScroll: this.target == document.body
+               });
+
+               return this;
+       },
+
+       resize: function(x, y){
+               var opt = {
+                       styles: ['padding', 'border']
+               };
+               if (this.options.maskMargins) opt.styles.push('margin');
+
+               var dim = this.target.getComputedSize(opt);
+               if (this.target == document.body){
+                       this.element.setStyles({width: 0, height: 0});
+                       var win = window.getScrollSize();
+                       if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+                       if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+               }
+               this.element.setStyles({
+                       width: Array.pick([x, dim.totalWidth, dim.x]),
+                       height: Array.pick([y, dim.totalHeight, dim.y])
+               });
+
+               return this;
+       },
+
+       show: function(){
+               if (!this.hidden) return this;
+
+               window.addEvent('resize', this.position);
+               this.position();
+               this.showMask.apply(this, arguments);
+
+               return this;
+       },
+
+       showMask: function(){
+               this.element.setStyle('display', 'block');
+               this.hidden = false;
+               this.fireEvent('show');
+       },
+
+       hide: function(){
+               if (this.hidden) return this;
+
+               window.removeEvent('resize', this.position);
+               this.hideMask.apply(this, arguments);
+               if (this.options.destroyOnHide) return this.destroy();
+
+               return this;
+       },
+
+       hideMask: function(){
+               this.element.setStyle('display', 'none');
+               this.hidden = true;
+               this.fireEvent('hide');
+       },
+
+       toggle: function(){
+               this[this.hidden ? 'show' : 'hide']();
+       },
+
+       destroy: function(){
+               this.hide();
+               this.element.destroy();
+               this.fireEvent('destroy');
+               this.target.eliminate('mask');
+       }
+
+});
+
+Element.Properties.mask = {
+
+       set: function(options){
+               var mask = this.retrieve('mask');
+               if (mask) mask.destroy();
+               return this.eliminate('mask').store('mask:options', options);
+       },
+
+       get: function(){
+               var mask = this.retrieve('mask');
+               if (!mask){
+                       mask = new Mask(this, this.retrieve('mask:options'));
+                       this.store('mask', mask);
+               }
+               return mask;
+       }
+
+};
+
+Element.implement({
+
+       mask: function(options){
+               if (options) this.set('mask', options);
+               this.get('mask').show();
+               return this;
+       },
+
+       unmask: function(){
+               this.get('mask').hide();
+               return this;
+       }
+
+});
+
+
+// Begin: Source/Utilities/DOMReady.js
+/*
+---
+
+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);
+
+
+// Begin: Source/Element/Element.Pin.js
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Element.Event
+  - Core/Element.Dimensions
+  - Core/Element.Style
+  - /MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+       var supportsPositionFixed = false,
+               supportTested = false;
+
+       var testPositionFixed = function(){
+               var test = new Element('div').setStyles({
+                       position: 'fixed',
+                       top: 0,
+                       right: 0
+               }).inject(document.body);
+               supportsPositionFixed = (test.offsetTop === 0);
+               test.dispose();
+               supportTested = true;
+       };
+
+       Element.implement({
+
+               pin: function(enable, forceScroll){
+                       if (!supportTested) testPositionFixed();
+                       if (this.getStyle('display') == 'none') return this;
+
+                       var pinnedPosition,
+                               scroll = window.getScroll(),
+                               parent,
+                               scrollFixer;
+
+                       if (enable !== false){
+                               pinnedPosition = this.getPosition(supportsPositionFixed ? document.body : this.getOffsetParent());
+                               if (!this.retrieve('pin:_pinned')){
+                                       var currentPosition = {
+                                               top: pinnedPosition.y - scroll.y,
+                                               left: pinnedPosition.x - scroll.x
+                                       };
+
+                                       if (supportsPositionFixed && !forceScroll){
+                                               this.setStyle('position', 'fixed').setStyles(currentPosition);
+                                       } else {
+
+                                               parent = this.getOffsetParent();
+                                               var position = this.getPosition(parent),
+                                                       styles = this.getStyles('left', 'top');
+
+                                               if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+                                               if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+                                               position = {
+                                                       x: styles.left.toInt() - scroll.x,
+                                                       y: styles.top.toInt() - scroll.y
+                                               };
+
+                                               scrollFixer = function(){
+                                                       if (!this.retrieve('pin:_pinned')) return;
+                                                       var scroll = window.getScroll();
+                                                       this.setStyles({
+                                                               left: position.x + scroll.x,
+                                                               top: position.y + scroll.y
+                                                       });
+                                               }.bind(this);
+
+                                               this.store('pin:_scrollFixer', scrollFixer);
+                                               window.addEvent('scroll', scrollFixer);
+                                       }
+                                       this.store('pin:_pinned', true);
+                               }
+
+                       } else {
+                               if (!this.retrieve('pin:_pinned')) return this;
+
+                               parent = this.getParent();
+                               var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+                               pinnedPosition = this.getPosition(offsetParent);
+
+                               this.store('pin:_pinned', false);
+                               scrollFixer = this.retrieve('pin:_scrollFixer');
+                               if (!scrollFixer){
+                                       this.setStyles({
+                                               position: 'absolute',
+                                               top: pinnedPosition.y + scroll.y,
+                                               left: pinnedPosition.x + scroll.x
+                                       });
+                               } else {
+                                       this.store('pin:_scrollFixer', null);
+                                       window.removeEvent('scroll', scrollFixer);
+                               }
+                               this.removeClass('isPinned');
+                       }
+                       return this;
+               },
+
+               unpin: function(){
+                       return this.pin(false);
+               },
+
+               togglePin: function(){
+                       return this.pin(!this.retrieve('pin:_pinned'));
+               }
+
+       });
+
+//<1.2compat>
+Element.alias('togglepin', 'togglePin');
+//</1.2compat>
+
+})();
+
+
+// Begin: Source/Types/Object.Extras.js
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Object
+  - /MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+       return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+       getFromPath: function(source, parts){
+               if (typeof parts == 'string') parts = parts.split('.');
+               for (var i = 0, l = parts.length; i < l; i++){
+                       if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+                       else return null;
+               }
+               return source;
+       },
+
+       cleanValues: function(object, method){
+               method = method || defined;
+               for (var key in object) if (!method(object[key])){
+                       delete object[key];
+               }
+               return object;
+       },
+
+       erase: function(object, key){
+               if (hasOwnProperty.call(object, key)) delete object[key];
+               return object;
+       },
+
+       run: function(object){
+               var args = Array.slice(arguments, 1);
+               for (var key in object) if (object[key].apply){
+                       object[key].apply(object, args);
+               }
+               return object;
+       }
+
+});
+
+})();
+
+
+// Begin: Source/Core/Clientcide.js
+/*
+---
+
+name: Clientcide
+
+description: The Clientcide namespace.
+
+license: MIT-style license.
+
+provides: Clientcide
+
+...
+*/
+var Clientcide = {
+       version: '3.0.10',
+       assetLocation: "http://github.com/anutron/clientcide/raw/master/Assets",
+       setAssetLocation: function(baseHref) {
+               Clientcide.assetLocation = baseHref;
+               if (Clientcide.preloaded) Clientcide.preLoadCss();
+       },
+       preLoadCss: function(){
+               if (window.StickyWin && StickyWin.ui) StickyWin.ui();
+               if (window.StickyWin && StickyWin.pointy) StickyWin.pointy();
+               Clientcide.preloaded = true;
+               return true;
+       },
+       preloaded: false
+};
+(function(){
+       if (!window.addEvent) return;
+       var preload = function(){
+               if (window.dbug) dbug.log('preloading clientcide css');
+               if (!Clientcide.preloaded) Clientcide.preLoadCss();
+       };
+       window.addEvent('domready', preload);
+       window.addEvent('load', preload);
+})();
+setCNETAssetBaseHref = Clientcide.setAssetLocation;
+
+
+// Begin: Source/Core/dbug.js
+/*
+---
+
+name: dbug
+
+description: A wrapper for Firebug console.* statements.
+
+license: MIT-style license.
+
+authors:
+- Aaron Newton
+
+provides: dbug
+
+...
+*/
+var dbug = {
+       logged: [],
+       timers: {},
+       firebug: false,
+       enabled: false,
+       log: function() {
+               dbug.logged.push(arguments);
+       },
+       nolog: function(msg) {
+               dbug.logged.push(arguments);
+       },
+       time: function(name){
+               dbug.timers[name] = new Date().getTime();
+       },
+       timeEnd: function(name){
+               if (dbug.timers[name]) {
+                       var end = new Date().getTime() - dbug.timers[name];
+                       dbug.timers[name] = false;
+                       dbug.log('%s: %s', name, end);
+               } else dbug.log('no such timer: %s', name);
+       },
+       enable: function(silent) {
+               var con = window.firebug ? firebug.d.console.cmd : window.console;
+
+               if((!!window.console && !!window.console.warn) || window.firebug) {
+                       try {
+                               dbug.enabled = true;
+                               dbug.log = function(){
+                                               try {
+                                                       (con.debug || con.log).apply(con, arguments);
+                                               } catch(e) {
+                                                       console.log(Array.slice(arguments));
+                                               }
+                               };
+                               dbug.time = function(){
+                                       con.time.apply(con, arguments);
+                               };
+                               dbug.timeEnd = function(){
+                                       con.timeEnd.apply(con, arguments);
+                               };
+                               if(!silent) dbug.log('enabling dbug');
+                               for(var i=0;i<dbug.logged.length;i++){ dbug.log.apply(con, dbug.logged[i]); }
+                               dbug.logged=[];
+                       } catch(e) {
+                               dbug.enable.delay(400);
+                       }
+               }
+       },
+       disable: function(){
+               if(dbug.firebug) dbug.enabled = false;
+               dbug.log = dbug.nolog;
+               dbug.time = function(){};
+               dbug.timeEnd = function(){};
+       },
+       cookie: function(set){
+               var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
+               var debugCookie = value ? unescape(value[1]) : false;
+               if((set == null && debugCookie != 'true') || (set != null && set)) {
+                       dbug.enable();
+                       dbug.log('setting debugging cookie');
+                       var date = new Date();
+                       date.setTime(date.getTime()+(24*60*60*1000));
+                       document.cookie = 'jsdebug=true;expires='+date.toGMTString()+';path=/;';
+               } else dbug.disableCookie();
+       },
+       disableCookie: function(){
+               dbug.log('disabling debugging cookie');
+               document.cookie = 'jsdebug=false;path=/;';
+       },
+       conditional: function(fn, fnIfError) {
+               if (dbug.enabled) {
+                       return fn();
+               } else {
+                       try {
+                               return fn();
+                       } catch(e) {
+                               if (fnIfError) fnIfError(e);
+                       }
+               }
+       }
+};
+
+(function(){
+       var fb = !!window.console || !!window.firebug;
+       var con = window.firebug ? window.firebug.d.console.cmd : window.console;
+       var debugMethods = ['debug','info','warn','error','assert','dir','dirxml'];
+       var otherMethods = ['trace','group','groupEnd','profile','profileEnd','count'];
+       function set(methodList, defaultFunction) {
+
+               var getLogger = function(method) {
+                       return function(){
+                               con[method].apply(con, arguments);
+                       };
+               };
+
+               for(var i = 0; i < methodList.length; i++){
+                       var method = methodList[i];
+                       if (fb && con[method]) {
+                               dbug[method] = getLogger(method);
+                       } else {
+                               dbug[method] = defaultFunction;
+                       }
+               }
+       };
+       set(debugMethods, dbug.log);
+       set(otherMethods, function(){});
+})();
+if ((!!window.console && !!window.console.warn) || window.firebug){
+       dbug.firebug = true;
+       var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
+       var debugCookie = value ? unescape(value[1]) : false;
+       if(window.location.href.indexOf("jsdebug=true")>0 || debugCookie=='true') dbug.enable();
+       if(debugCookie=='true')dbug.log('debugging cookie enabled');
+       if(window.location.href.indexOf("jsdebugCookie=true")>0){
+               dbug.cookie();
+               if(!dbug.enabled)dbug.enable();
+       }
+       if(window.location.href.indexOf("jsdebugCookie=false")>0)dbug.disableCookie();
+}
+
+
+// Begin: Source/UI/StyleWriter.js
+/*
+---
+name: StyleWriter
+
+description: Provides a simple method for injecting a css style element into the DOM if it's not already present.
+
+license: MIT-Style License
+
+requires: [Core/Class, Core/DomReady, Core/Element, dbug]
+
+provides: StyleWriter
+
+...
+*/
+
+var StyleWriter = new Class({
+       createStyle: function(css, id) {
+               window.addEvent('domready', function(){
+                       try {
+                               if (document.id(id) && id) return;
+                               var style = new Element('style', {id: id||''}).inject($$('head')[0]);
+                               if (Browser.ie) style.styleSheet.cssText = css;
+                               else style.set('text', css);
+                       }catch(e){dbug.log('error: %s',e);}
+               }.bind(this));
+       }
+});
+
+// Begin: Source/UI/StickyWin.js
+/*
+---
+
+name: StickyWin
+
+description: Creates a div within the page with the specified contents at the location relative to the element you specify; basically an in-page popup maker.
+
+license: MIT-Style License
+
+requires: [
+  Core/DomReady,
+  Core/Slick.Finder,
+  More/Element.Position,
+  More/Class.Binds,
+  More/Element.Shortcuts,
+  More/Element.Pin,
+  More/IframeShim,
+  More/Object.Extras,
+  Clientcide,
+  StyleWriter
+]
+
+provides: [StickyWin, StickyWin.Stacker]
+...
+*/
+
+
+var StickyWin = new Class({
+       Binds: ['destroy', 'hide', 'togglepin', 'esc'],
+       Implements: [Options, Events, StyleWriter],
+       options: {
+//             onDisplay: function(){},
+//             onClose: function(){},
+//             onDestroy: function(){},
+               closeClassName: 'closeSticky',
+               pinClassName: 'pinSticky',
+               content: '',
+               zIndex: 10000,
+               className: '',
+//             id: ... set above in initialize function
+/*     these are the defaults for Element.position anyway
+               ************************************************
+               edge: false, //see Element.position
+               position: 'center', //center, corner == upperLeft, upperRight, bottomLeft, bottomRight
+               offset: {x:0,y:0},
+               relativeTo: document.body, */
+               width: false,
+               height: false,
+               timeout: -1,
+               allowMultipleByClass: true,
+               allowMultiple: true,
+               showNow: true,
+               useIframeShim: true,
+               iframeShimSelector: '',
+               destroyOnClose: false,
+               closeOnClickOut: false,
+               closeOnEsc: false,
+               getWindowManager: function(){ return StickyWin.WM; }
+       },
+
+       css: '.SWclearfix:after {content: "."; display: block; height: 0; clear: both; visibility: hidden;}'+
+                '.SWclearfix {display: inline-table;} * html .SWclearfix {height: 1%;} .SWclearfix {display: block;}',
+
+       initialize: function(options){
+               this.options.inject = this.options.inject || {
+                       target: document.body,
+                       where: 'bottom'
+               };
+               this.setOptions(options);
+               this.windowManager = this.options.getWindowManager();
+               this.id = this.options.id || 'StickyWin_'+new Date().getTime();
+               this.makeWindow();
+               if (this.windowManager) this.windowManager.add(this);
+
+               if (this.options.content) this.setContent(this.options.content);
+               if (this.options.timeout > 0) {
+                       this.addEvent('onDisplay', function(){
+                               this.hide.delay(this.options.timeout, this);
+                       }.bind(this));
+               }
+               //add css for clearfix
+               this.createStyle(this.css, 'StickyWinClearFix');
+               if (this.options.closeOnClickOut || this.options.closeOnEsc) this.attach();
+               if (this.options.destroyOnClose) this.addEvent('close', this.destroy);
+               if (this.options.showNow) this.show();
+       },
+       toElement: function(){
+               return this.element;
+       },
+       attach: function(dettach){
+               var method = dettach ? 'removeEvents' : 'addEvents';
+               var events = {};
+               if (this.options.closeOnClickOut) events.click = this.esc;
+               if (this.options.closeOnEsc) events.keyup = this.esc;
+               document[method](events);
+       },
+       esc: function(e) {
+               if (e.key == "esc") this.hide();
+               if (e.type == "click" && this.element != e.target && !this.element.contains(e.target)) this.hide();
+       },
+       makeWindow: function(){
+               this.destroyOthers();
+               if (!document.id(this.id)) {
+                       this.win = new Element('div', {
+                               id: this.id
+                       }).addClass(this.options.className).addClass('StickyWinInstance').addClass('SWclearfix').setStyles({
+                               display: 'none',
+                               position: 'absolute',
+                               zIndex: this.options.zIndex
+                       }).inject(this.options.inject.target, this.options.inject.where).store('StickyWin', this);
+               } else this.win = document.id(this.id);
+               this.element = this.win;
+               if (this.options.width && typeOf(this.options.width.toInt())=="number") this.win.setStyle('width', this.options.width.toInt());
+               if (this.options.height && typeOf(this.options.height.toInt())=="number") this.win.setStyle('height', this.options.height.toInt());
+               return this;
+       },
+       show: function(suppressEvent){
+               this.showWin();
+               if (!suppressEvent) this.fireEvent('display');
+               if (this.options.useIframeShim) this.showIframeShim();
+               this.visible = true;
+               return this;
+       },
+       showWin: function(){
+               if (this.windowManager) this.windowManager.focus(this);
+               if (!this.positioned) this.position();
+               this.win.show();
+       },
+       hide: function(suppressEvent){
+               if (typeOf(suppressEvent) == "event" || !suppressEvent) this.fireEvent('close');
+               this.hideWin();
+               if (this.options.useIframeShim) this.hideIframeShim();
+               this.visible = false;
+               return this;
+       },
+       hideWin: function(){
+               this.win.setStyle('display','none');
+       },
+       destroyOthers: function() {
+               if (!this.options.allowMultipleByClass || !this.options.allowMultiple) {
+                       $$('div.StickyWinInstance').each(function(sw) {
+                               if (!this.options.allowMultiple || (!this.options.allowMultipleByClass && sw.hasClass(this.options.className)))
+                                       sw.retrieve('StickyWin').destroy();
+                       }, this);
+               }
+       },
+       setContent: function(html) {
+               if (this.win.getChildren().length>0) this.win.empty();
+               if (typeOf(html) == "string") this.win.set('html', html);
+               else if (document.id(html)) this.win.adopt(html);
+               this.win.getElements('.'+this.options.closeClassName).each(function(el){
+                       el.addEvent('click', this.hide);
+               }, this);
+               this.win.getElements('.'+this.options.pinClassName).each(function(el){
+                       el.addEvent('click', this.togglepin);
+               }, this);
+               return this;
+       },
+       position: function(options){
+               this.positioned = true;
+               this.setOptions(options);
+               this.win.position(
+                       Object.cleanValues({
+                               allowNegative: [this.options.allowNegative, this.options.relativeTo != document.body].pick(),
+                               relativeTo: this.options.relativeTo,
+                               position: this.options.position,
+                               offset: this.options.offset,
+                               edge: this.options.edge
+                       })
+               );
+               if (this.shim) this.shim.position();
+               return this;
+       },
+       pin: function(pin) {
+               if (!this.win.pin) {
+                       dbug.log('you must include element.pin.js!');
+                       return this;
+               }
+               this.pinned = pin != null && pin;
+               this.win.pin(pin);
+               return this;
+       },
+       unpin: function(){
+               return this.pin(false);
+       },
+       togglepin: function(){
+               return this.pin(!this.pinned);
+       },
+       makeIframeShim: function(){
+               if (!this.shim){
+                       var el = (this.options.iframeShimSelector)?this.win.getElement(this.options.iframeShimSelector):this.win;
+                       this.shim = new IframeShim(el, {
+                               display: false,
+                               name: 'StickyWinShim'
+                       });
+               }
+       },
+       showIframeShim: function(){
+               if (this.options.useIframeShim) {
+                       this.makeIframeShim();
+                       this.shim.show();
+               }
+       },
+       hideIframeShim: function(){
+               if (this.shim) this.shim.hide();
+       },
+       destroy: function(){
+               this.destroyed = true;
+               this.attach(true);
+               if (this.windowManager) this.windowManager.remove(this);
+               if (this.win) this.win.destroy();
+               if (this.options.useIframeShim && this.shim) this.shim.destroy();
+               if (document.id('modalOverlay')) document.id('modalOverlay').destroy();
+               this.fireEvent('destroy');
+       }
+});
+
+StickyWin.Stacker = new Class({
+       Implements: [Options, Events],
+       Binds: ['click'],
+       instances: [],
+       options: {
+               zIndexBase: 9000
+       },
+       initialize: function(options) {
+               this.setOptions(options);
+       },
+       add: function(sw) {
+               this.instances.include(sw);
+               $(sw).addEvent('mousedown', this.click);
+       },
+       click: function(e) {
+               this.instances.each(function(sw){
+                       var el = $(sw);
+                       if (el == e.target || el.contains($(e.target))) this.focus(sw);
+               }, this);
+       },
+       focus: function(instance){
+               if (this.focused == instance) return;
+               this.focused = instance;
+               if (instance) this.instances.erase(instance).push(instance);
+               this.instances.each(function(current, i){
+                       $(current).setStyle('z-index', this.options.zIndexBase + i);
+               }, this);
+               this.focused = instance;
+       },
+       remove: function(sw) {
+               this.instances.erase(sw);
+               $(sw).removeEvent('click', this.click);
+       }
+});
+StickyWin.WM = new StickyWin.Stacker();
+
+// Begin: Source/UI/StickyWin.Modal.js
+/*
+---
+
+name: StickyWin.Modal
+
+description: This script extends StickyWin and StickyWin.Fx classes to add Mask functionality.
+
+license: MIT-Style License
+
+requires: [More/Mask, StickyWin]
+
+provides: StickyWin.Modal
+...
+*/
+StickyWin.Modal = new Class({
+
+       Extends: StickyWin,
+
+       options: {
+               modalize: true,
+               maskOptions: {
+                       style: {
+                               'background-color':'#333',
+                               opacity:0.8
+                       }
+               },
+               hideOnClick: true,
+               getWindowManager: function(){ return StickyWin.ModalWM; }
+       },
+
+       initialize: function(options) {
+               this.options.maskTarget = this.options.maskTarget || document.body;
+               this.setOptions(options);
+               this.mask = new Mask(this.options.maskTarget, this.options.maskOptions).addEvent('click', function() {
+                       if (this.options.hideOnClick) this.hide();
+               }.bind(this));
+               this.parent(options);
+       },
+
+       show: function(showModal){
+               if ([showModal, this.options.modalize].pick()) this.mask.show();
+               this.parent();
+       },
+
+       hide: function(hideModal){
+               if ([hideModal, true].pick()) this.mask.hide();
+               this.parent();
+       },
+
+       destroy: function(){
+               this.mask.destroy();
+               this.parent.apply(this, arguments);
+       }
+
+});
+
+StickyWin.ModalWM = new StickyWin.Stacker({
+       zIndexBase: 11000
+});
+if (StickyWin.Fx) StickyWin.Fx.Modal = StickyWin.Modal;
+
+// Begin: Source/Types/String.Extras.js
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+  - Guillermo Rauch
+  - Christopher Pitt
+
+requires:
+  - Core/String
+  - Core/Array
+  - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+       'a': /[àáâãäåăą]/g,
+       'A': /[ÀÁÂÃÄÅĂĄ]/g,
+       'c': /[ćčç]/g,
+       'C': /[ĆČÇ]/g,
+       'd': /[ďđ]/g,
+       'D': /[ĎÐ]/g,
+       'e': /[èéêëěę]/g,
+       'E': /[ÈÉÊËĚĘ]/g,
+       'g': /[ğ]/g,
+       'G': /[Ğ]/g,
+       'i': /[ìíîï]/g,
+       'I': /[ÌÍÎÏ]/g,
+       'l': /[ĺľł]/g,
+       'L': /[ĹĽŁ]/g,
+       'n': /[ñňń]/g,
+       'N': /[ÑŇŃ]/g,
+       'o': /[òóôõöøő]/g,
+       'O': /[ÒÓÔÕÖØ]/g,
+       'r': /[řŕ]/g,
+       'R': /[ŘŔ]/g,
+       's': /[ššş]/g,
+       'S': /[ŠŞŚ]/g,
+       't': /[ťţ]/g,
+       'T': /[ŤŢ]/g,
+       'ue': /[ü]/g,
+       'UE': /[Ü]/g,
+       'u': /[ùúûůµ]/g,
+       'U': /[ÙÚÛŮ]/g,
+       'y': /[ÿý]/g,
+       'Y': /[ŸÝ]/g,
+       'z': /[žźż]/g,
+       'Z': /[ŽŹŻ]/g,
+       'th': /[þ]/g,
+       'TH': /[Þ]/g,
+       'dh': /[ð]/g,
+       'DH': /[Ð]/g,
+       'ss': /[ß]/g,
+       'oe': /[œ]/g,
+       'OE': /[Œ]/g,
+       'ae': /[æ]/g,
+       'AE': /[Æ]/g
+},
+
+tidy = {
+       ' ': /[\xa0\u2002\u2003\u2009]/g,
+       '*': /[\xb7]/g,
+       '\'': /[\u2018\u2019]/g,
+       '"': /[\u201c\u201d]/g,
+       '...': /[\u2026]/g,
+       '-': /[\u2013]/g,
+//     '--': /[\u2014]/g,
+       '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+       ms: 1,
+       s: 1000,
+       m: 6e4,
+       h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+       var result = string, key;
+       for (key in replacements) result = result.replace(replacements[key], key);
+       return result;
+};
+
+var getRegexForTag = function(tag, contents){
+       tag = tag || '';
+       var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+               reg = new RegExp(regstr, "gi");
+       return reg;
+};
+
+String.implement({
+
+       standardize: function(){
+               return walk(this, special);
+       },
+
+       repeat: function(times){
+               return new Array(times + 1).join(this);
+       },
+
+       pad: function(length, str, direction){
+               if (this.length >= length) return this;
+
+               var pad = (str == null ? ' ' : '' + str)
+                       .repeat(length - this.length)
+                       .substr(0, length - this.length);
+
+               if (!direction || direction == 'right') return this + pad;
+               if (direction == 'left') return pad + this;
+
+               return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+       },
+
+       getTags: function(tag, contents){
+               return this.match(getRegexForTag(tag, contents)) || [];
+       },
+
+       stripTags: function(tag, contents){
+               return this.replace(getRegexForTag(tag, contents), '');
+       },
+
+       tidy: function(){
+               return walk(this, tidy);
+       },
+
+       truncate: function(max, trail, atChar){
+               var string = this;
+               if (trail == null && arguments.length == 1) trail = '…';
+               if (string.length > max){
+                       string = string.substring(0, max);
+                       if (atChar){
+                               var index = string.lastIndexOf(atChar);
+                               if (index != -1) string = string.substr(0, index);
+                       }
+                       if (trail) string += trail;
+               }
+               return string;
+       },
+
+       ms: function(){
+         // "Borrowed" from https://gist.github.com/1503944
+               var units = findUnits.exec(this);
+               if (units == null) return Number(this);
+               return Number(units[1]) * conversions[units[2]];
+       }
+
+});
+
+})();
+
+
+// Begin: Source/UI/StickyWin.UI.js
+ /*
+---
+name: StickyWin.UI
+
+description: Creates an html holder for in-page popups using a default style.
+
+license: MIT-Style License
+
+requires: [Core/Element.Style, More/String.Extras, StyleWriter, StickyWin]
+
+provides: StickyWin.UI
+...
+*/
+StickyWin.UI = new Class({
+       Implements: [Options, StyleWriter],
+       options: {
+               width: 300,
+               css: "div.DefaultStickyWin {font-family:verdana; font-size:11px; line-height: 13px;position: relative;}"+
+                       "div.DefaultStickyWin div.top{-moz-user-select: none;-khtml-user-select: none;}"+
+                       "div.DefaultStickyWin div.top_ul{background:url({%baseHref%}full.png) top left no-repeat; height:30px; width:15px; float:left}"+
+                       "div.DefaultStickyWin div.top_ur{position:relative; left:0px !important; left:-4px; background:url({%baseHref%}full.png) top right !important; height:30px; margin:0px 0px 0px 15px !important; margin-right:-4px; padding:0px}"+
+                       "div.DefaultStickyWin h1.caption{clear: none !important; margin:0px !important; overflow: hidden; padding:0 !important; font-weight:bold; color:#555; font-size:14px !important; position:relative; top:8px !important; left:5px !important; float: left; height: 22px !important;}"+
+                       "div.DefaultStickyWin div.middle, div.DefaultStickyWin div.closeBody {background:url({%baseHref%}body.png) top left repeat-y; margin:0px 20px 0px 0px !important;       margin-bottom: -3px; position: relative;        top: 0px !important; top: -3px;}"+
+                       "div.DefaultStickyWin div.body{background:url({%baseHref%}body.png) top right repeat-y; padding:8px 23px 8px 0px !important; margin-left:5px !important; position:relative; right:-20px !important; z-index: 1;}"+
+                       "div.DefaultStickyWin div.bottom{clear:both;}"+
+                       "div.DefaultStickyWin div.bottom_ll{background:url({%baseHref%}full.png) bottom left no-repeat; width:15px; height:15px; float:left}"+
+                       "div.DefaultStickyWin div.bottom_lr{background:url({%baseHref%}full.png) bottom right; position:relative; left:0px !important; left:-4px; margin:0px 0px 0px 15px !important; margin-right:-4px; height:15px}"+
+                       "div.DefaultStickyWin div.closeButtons{text-align: center; background:url({%baseHref%}body.png) top right repeat-y; padding: 4px 30px 8px 0px; margin-left:5px; position:relative; right:-20px}"+
+                       "div.DefaultStickyWin a.button:hover{background:url({%baseHref%}big_button_over.gif) repeat-x}"+
+                       "div.DefaultStickyWin a.button {background:url({%baseHref%}big_button.gif) repeat-x; margin: 2px 8px 2px 8px; padding: 2px 12px; cursor:pointer; border: 1px solid #999 !important; text-decoration:none; color: #000 !important;}"+
+                       "div.DefaultStickyWin div.closeButton{width:13px; height:13px; background:url({%baseHref%}closebtn.gif) no-repeat; position: absolute; right: 0px; margin:10px 15px 0px 0px !important; cursor:pointer;top:0px}"+
+                       "div.DefaultStickyWin div.dragHandle {  width: 11px;    height: 25px;   position: relative;     top: 5px;       left: -3px;     cursor: move;   background: url({%baseHref%}drag_corner.gif); float: left;}",
+               cornerHandle: false,
+               cssClass: '',
+               buttons: [],
+               cssId: 'defaultStickyWinStyle',
+               cssClassName: 'DefaultStickyWin',
+               closeButton: true
+/*     These options are deprecated:
+               closeTxt: false,
+               onClose: function(){},
+               confirmTxt: false,
+               onConfirm: function(){} */
+       },
+       initialize: function() {
+               var args = this.getArgs(arguments);
+               this.setOptions(args.options);
+               this.legacy();
+               var css = this.options.css.substitute({baseHref: this.options.baseHref || Clientcide.assetLocation + '/stickyWinHTML/'}, /\\?\{%([^}]+)%\}/g);
+               if (Browser.ie) css = css.replace(/png/g, 'gif');
+               this.createStyle(css, this.options.cssId);
+               this.build();
+               if (args.caption || args.body) this.setContent(args.caption, args.body);
+       },
+       toElement: function(){
+               return this.element;
+       },
+       getArgs: function(){
+               return StickyWin.UI.getArgs.apply(this, arguments);
+       },
+       legacy: function(){
+               var opt = this.options; //saving bytes
+               //legacy support
+               if (opt.confirmTxt) opt.buttons.push({text: opt.confirmTxt, onClick: opt.onConfirm || function(){}});
+               if (opt.closeTxt) opt.buttons.push({text: opt.closeTxt, onClick: opt.onClose || function(){}});
+       },
+       build: function(){
+               var opt = this.options;
+
+               var container = new Element('div', {
+                       'class': opt.cssClassName
+               });
+               if (opt.width) container.setStyle('width', opt.width);
+               this.element = container;
+               this.element.store('StickyWinUI', this);
+               if (opt.cssClass) container.addClass(opt.cssClass);
+
+
+               var bodyDiv = new Element('div').addClass('body');
+               this.body = bodyDiv;
+
+               var top_ur = new Element('div').addClass('top_ur');
+               this.top_ur = top_ur;
+               this.top = new Element('div').addClass('top').adopt(
+                               new Element('div').addClass('top_ul')
+                       ).adopt(top_ur);
+               container.adopt(this.top);
+
+               if (opt.cornerHandle) new Element('div').addClass('dragHandle').inject(top_ur, 'top');
+
+               //body
+               container.adopt(new Element('div').addClass('middle').adopt(bodyDiv));
+               //close buttons
+               if (opt.buttons.length > 0){
+                       var closeButtons = new Element('div').addClass('closeButtons');
+                       opt.buttons.each(function(button){
+                               if (button.properties && button.properties.className){
+                                       button.properties['class'] = button.properties.className;
+                                       delete button.properties.className;
+                               }
+                               var properties = Object.merge({'class': 'closeSticky'}, button.properties);
+                               new Element('a').addEvent('click', button.onClick || function(){})
+                                       .appendText(button.text).inject(closeButtons).set(properties).addClass('button');
+                       });
+                       container.adopt(new Element('div').addClass('closeBody').adopt(closeButtons));
+               }
+               //footer
+               container.adopt(
+                       new Element('div').addClass('bottom').adopt(
+                                       new Element('div').addClass('bottom_ll')
+                               ).adopt(
+                                       new Element('div').addClass('bottom_lr')
+                       )
+               );
+               if (this.options.closeButton) container.adopt(new Element('div').addClass('closeButton').addClass('closeSticky'));
+               return this;
+       },
+       setCaption: function(caption) {
+               this.caption = caption;
+               if (!this.h1) {
+                       this.makeCaption(caption);
+               } else {
+                       if (document.id(caption)) this.h1.adopt(caption);
+                       else this.h1.set('html', caption);
+               }
+               return this;
+       },
+       makeCaption: function(caption) {
+               if (!caption) return this.destroyCaption();
+               var opt = this.options;
+               this.h1 = new Element('h1').addClass('caption');
+               if (opt.width) this.h1.setStyle('width', (opt.width.toInt()-(opt.cornerHandle?55:40)-(opt.closeButton?10:0)));
+               this.setCaption(caption);
+               this.top_ur.adopt(this.h1);
+               if (!this.options.cornerHandle) this.h1.addClass('dragHandle');
+               return this;
+       },
+       destroyCaption: function(){
+               if (this.h1) {
+                       this.h1.destroy();
+                       this.h1 = null;
+               }
+               return this;
+       },
+       setContent: function(){
+               var args = this.getArgs.apply(this, arguments);
+               var caption = args.caption;
+               var body = args.body;
+               this.setCaption(caption);
+               if (document.id(body)) this.body.empty().adopt(body);
+               else this.body.set('html', body);
+               return this;
+       }
+});
+StickyWin.UI.getArgs = function(){
+       var input = typeOf(arguments[0]) == "arguments"?arguments[0]:arguments;
+       if (Browser.opera && 1 === input.length) input = input[0];
+
+       var cap = input[0], bod = input[1];
+       var args = Array.link(input, {options: Type.isObject});
+       if (input.length == 3 || (!args.options && input.length == 2)) {
+               args.caption = cap;
+               args.body = bod;
+       } else if ((typeOf(bod) == 'object' || !bod) && cap && typeOf(cap) != 'object'){
+               args.body = cap;
+       }
+       return args;
+};
+
+StickyWin.ui = function(caption, body, options){
+       return document.id(new StickyWin.UI(caption, body, options));
+};
+
+
+// Begin: Source/UI/StickyWin.Alert.js
+/*
+---
+
+name: StickyWin.Alert
+
+description: Defines StickyWin.Alert, a simple little alert box with a close button.
+
+license: MIT-Style License
+
+requires: [StickyWin.Modal, StickyWin.UI]
+
+provides: [StickyWin.Alert, StickyWin.Error, StickyWin.alert, StickyWin.error]
+
+...
+*/
+StickyWin.Alert = new Class({
+       Implements: Options,
+       Extends: StickyWin.Modal,
+       options: {
+               destroyOnClose: true,
+               modalOptions: {
+                       modalStyle: {
+                               zIndex: 11000
+                       }
+               },
+               zIndex: 110001,
+               uiOptions: {
+                       width: 250,
+                       buttons: [
+                               {text: 'Ok'}
+                       ]
+               },
+               getWindowManager: function(){}
+       },
+       initialize: function(caption, message, options) {
+               this.message = message;
+               this.caption = caption;
+               this.setOptions(options);
+               this.setOptions({
+                       content: this.build()
+               });
+               this.parent(options);
+       },
+       makeMessage: function() {
+               return new Element('p', {
+                       'class': 'errorMsg SWclearfix',
+                       styles: {
+                               margin: 0,
+                               minHeight: 10
+                       },
+                       html: this.message
+               });
+       },
+       build: function(){
+               return StickyWin.ui(this.caption, this.makeMessage(), this.options.uiOptions);
+       }
+});
+
+StickyWin.Error = new Class({
+       Extends: StickyWin.Alert,
+       makeMessage: function(){
+               var message = this.parent();
+               new Element('img', {
+                       src: (this.options.baseHref || Clientcide.assetLocation + '/simple.error.popup') + '/icon_problems_sm.gif',
+                       'class': 'bang clearfix',
+                       styles: {
+                               'float': 'left',
+                               width: 30,
+                               height: 30,
+                               margin: '3px 5px 5px 0px'
+                       }
+               }).inject(message, 'top');
+               return message;
+       }
+});
+
+StickyWin.alert = function(caption, message, options) {
+       if (typeOf(options) == "string") options = {baseHref: options};
+       return new StickyWin.Alert(caption, message, options);
+};
+
+StickyWin.error = function(caption, message, options) {
+       return new StickyWin.Error(caption, message, options);
+};
+
+// Begin: Source/UI/StickyWin.Confirm.js
+/*
+---
+name: StickyWin.Confirm
+
+description: Defines StickyWin.Conferm, a simple confirmation box with an ok and a close button.
+
+license: MIT-Style License
+
+requires: StickyWin.Alert
+
+provides: [StickyWin.Confirm, StickyWin.confirm]
+
+...
+*/
+StickyWin.Confirm = new Class({
+       Extends: StickyWin.Alert,
+       options: {
+               uiOptions: {
+                       width: 250
+               }
+       },
+       build: function(callback){
+               this.setOptions({
+                       uiOptions: {
+                               buttons: [
+                                       {text: 'Cancel'},
+                                       {
+                                               text: 'Ok',
+                                               onClick: callback || function(){
+                                                       this.fireEvent('confirm');
+                                               }.bind(this)
+                                       }
+                               ]
+                       }
+               });
+               return this.parent();
+       }
+});
+
+StickyWin.confirm = function(caption, message, callback, options) {
+       return new StickyWin.Confirm(caption, message, options).addEvent('confirm', callback);
+};
+
+// Begin: Source/Fx/Fx.Move.js
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Fx.Morph
+  - /Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+       Extends: Fx.Morph,
+
+       options: {
+               relativeTo: document.body,
+               position: 'center',
+               edge: false,
+               offset: {x: 0, y: 0}
+       },
+
+       start: function(destination){
+               var element = this.element,
+                       topLeft = element.getStyles('top', 'left');
+               if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+                       element.setPosition(element.getPosition(element.getOffsetParent()));
+               }
+               return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+       }
+
+});
+
+Element.Properties.move = {
+
+       set: function(options){
+               this.get('move').cancel().setOptions(options);
+               return this;
+       },
+
+       get: function(){
+               var move = this.retrieve('move');
+               if (!move){
+                       move = new Fx.Move(this, {link: 'cancel'});
+                       this.store('move', move);
+               }
+               return move;
+       }
+
+};
+
+Element.implement({
+
+       move: function(options){
+               this.get('move').start(options);
+               return this;
+       }
+
+});
+
+
+// Begin: Source/Fx/Fx.Scroll.js
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+
+requires:
+  - Core/Fx
+  - Core/Element.Event
+  - Core/Element.Dimensions
+  - /MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+       Extends: Fx,
+
+       options: {
+               offset: {x: 0, y: 0},
+               wheelStops: true
+       },
+
+       initialize: function(element, options){
+               this.element = this.subject = document.id(element);
+               this.parent(options);
+
+               if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+               if (this.options.wheelStops){
+                       var stopper = this.element,
+                               cancel = this.cancel.pass(false, this);
+                       this.addEvent('start', function(){
+                               stopper.addEvent('mousewheel', cancel);
+                       }, true);
+                       this.addEvent('complete', function(){
+                               stopper.removeEvent('mousewheel', cancel);
+                       }, true);
+               }
+       },
+
+       set: function(){
+               var now = Array.flatten(arguments);
+               if (Browser.firefox) now = [Math.round(now[0]), Math.round(now[1])]; // not needed anymore in newer firefox versions
+               this.element.scrollTo(now[0], now[1]);
+               return this;
+       },
+
+       compute: function(from, to, delta){
+               return [0, 1].map(function(i){
+                       return Fx.compute(from[i], to[i], delta);
+               });
+       },
+
+       start: function(x, y){
+               if (!this.check(x, y)) return this;
+               var scroll = this.element.getScroll();
+               return this.parent([scroll.x, scroll.y], [x, y]);
+       },
+
+       calculateScroll: function(x, y){
+               var element = this.element,
+                       scrollSize = element.getScrollSize(),
+                       scroll = element.getScroll(),
+                       size = element.getSize(),
+                       offset = this.options.offset,
+                       values = {x: x, y: y};
+
+               for (var z in values){
+                       if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+                       if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+                       values[z] += offset[z];
+               }
+
+               return [values.x, values.y];
+       },
+
+       toTop: function(){
+               return this.start.apply(this, this.calculateScroll(false, 0));
+       },
+
+       toLeft: function(){
+               return this.start.apply(this, this.calculateScroll(0, false));
+       },
+
+       toRight: function(){
+               return this.start.apply(this, this.calculateScroll('right', false));
+       },
+
+       toBottom: function(){
+               return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+       },
+
+       toElement: function(el, axes){
+               axes = axes ? Array.from(axes) : ['x', 'y'];
+               var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+               var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+                       return axes.contains(axis) ? value + scroll[axis] : false;
+               });
+               return this.start.apply(this, this.calculateScroll(position.x, position.y));
+       },
+
+       toElementEdge: function(el, axes, offset){
+               axes = axes ? Array.from(axes) : ['x', 'y'];
+               el = document.id(el);
+               var to = {},
+                       position = el.getPosition(this.element),
+                       size = el.getSize(),
+                       scroll = this.element.getScroll(),
+                       containerSize = this.element.getSize(),
+                       edge = {
+                               x: position.x + size.x,
+                               y: position.y + size.y
+                       };
+
+               ['x', 'y'].each(function(axis){
+                       if (axes.contains(axis)){
+                               if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+                               if (position[axis] < scroll[axis]) to[axis] = position[axis];
+                       }
+                       if (to[axis] == null) to[axis] = scroll[axis];
+                       if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+               }, this);
+
+               if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+               return this;
+       },
+
+       toElementCenter: function(el, axes, offset){
+               axes = axes ? Array.from(axes) : ['x', 'y'];
+               el = document.id(el);
+               var to = {},
+                       position = el.getPosition(this.element),
+                       size = el.getSize(),
+                       scroll = this.element.getScroll(),
+                       containerSize = this.element.getSize();
+
+               ['x', 'y'].each(function(axis){
+                       if (axes.contains(axis)){
+                               to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+                       }
+                       if (to[axis] == null) to[axis] = scroll[axis];
+                       if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+               }, this);
+
+               if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+               return this;
+       }
+
+});
+
+//<1.2compat>
+Fx.Scroll.implement({
+       scrollToCenter: function(){
+               return this.toElementCenter.apply(this, arguments);
+       },
+       scrollIntoView: function(){
+               return this.toElementEdge.apply(this, arguments);
+       }
+});
+//</1.2compat>
+
+function isBody(element){
+       return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+
+// Begin: Source/Class/Class.Refactor.js
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Class
+  - /MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+       Object.each(refactors, function(item, name){
+               var origin = original.prototype[name];
+               origin = (origin && origin.$origin) || origin || function(){};
+               original.implement(name, (typeof item == 'function') ? function(){
+                       var old = this.previous;
+                       this.previous = origin;
+                       var value = item.apply(this, arguments);
+                       this.previous = old;
+                       return value;
+               } : item);
+       });
+
+       return original;
+
+};
+
+
+// Begin: Source/Fx/Fx.Tween.js
+/*
+---
+
+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(property, from, to);
+               return this;
+       },
+
+       fade: function(how){
+               var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+               if (args[1] == null) args[1] = 'toggle';
+               switch (args[1]){
+                       case 'in': method = 'start'; args[1] = 1; break;
+                       case 'out': method = 'start'; args[1] = 0; break;
+                       case 'show': method = 'set'; args[1] = 1; break;
+                       case 'hide': method = 'set'; args[1] = 0; break;
+                       case 'toggle':
+                               var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+                               method = 'start';
+                               args[1] = flag ? 0 : 1;
+                               this.store('fade:flag', !flag);
+                               toggle = true;
+                       break;
+                       default: method = 'start';
+               }
+               if (!toggle) this.eliminate('fade:flag');
+               fade[method].apply(fade, args);
+               var to = args[args.length - 1];
+               if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+               else fade.chain(function(){
+                       this.element.setStyle('visibility', 'hidden');
+                       this.callChain();
+               });
+               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;
+       }
+
+});
+
+
+// Begin: Source/UI/StickyWin.Fx.js
+/*
+---
+
+name: StickyWin.Fx
+
+description: Extends StickyWin to create popups that fade in and out.
+
+license: MIT-style license.
+
+requires: [More/Class.Refactor, Core/Fx.Tween, StickyWin]
+
+provides: StickyWin.Fx
+
+...
+*/
+if (!Browser.ie){
+       StickyWin = Class.refactor(StickyWin, {
+               options: {
+                       //fadeTransition: 'sine:in:out',
+                       fade: true,
+                       fadeDuration: 150
+               },
+               hideWin: function(){
+                       if (this.options.fade) this.fade(0);
+                       else this.previous();
+               },
+               showWin: function(){
+                       if (this.options.fade) this.fade(1);
+                       else this.previous();
+               },
+               hide: function(){
+                       this.previous(this.options.fade);
+               },
+               show: function(){
+                       this.previous(this.options.fade);
+               },
+               fade: function(to){
+                       if (!this.fadeFx) {
+                               this.win.setStyles({
+                                       opacity: 0,
+                                       display: 'block'
+                               });
+                               var opts = {
+                                       property: 'opacity',
+                                       duration: this.options.fadeDuration
+                               };
+                               if (this.options.fadeTransition) opts.transition = this.options.fadeTransition;
+                               this.fadeFx = new Fx.Tween(this.win, opts);
+                       }
+                       if (to > 0) {
+                               this.win.setStyle('display','block');
+                               this.position();
+                       }
+                       this.fadeFx.clearChain();
+                       this.fadeFx.start(to).chain(function (){
+                               if (to == 0) {
+                                       this.win.setStyle('display', 'none');
+                                       this.fireEvent('onClose');
+                               } else {
+                                       this.fireEvent('onDisplay');
+                               }
+                       }.bind(this));
+                       return this;
+               }
+       });
+}
+StickyWin.Fx = StickyWin;
+
+
+// Begin: Source/Types/Array.Extras.js
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+  - Christoph Pojer
+  - Sebastian Markbåge
+
+requires:
+  - Core/Array
+  - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+       min: function(){
+               return Math.min.apply(null, this);
+       },
+
+       max: function(){
+               return Math.max.apply(null, this);
+       },
+
+       average: function(){
+               return this.length ? this.sum() / this.length : 0;
+       },
+
+       sum: function(){
+               var result = 0, l = this.length;
+               if (l){
+                       while (l--) result += this[l];
+               }
+               return result;
+       },
+
+       unique: function(){
+               return [].combine(this);
+       },
+
+       shuffle: function(){
+               for (var i = this.length; i && --i;){
+                       var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+                       this[i] = this[r];
+                       this[r] = temp;
+               }
+               return this;
+       },
+
+       reduce: function(fn, value){
+               for (var i = 0, l = this.length; i < l; i++){
+                       if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+               }
+               return value;
+       },
+
+       reduceRight: function(fn, value){
+               var i = this.length;
+               while (i--){
+                       if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+               }
+               return value;
+       },
+
+       pluck: function(prop){
+               return this.map(function(item){
+                       return item[prop];
+               });
+       }
+
+});
+
+})();
+
+
+// Begin: Source/Request/Request.HTML.js
+/*
+---
+
+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;
+       }
+
+});
+
+
+// Begin: Source/Layout/TabSwapper.js
+/*
+---
+
+name: TabSwapper
+
+description: Handles the scripting for a common UI layout; the tabbed box.
+
+license: MIT-Style License
+
+requires: [Core/Element.Event, Core/Fx.Tween, Core/Fx.Morph, Core/Element.Dimensions, More/Element.Shortcuts, More/Element.Measure]
+
+provides: TabSwapper
+
+...
+*/
+var TabSwapper = new Class({
+       Implements: [Options, Events],
+       options: {
+               // initPanel: null,
+               // smooth: false,
+               // smoothSize: false,
+               // maxSize: null,
+               // onActive: function(){},
+               // onActiveAfterFx: function(){},
+               // onBackground: function(){}
+               // cookieName: null,
+               preventDefault: true,
+               selectedClass: 'tabSelected',
+               mouseoverClass: 'tabOver',
+               deselectedClass: '',
+               rearrangeDOM: true,
+               effectOptions: {
+                       duration: 500
+               },
+               cookieDays: 999
+       },
+       tabs: [],
+       sections: [],
+       clickers: [],
+       sectionFx: [],
+       initialize: function(options){
+               this.setOptions(options);
+               var prev = this.setup();
+               if (prev) return prev;
+               if (this.options.initPanel != null) this.show(this.options.initPanel);
+               else if (this.options.cookieName && this.recall()) this.show(this.recall().toInt());
+               else this.show(0);
+
+       },
+       setup: function(){
+               var opt = this.options,
+                   sections = $$(opt.sections),
+                   tabs = $$(opt.tabs);
+               if (tabs[0] && tabs[0].retrieve('tabSwapper')) return tabs[0].retrieve('tabSwapper');
+               var clickers = $$(opt.clickers);
+               tabs.each(function(tab, index){
+                       this.addTab(tab, sections[index], clickers[index], index);
+               }, this);
+       },
+       addTab: function(tab, section, clicker, index){
+               tab = document.id(tab); clicker = document.id(clicker); section = document.id(section);
+               //if the tab is already in the interface, just move it
+               if (this.tabs.indexOf(tab) >= 0 && tab.retrieve('tabbered')
+                        && this.tabs.indexOf(tab) != index && this.options.rearrangeDOM) {
+                       this.moveTab(this.tabs.indexOf(tab), index);
+                       return this;
+               }
+               //if the index isn't specified, put the tab at the end
+               if (index == null) index = this.tabs.length;
+               //if this isn't the first item, and there's a tab
+               //already in the interface at the index 1 less than this
+               //insert this after that one
+               if (index > 0 && this.tabs[index-1] && this.options.rearrangeDOM) {
+                       tab.inject(this.tabs[index-1], 'after');
+                       section.inject(this.tabs[index-1].retrieve('section'), 'after');
+               }
+               this.tabs.splice(index, 0, tab);
+               clicker = clicker || tab;
+
+               tab.addEvents({
+                       mouseout: function(){
+                               tab.removeClass(this.options.mouseoverClass);
+                       }.bind(this),
+                       mouseover: function(){
+                               tab.addClass(this.options.mouseoverClass);
+                       }.bind(this)
+               });
+
+               clicker.addEvent('click', function(e){
+                       if (this.options.preventDefault) e.preventDefault();
+                       this.show(index);
+               }.bind(this));
+
+               tab.store('tabbered', true);
+               tab.store('section', section);
+               tab.store('clicker', clicker);
+               this.hideSection(index);
+               return this;
+       },
+       removeTab: function(index){
+               var now = this.tabs[this.now];
+               if (this.now == index){
+                       if (index > 0) this.show(index - 1);
+                       else if (index < this.tabs.length) this.show(index + 1);
+               }
+               this.now = this.tabs.indexOf(now);
+               return this;
+       },
+       moveTab: function(from, to){
+               var tab = this.tabs[from];
+               var clicker = tab.retrieve('clicker');
+               var section = tab.retrieve('section');
+
+               var toTab = this.tabs[to];
+               var toClicker = toTab.retrieve('clicker');
+               var toSection = toTab.retrieve('section');
+
+               this.tabs.erase(tab).splice(to, 0, tab);
+
+               tab.inject(toTab, 'before');
+               clicker.inject(toClicker, 'before');
+               section.inject(toSection, 'before');
+               return this;
+       },
+       show: function(i){
+               if (this.now == null) {
+                       this.tabs.each(function(tab, idx){
+                               if (i != idx)
+                                       this.hideSection(idx);
+                       }, this);
+               }
+               this.showSection(i).save(i);
+               return this;
+       },
+       save: function(index){
+               if (this.options.cookieName)
+                       Cookie.write(this.options.cookieName, index, {duration:this.options.cookieDays});
+               return this;
+       },
+       recall: function(){
+               return (this.options.cookieName) ? Cookie.read(this.options.cookieName) : false;
+       },
+       hideSection: function(idx) {
+               var tab = this.tabs[idx];
+               if (!tab) return this;
+               var sect = tab.retrieve('section');
+               if (!sect) return this;
+               if (sect.getStyle('display') != 'none') {
+                       this.lastHeight = sect.getSize().y;
+                       sect.setStyle('display', 'none');
+                       tab.swapClass(this.options.selectedClass, this.options.deselectedClass);
+                       this.fireEvent('onBackground', [idx, sect, tab]);
+               }
+               return this;
+       },
+       showSection: function(idx) {
+               var tab = this.tabs[idx];
+               if (!tab) return this;
+               var sect = tab.retrieve('section');
+               if (!sect) return this;
+               var smoothOk = this.options.smooth && !Browser.ie;
+               if (this.now != idx) {
+                       if (!tab.retrieve('tabFx'))
+                               tab.store('tabFx', new Fx.Morph(sect, this.options.effectOptions));
+                       var overflow = sect.getStyle('overflow');
+                       var start = {
+                               display:'block',
+                               overflow: 'hidden'
+                       };
+                       if (smoothOk) start.opacity = 0;
+                       var effect = false;
+                       if (smoothOk) {
+                               effect = {opacity: 1};
+                       } else if (sect.getStyle('opacity').toInt() < 1) {
+                               sect.setStyle('opacity', 1);
+                               if (!this.options.smoothSize) this.fireEvent('onActiveAfterFx', [idx, sect, tab]);
+                       }
+                       if (this.options.smoothSize) {
+                               var size = sect.getDimensions().height;
+                               if (this.options.maxSize != null && this.options.maxSize < size)
+                                       size = this.options.maxSize;
+                               if (!effect) effect = {};
+                               effect.height = size;
+                       }
+                       if (this.now != null) this.hideSection(this.now);
+                       if (this.options.smoothSize && this.lastHeight) start.height = this.lastHeight;
+                       sect.setStyles(start);
+                       var finish = function(){
+                               this.fireEvent('onActiveAfterFx', [idx, sect, tab]);
+                               sect.setStyles({
+                                       height: this.options.maxSize == effect.height ? this.options.maxSize : "auto",
+                                       overflow: overflow
+                               });
+                               sect.getElements('input, textarea').setStyle('opacity', 1);
+                       }.bind(this);
+                       if (effect) {
+                               tab.retrieve('tabFx').start(effect).chain(finish);
+                       } else {
+                               finish();
+                       }
+                       this.now = idx;
+                       this.fireEvent('onActive', [idx, sect, tab]);
+               }
+               tab.swapClass(this.options.deselectedClass, this.options.selectedClass);
+               return this;
+       }
+});
+
+
+// Begin: Source/Element/Element.Forms.js
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Element
+  - /String.Extras
+  - /MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+       tidy: function(){
+               this.set('value', this.get('value').tidy());
+       },
+
+       getTextInRange: function(start, end){
+               return this.get('value').substring(start, end);
+       },
+
+       getSelectedText: function(){
+               if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+               return document.selection.createRange().text;
+       },
+
+       getSelectedRange: function(){
+               if (this.selectionStart != null){
+                       return {
+                               start: this.selectionStart,
+                               end: this.selectionEnd
+                       };
+               }
+
+               var pos = {
+                       start: 0,
+                       end: 0
+               };
+               var range = this.getDocument().selection.createRange();
+               if (!range || range.parentElement() != this) return pos;
+               var duplicate = range.duplicate();
+
+               if (this.type == 'text'){
+                       pos.start = 0 - duplicate.moveStart('character', -100000);
+                       pos.end = pos.start + range.text.length;
+               } else {
+                       var value = this.get('value');
+                       var offset = value.length;
+                       duplicate.moveToElementText(this);
+                       duplicate.setEndPoint('StartToEnd', range);
+                       if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+                       pos.end = offset - duplicate.text.length;
+                       duplicate.setEndPoint('StartToStart', range);
+                       pos.start = offset - duplicate.text.length;
+               }
+               return pos;
+       },
+
+       getSelectionStart: function(){
+               return this.getSelectedRange().start;
+       },
+
+       getSelectionEnd: function(){
+               return this.getSelectedRange().end;
+       },
+
+       setCaretPosition: function(pos){
+               if (pos == 'end') pos = this.get('value').length;
+               this.selectRange(pos, pos);
+               return this;
+       },
+
+       getCaretPosition: function(){
+               return this.getSelectedRange().start;
+       },
+
+       selectRange: function(start, end){
+               if (this.setSelectionRange){
+                       this.focus();
+                       this.setSelectionRange(start, end);
+               } else {
+                       var value = this.get('value');
+                       var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+                       start = value.substr(0, start).replace(/\r/g, '').length;
+                       var range = this.createTextRange();
+                       range.collapse(true);
+                       range.moveEnd('character', start + diff);
+                       range.moveStart('character', start);
+                       range.select();
+               }
+               return this;
+       },
+
+       insertAtCursor: function(value, select){
+               var pos = this.getSelectedRange();
+               var text = this.get('value');
+               this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+               if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+               else this.setCaretPosition(pos.start + value.length);
+               return this;
+       },
+
+       insertAroundCursor: function(options, select){
+               options = Object.append({
+                       before: '',
+                       defaultMiddle: '',
+                       after: ''
+               }, options);
+
+               var value = this.getSelectedText() || options.defaultMiddle;
+               var pos = this.getSelectedRange();
+               var text = this.get('value');
+
+               if (pos.start == pos.end){
+                       this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+                       this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+               } else {
+                       var current = text.substring(pos.start, pos.end);
+                       this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+                       var selStart = pos.start + options.before.length;
+                       if (select !== false) this.selectRange(selStart, selStart + current.length);
+                       else this.setCaretPosition(selStart + text.length);
+               }
+               return this;
+       }
+
+});
+
+
+// Begin: Source/Fx/Fx.Reveal.js
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Fx.Morph
+  - /Element.Shortcuts
+  - /Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+       var hideThese = object.options.hideInputs;
+       if (window.OverText){
+               var otClasses = [null];
+               OverText.each(function(ot){
+                       otClasses.include('.' + ot.options.labelClass);
+               });
+               if (otClasses) hideThese += otClasses.join(', ');
+       }
+       return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+       Extends: Fx.Morph,
+
+       options: {/*
+               onShow: function(thisElement){},
+               onHide: function(thisElement){},
+               onComplete: function(thisElement){},
+               heightOverride: null,
+               widthOverride: null,*/
+               link: 'cancel',
+               styles: ['padding', 'border', 'margin'],
+               transitionOpacity: !Browser.ie6,
+               mode: 'vertical',
+               display: function(){
+                       return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+               },
+               opacity: 1,
+               hideInputs: Browser.ie ? 'select, input, textarea, object, embed' : null
+       },
+
+       dissolve: function(){
+               if (!this.hiding && !this.showing){
+                       if (this.element.getStyle('display') != 'none'){
+                               this.hiding = true;
+                               this.showing = false;
+                               this.hidden = true;
+                               this.cssText = this.element.style.cssText;
+
+                               var startStyles = this.element.getComputedSize({
+                                       styles: this.options.styles,
+                                       mode: this.options.mode
+                               });
+                               if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+                               var zero = {};
+                               Object.each(startStyles, function(style, name){
+                                       zero[name] = [style, 0];
+                               });
+
+                               this.element.setStyles({
+                                       display: Function.from(this.options.display).call(this),
+                                       overflow: 'hidden'
+                               });
+
+                               var hideThese = hideTheseOf(this);
+                               if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+                               this.$chain.unshift(function(){
+                                       if (this.hidden){
+                                               this.hiding = false;
+                                               this.element.style.cssText = this.cssText;
+                                               this.element.setStyle('display', 'none');
+                                               if (hideThese) hideThese.setStyle('visibility', 'visible');
+                                       }
+                                       this.fireEvent('hide', this.element);
+                                       this.callChain();
+                               }.bind(this));
+
+                               this.start(zero);
+                       } else {
+                               this.callChain.delay(10, this);
+                               this.fireEvent('complete', this.element);
+                               this.fireEvent('hide', this.element);
+                       }
+               } else if (this.options.link == 'chain'){
+                       this.chain(this.dissolve.bind(this));
+               } else if (this.options.link == 'cancel' && !this.hiding){
+                       this.cancel();
+                       this.dissolve();
+               }
+               return this;
+       },
+
+       reveal: function(){
+               if (!this.showing && !this.hiding){
+                       if (this.element.getStyle('display') == 'none'){
+                               this.hiding = false;
+                               this.showing = true;
+                               this.hidden = false;
+                               this.cssText = this.element.style.cssText;
+
+                               var startStyles;
+                               this.element.measure(function(){
+                                       startStyles = this.element.getComputedSize({
+                                               styles: this.options.styles,
+                                               mode: this.options.mode
+                                       });
+                               }.bind(this));
+                               if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+                               if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+                               if (this.options.transitionOpacity){
+                                       this.element.setStyle('opacity', 0);
+                                       startStyles.opacity = this.options.opacity;
+                               }
+
+                               var zero = {
+                                       height: 0,
+                                       display: Function.from(this.options.display).call(this)
+                               };
+                               Object.each(startStyles, function(style, name){
+                                       zero[name] = 0;
+                               });
+                               zero.overflow = 'hidden';
+
+                               this.element.setStyles(zero);
+
+                               var hideThese = hideTheseOf(this);
+                               if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+                               this.$chain.unshift(function(){
+                                       this.element.style.cssText = this.cssText;
+                                       this.element.setStyle('display', Function.from(this.options.display).call(this));
+                                       if (!this.hidden) this.showing = false;
+                                       if (hideThese) hideThese.setStyle('visibility', 'visible');
+                                       this.callChain();
+                                       this.fireEvent('show', this.element);
+                               }.bind(this));
+
+                               this.start(startStyles);
+                       } else {
+                               this.callChain();
+                               this.fireEvent('complete', this.element);
+                               this.fireEvent('show', this.element);
+                       }
+               } else if (this.options.link == 'chain'){
+                       this.chain(this.reveal.bind(this));
+               } else if (this.options.link == 'cancel' && !this.showing){
+                       this.cancel();
+                       this.reveal();
+               }
+               return this;
+       },
+
+       toggle: function(){
+               if (this.element.getStyle('display') == 'none'){
+                       this.reveal();
+               } else {
+                       this.dissolve();
+               }
+               return this;
+       },
+
+       cancel: function(){
+               this.parent.apply(this, arguments);
+               if (this.cssText != null) this.element.style.cssText = this.cssText;
+               this.hiding = false;
+               this.showing = false;
+               return this;
+       }
+
+});
+
+Element.Properties.reveal = {
+
+       set: function(options){
+               this.get('reveal').cancel().setOptions(options);
+               return this;
+       },
+
+       get: function(){
+               var reveal = this.retrieve('reveal');
+               if (!reveal){
+                       reveal = new Fx.Reveal(this);
+                       this.store('reveal', reveal);
+               }
+               return reveal;
+       }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+       reveal: function(options){
+               this.get('reveal').setOptions(options).reveal();
+               return this;
+       },
+
+       dissolve: function(options){
+               this.get('reveal').setOptions(options).dissolve();
+               return this;
+       },
+
+       nix: function(options){
+               var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+               this.get('reveal').setOptions(options).dissolve().chain(function(){
+                       this[params.destroy ? 'destroy' : 'dispose']();
+               }.bind(this));
+               return this;
+       },
+
+       wink: function(){
+               var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+               var reveal = this.get('reveal').setOptions(params.options);
+               reveal.reveal().chain(function(){
+                       (function(){
+                               reveal.dissolve();
+                       }).delay(params.duration || 2000);
+               });
+       }
+
+});
+
+})();
+
+
+// Begin: Source/Layout/Collapsible.js
+/*
+---
+name: Collapsible
+
+description: Enables a dom element to, when clicked, hide or show (it toggles) another dom element. Kind of an Accordion for one item.
+
+license: MIT-Style License
+
+requires: [Core/Element.Event, More/Fx.Reveal]
+
+provides: Collapsible
+...
+*/
+var Collapsible = new Class({
+       Extends: Fx.Reveal,
+       initialize: function(clicker, section, options) {
+               this.clicker = document.id(clicker);
+               this.section = document.id(section);
+               this.parent(this.section, options);
+               this.boundtoggle = this.toggle.bind(this);
+               this.attach();
+       },
+       attach: function(){
+               this.clicker.addEvent('click', this.boundtoggle);
+       },
+       detach: function(){
+               this.clicker.removeEvent('click', this.boundtoggle);
+       }
+});
+//legacy, this class originated w/ a typo. nice!
+var Collapsable = Collapsible;
+
+// Begin: Source/Utilities/Table.js
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+  - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+       this.length = 0;
+       var keys = [],
+           values = [];
+       
+       this.set = function(key, value){
+               var index = keys.indexOf(key);
+               if (index == -1){
+                       var length = keys.length;
+                       keys[length] = key;
+                       values[length] = value;
+                       this.length++;
+               } else {
+                       values[index] = value;
+               }
+               return this;
+       };
+
+       this.get = function(key){
+               var index = keys.indexOf(key);
+               return (index == -1) ? null : values[index];
+       };
+
+       this.erase = function(key){
+               var index = keys.indexOf(key);
+               if (index != -1){
+                       this.length--;
+                       keys.splice(index, 1);
+                       return values.splice(index, 1)[0];
+               }
+               return null;
+       };
+
+       this.each = this.forEach = function(fn, bind){
+               for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+       };
+       
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
+
+
+// Begin: Source/Element.Data.js
+/*
+---
+name: Element.Data
+description: Stores data in HTML5 data properties
+provides: [Element.Data]
+requires: [Core/Element, Core/JSON]
+script: Element.Data.js
+
+...
+*/
+(function(){
+
+       JSON.isSecure = function(string){
+               //this verifies that the string is parsable JSON and not malicious (borrowed from JSON.js in MooTools, which in turn borrowed it from Crockford)
+               //this version is a little more permissive, as it allows single quoted attributes because forcing the use of double quotes
+               //is a pain when this stuff is used as HTML properties
+               return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '').replace(/'[^'\\\n\r]*'/g, ''));
+       };
+
+       Element.implement({
+               /*
+                       sets an HTML5 data property.
+                       arguments:
+                               name - (string) the data name to store; will be automatically prefixed with 'data-'.
+                               value - (string, number) the value to store.
+               */
+               setData: function(name, value){
+                       return this.set('data-' + name.hyphenate(), value);
+               },
+
+               getData: function(name, defaultValue){
+                       var value = this.get('data-' + name.hyphenate());
+                       if (value != undefined){
+                               return value;
+                       } else if (defaultValue != undefined){
+                               this.setData(name, defaultValue);
+                               return defaultValue;
+                       }
+               },
+
+               /* 
+                       arguments:
+                               name - (string) the data name to store; will be automatically prefixed with 'data-'
+                               value - (string, array, or object) if an object or array the object will be JSON encoded; otherwise stored as provided.
+               */
+               setJSONData: function(name, value){
+                       return this.setData(name, JSON.encode(value));
+               },
+
+               /*
+                       retrieves a property from HTML5 data property you specify
+               
+                       arguments:
+                               name - (retrieve) the data name to store; will be automatically prefixed with 'data-'
+                               strict - (boolean) if true, will set the JSON.decode's secure flag to true; otherwise the value is still tested but allows single quoted attributes.
+                               defaultValue - (string, array, or object) the value to set if no value is found (see storeData above)
+               */
+               getJSONData: function(name, strict, defaultValue){
+                       var value = this.get('data-' + name);
+                       if (value != undefined){
+                               if (value && JSON.isSecure(value)) {
+                                       return JSON.decode(value, strict);
+                               } else {
+                                       return value;
+                               }
+                       } else if (defaultValue != undefined){
+                               this.setJSONData(name, defaultValue);
+                               return defaultValue;
+                       }
+               }
+
+       });
+
+})();
+
+// Begin: Source/BehaviorAPI.js
+/*
+---
+name: BehaviorAPI
+description: HTML getters for Behavior's API model.
+requires: [Core/Class, /Element.Data]
+provides: [BehaviorAPI]
+...
+*/
+
+
+(function(){
+       //see Docs/BehaviorAPI.md for documentation of public methods.
+
+       var reggy = /[^a-z0-9\-]/gi;
+
+       window.BehaviorAPI = new Class({
+               element: null,
+               prefix: '',
+               defaults: {},
+
+               initialize: function(element, prefix){
+                       this.element = element;
+                       this.prefix = prefix.toLowerCase().replace('.', '-', 'g').replace(reggy, '');
+               },
+
+               /******************
+                * PUBLIC METHODS
+                ******************/
+
+               get: function(/* name[, name, name, etc] */){
+                       if (arguments.length > 1) return this._getObj(Array.from(arguments));
+                       return this._getValue(arguments[0]);
+               },
+
+               getAs: function(/*returnType, name, defaultValue OR {name: returnType, name: returnType, etc}*/){
+                       if (typeOf(arguments[0]) == 'object') return this._getValuesAs.apply(this, arguments);
+                       return this._getValueAs.apply(this, arguments);
+               },
+
+               require: function(/* name[, name, name, etc] */){
+                       for (var i = 0; i < arguments.length; i++){
+                               if (this._getValue(arguments[i]) == undefined) throw new Error('Could not retrieve ' + this.prefix + '-' + arguments[i] + ' option from element.');
+                       }
+                       return this;
+               },
+
+               requireAs: function(returnType, name /* OR {name: returnType, name: returnType, etc}*/){
+                       var val;
+                       if (typeOf(arguments[0]) == 'object'){
+                               for (var objName in arguments[0]){
+                                       val = this._getValueAs(arguments[0][objName], objName);
+                                       if (val === undefined || val === null) throw new Error("Could not retrieve " + this.prefix + '-' + objName + " option from element.");
+                               }
+                       } else {
+                               val = this._getValueAs(returnType, name);
+                               if (val === undefined || val === null) throw new Error("Could not retrieve " + this.prefix + '-' + name + " option from element.");
+                       }
+                       return this;
+               },
+
+               setDefault: function(name, value /* OR {name: value, name: value, etc }*/){
+                       if (typeOf(arguments[0]) == 'object'){
+                               for (var objName in arguments[0]){
+                                       this.setDefault(objName, arguments[0][objName]);
+                               }
+                               return;
+                       }
+                       name = name.camelCase();
+                       this.defaults[name] = value;
+                       if (this._getValue(name) == null){
+                               var options = this._getOptions();
+                               options[name] = value;
+                       }
+                       return this;
+               },
+
+               refreshAPI: function(){
+                       delete this.options;
+                       this.setDefault(this.defaults);
+                       return;
+               },
+
+               /******************
+                * PRIVATE METHODS
+                ******************/
+
+               //given an array of names, returns an object of key/value pairs for each name
+               _getObj: function(names){
+                       var obj = {};
+                       names.each(function(name){
+                               var value = this._getValue(name);
+                               if (value !== undefined) obj[name] = value;
+                       }, this);
+                       return obj;
+               },
+               //gets the data-behaviorname-options object and parses it as JSON
+               _getOptions: function(){
+                       try {
+                               if (!this.options){
+                                       var options = this.element.getData(this.prefix + '-options', '{}');
+                                       if (options && options.substring(0,1) != '{') options = '{' + options + '}';
+                                       var isSecure = JSON.isSecure(options);
+                                       if (!isSecure) throw new Error('warning, options value for element is not parsable, check your JSON format for quotes, etc.');
+                                       this.options = isSecure ? JSON.decode(options) : {};
+                                       for (option in this.options) {
+                                               this.options[option.camelCase()] = this.options[option];
+                                       }
+                               }
+                       } catch (e){
+                               throw new Error('Could not get options from element; check your syntax. ' + this.prefix + '-options: "' + this.element.getData(this.prefix + '-options', '{}') + '"');
+                       }
+                       return this.options;
+               },
+               //given a name (string) returns the value for it
+               _getValue: function(name){
+                       name = name.camelCase();
+                       var options = this._getOptions();
+                       if (!options.hasOwnProperty(name)){
+                               var inline = this.element.getData(this.prefix + '-' + name.hyphenate());
+                               if (inline) options[name] = inline;
+                       }
+                       return options[name];
+               },
+               //given a Type and a name (string) returns the value for it coerced to that type if possible
+               //else returns the defaultValue or null
+               _getValueAs: function(returnType, name, defaultValue){
+                       var value = this._getValue(name);
+                       if (value == null || value == undefined) return defaultValue;
+                       var coerced = this._coerceFromString(returnType, value);
+                       if (coerced == null) throw new Error("Could not retrieve value '" + name + "' as the specified type. Its value is: " + value);
+                       return coerced;
+               },
+               //given an object of name/Type pairs, returns those as an object of name/value (as specified Type) pairs
+               _getValuesAs: function(obj){
+                       var returnObj = {};
+                       for (var name in obj){
+                               returnObj[name] = this._getValueAs(obj[name], name);
+                       }
+                       return returnObj;
+               },
+               //attempts to run a value through the JSON parser. If the result is not of that type returns null.
+               _coerceFromString: function(toType, value){
+                       if (typeOf(value) == 'string' && toType != String){
+                               if (JSON.isSecure(value)) value = JSON.decode(value);
+                       }
+                       if (instanceOf(value, toType)) return value;
+                       return null;
+               }
+       });
+
+})();
+
+// Begin: Source/Behavior.js
+/*
+---
+name: Behavior
+description: Auto-instantiates widgets/classes based on parsed, declarative HTML.
+requires: [Core/Class.Extras, Core/Element.Event, Core/Selectors, More/Table, /Element.Data, /BehaviorAPI]
+provides: [Behavior]
+...
+*/
+
+(function(){
+
+       var getLog = function(method){
+               return function(){
+                       if (window.console && console[method]){
+                               if(console[method].apply) console[method].apply(console, arguments);
+                               else console[method](Array.from(arguments).join(' '));
+                       }
+               };
+       };
+
+       var PassMethods = new Class({
+               //pass a method pointer through to a filter
+               //by default the methods for add/remove events are passed to the filter
+               //pointed to this instance of behavior. you could use this to pass along
+               //other methods to your filters. For example, a method to close a popup
+               //for filters presented inside popups.
+               passMethod: function(method, fn){
+                       if (this.API.prototype[method]) throw new Error('Cannot overwrite API method ' + method + ' as it already exists');
+                       this.API.implement(method, fn);
+                       return this;
+               },
+
+               passMethods: function(methods){
+                       for (method in methods) this.passMethod(method, methods[method]);
+                       return this;
+               }
+
+       });
+
+       var spaceOrCommaRegex = /\s*,\s*|\s+/g;
+
+       BehaviorAPI.implement({
+               deprecate: function(deprecated, asJSON){
+                       var set,
+                           values = {};
+                       Object.each(deprecated, function(prop, key){
+                               var value = this.element[ asJSON ? 'getJSONData' : 'getData'](prop);
+                               if (value !== undefined){
+                                       set = true;
+                                       values[key] = value;
+                               }
+                       }, this);
+                       this.setDefault(values);
+                       return this;
+               }
+       });
+
+       this.Behavior = new Class({
+
+               Implements: [Options, Events, PassMethods],
+
+               options: {
+                       //by default, errors thrown by filters are caught; the onError event is fired.
+                       //set this to *true* to NOT catch these errors to allow them to be handled by the browser.
+                       // breakOnErrors: false,
+                       // container: document.body,
+
+                       //default error behavior when a filter cannot be applied
+                       onError: getLog('error'),
+                       onWarn: getLog('warn'),
+                       enableDeprecation: true,
+                       selector: '[data-behavior]'
+               },
+
+               initialize: function(options){
+                       this.setOptions(options);
+                       this.API = new Class({ Extends: BehaviorAPI });
+                       this.passMethods({
+                               getDelegator: this.getDelegator.bind(this),
+                               addEvent: this.addEvent.bind(this),
+                               removeEvent: this.removeEvent.bind(this),
+                               addEvents: this.addEvents.bind(this),
+                               removeEvents: this.removeEvents.bind(this),
+                               fireEvent: this.fireEvent.bind(this),
+                               applyFilters: this.apply.bind(this),
+                               applyFilter: this.applyFilter.bind(this),
+                               getContentElement: this.getContentElement.bind(this),
+                               cleanup: this.cleanup.bind(this),
+                               getContainerSize: function(){
+                                       return this.getContentElement().measure(function(){
+                                               return this.getSize();
+                                       });
+                               }.bind(this),
+                               error: function(){ this.fireEvent('error', arguments); }.bind(this),
+                               fail: function(){
+                                       var msg = Array.join(arguments, ' ');
+                                       throw new Error(msg);
+                               },
+                               warn: function(){
+                                       this.fireEvent('warn', arguments);
+                               }.bind(this)
+                       });
+               },
+
+               getDelegator: function(){
+                       return this.delegator;
+               },
+
+               setDelegator: function(delegator){
+                       if (!instanceOf(delegator, Delegator)) throw new Error('Behavior.setDelegator only accepts instances of Delegator.');
+                       this.delegator = delegator;
+                       return this;
+               },
+
+               getContentElement: function(){
+                       return this.options.container || document.body;
+               },
+
+               //Applies all the behavior filters for an element.
+               //container - (element) an element to apply the filters registered with this Behavior instance to.
+               //force - (boolean; optional) passed through to applyFilter (see it for docs)
+               apply: function(container, force){
+                 this._getElements(container).each(function(element){
+                               var plugins = [];
+                               element.getBehaviors().each(function(name){
+                                       var filter = this.getFilter(name);
+                                       if (!filter){
+                                               this.fireEvent('error', ['There is no filter registered with this name: ', name, element]);
+                                       } else {
+                                               var config = filter.config;
+                                               if (config.delay !== undefined){
+                                                       this.applyFilter.delay(filter.config.delay, this, [element, filter, force]);
+                                               } else if(config.delayUntil){
+                                                       this._delayFilterUntil(element, filter, force);
+                                               } else if(config.initializer){
+                                                       this._customInit(element, filter, force);
+                                               } else {
+                                                       plugins.append(this.applyFilter(element, filter, force, true));
+                                               }
+                                       }
+                               }, this);
+                               plugins.each(function(plugin){ plugin(); });
+                       }, this);
+                       return this;
+               },
+
+               _getElements: function(container){
+                       if (typeOf(this.options.selector) == 'function') return this.options.selector(container);
+                       else return document.id(container).getElements(this.options.selector);
+               },
+
+               //delays a filter until the event specified in filter.config.delayUntil is fired on the element
+               _delayFilterUntil: function(element, filter, force){
+                       var events = filter.config.delayUntil.split(','),
+                           attached = {},
+                           inited = false;
+                       var clear = function(){
+                               events.each(function(event){
+                                       element.removeEvent(event, attached[event]);
+                               });
+                               clear = function(){};
+                       };
+                       events.each(function(event){
+                               var init = function(e){
+                                       clear();
+                                       if (inited) return;
+                                       inited = true;
+                                       var setup = filter.setup;
+                                       filter.setup = function(element, api, _pluginResult){
+                                               api.event = e;
+                                               return setup.apply(filter, [element, api, _pluginResult]);
+                                       };
+                                       this.applyFilter(element, filter, force);
+                                       filter.setup = setup;
+                               }.bind(this);
+                               element.addEvent(event, init);
+                               attached[event] = init;
+                       }, this);
+               },
+
+               //runs custom initiliazer defined in filter.config.initializer
+               _customInit: function(element, filter, force){
+                       var api = new this.API(element, filter.name);
+                       api.runSetup = this.applyFilter.pass([element, filter, force], this);
+                       filter.config.initializer(element, api);
+               },
+
+               //Applies a specific behavior to a specific element.
+               //element - the element to which to apply the behavior
+               //filter - (object) a specific behavior filter, typically one registered with this instance or registered globally.
+               //force - (boolean; optional) apply the behavior to each element it matches, even if it was previously applied. Defaults to *false*.
+               //_returnPlugins - (boolean; optional; internal) if true, plugins are not rendered but instead returned as an array of functions
+               //_pluginTargetResult - (obj; optional internal) if this filter is a plugin for another, this is whatever that target filter returned
+               //                      (an instance of a class for example)
+               applyFilter: function(element, filter, force, _returnPlugins, _pluginTargetResult){
+                       var pluginsToReturn = [];
+                       if (this.options.breakOnErrors){
+                               pluginsToReturn = this._applyFilter.apply(this, arguments);
+                       } else {
+                               try {
+                                       pluginsToReturn = this._applyFilter.apply(this, arguments);
+                               } catch (e){
+                                       this.fireEvent('error', ['Could not apply the behavior ' + filter.name, e]);
+                               }
+                       }
+                       return _returnPlugins ? pluginsToReturn : this;
+               },
+
+               //see argument list above for applyFilter
+               _applyFilter: function(element, filter, force, _returnPlugins, _pluginTargetResult){
+                       var pluginsToReturn = [];
+                       element = document.id(element);
+                       //get the filters already applied to this element
+                       var applied = getApplied(element);
+                       //if this filter is not yet applied to the element, or we are forcing the filter
+                       if (!applied[filter.name] || force){
+                               //if it was previously applied, garbage collect it
+                               if (applied[filter.name]) applied[filter.name].cleanup(element);
+                               var api = new this.API(element, filter.name);
+
+                               //deprecated
+                               api.markForCleanup = filter.markForCleanup.bind(filter);
+                               api.onCleanup = function(fn){
+                                       filter.markForCleanup(element, fn);
+                               };
+
+                               if (filter.config.deprecated && this.options.enableDeprecation) api.deprecate(filter.config.deprecated);
+                               if (filter.config.deprecateAsJSON && this.options.enableDeprecation) api.deprecate(filter.config.deprecatedAsJSON, true);
+
+                               //deal with requirements and defaults
+                               if (filter.config.requireAs){
+                                       api.requireAs(filter.config.requireAs);
+                               } else if (filter.config.require){
+                                       api.require.apply(api, Array.from(filter.config.require));
+                               }
+
+                               if (filter.config.defaults) api.setDefault(filter.config.defaults);
+
+                               //apply the filter
+                               var result = filter.setup(element, api, _pluginTargetResult);
+                               if (filter.config.returns && !instanceOf(result, filter.config.returns)){
+                                       throw new Error("Filter " + filter.name + " did not return a valid instance.");
+                               }
+                               element.store('Behavior Filter result:' + filter.name, result);
+                               //and mark it as having been previously applied
+                               applied[filter.name] = filter;
+                               //apply all the plugins for this filter
+                               var plugins = this.getPlugins(filter.name);
+                               if (plugins){
+                                       for (var name in plugins){
+                                               if (_returnPlugins){
+                                                       pluginsToReturn.push(this.applyFilter.pass([element, plugins[name], force, null, result], this));
+                                               } else {
+                                                       this.applyFilter(element, plugins[name], force, null, result);
+                                               }
+                                       }
+                               }
+                       }
+                       return pluginsToReturn;
+               },
+
+               //given a name, returns a registered behavior
+               getFilter: function(name){
+                       return this._registered[name] || Behavior.getFilter(name);
+               },
+
+               getPlugins: function(name){
+                       return this._plugins[name] || Behavior._plugins[name];
+               },
+
+               //Garbage collects all applied filters for an element and its children.
+               //element - (*element*) container to cleanup
+               //ignoreChildren - (*boolean*; optional) if *true* only the element will be cleaned, otherwise the element and all the
+               //        children with filters applied will be cleaned. Defaults to *false*.
+               cleanup: function(element, ignoreChildren){
+                       element = document.id(element);
+                       var applied = getApplied(element);
+                       for (var filter in applied){
+                               applied[filter].cleanup(element);
+                               element.eliminate('Behavior Filter result:' + filter);
+                               delete applied[filter];
+                       }
+                       if (!ignoreChildren) this._getElements(element).each(this.cleanup, this);
+                       return this;
+               }
+
+       });
+
+       //Export these for use elsewhere (notabily: Delegator).
+       Behavior.getLog = getLog;
+       Behavior.PassMethods = PassMethods;
+
+
+       //Returns the applied behaviors for an element.
+       var getApplied = function(el){
+               return el.retrieve('_appliedBehaviors', {});
+       };
+
+       //Registers a behavior filter.
+       //name - the name of the filter
+       //fn - a function that applies the filter to the given element
+       //overwrite - (boolean) if true, will overwrite existing filter if one exists; defaults to false.
+       var addFilter = function(name, fn, overwrite){
+               if (!this._registered[name] || overwrite) this._registered[name] = new Behavior.Filter(name, fn);
+               else throw new Error('Could not add the Behavior filter "' + name  +'" as a previous trigger by that same name exists.');
+       };
+
+       var addFilters = function(obj, overwrite){
+               for (var name in obj){
+                       addFilter.apply(this, [name, obj[name], overwrite]);
+               }
+       };
+
+       //Registers a behavior plugin
+       //filterName - (*string*) the filter (or plugin) this is a plugin for
+       //name - (*string*) the name of this plugin
+       //setup - a function that applies the filter to the given element
+       var addPlugin = function(filterName, name, setup, overwrite){
+               if (!this._plugins[filterName]) this._plugins[filterName] = {};
+               if (!this._plugins[filterName][name] || overwrite) this._plugins[filterName][name] = new Behavior.Filter(name, setup);
+               else throw new Error('Could not add the Behavior filter plugin "' + name  +'" as a previous trigger by that same name exists.');
+       };
+
+       var addPlugins = function(obj, overwrite){
+               for (var name in obj){
+                       addPlugin.apply(this, [obj[name].fitlerName, obj[name].name, obj[name].setup], overwrite);
+               }
+       };
+
+       var setFilterDefaults = function(name, defaults){
+               var filter = this.getFilter(name);
+               if (!filter.config.defaults) filter.config.defaults = {};
+               Object.append(filter.config.defaults, defaults);
+       };
+
+       //Add methods to the Behavior namespace for global registration.
+       Object.append(Behavior, {
+               _registered: {},
+               _plugins: {},
+               addGlobalFilter: addFilter,
+               addGlobalFilters: addFilters,
+               addGlobalPlugin: addPlugin,
+               addGlobalPlugins: addPlugins,
+               setFilterDefaults: setFilterDefaults,
+               getFilter: function(name){
+                       return this._registered[name];
+               }
+       });
+       //Add methods to the Behavior class for instance registration.
+       Behavior.implement({
+               _registered: {},
+               _plugins: {},
+               addFilter: addFilter,
+               addFilters: addFilters,
+               addPlugin: addPlugin,
+               addPlugins: addPlugins,
+               setFilterDefaults: setFilterDefaults
+       });
+
+       //This class is an actual filter that, given an element, alters it with specific behaviors.
+       Behavior.Filter = new Class({
+
+               config: {
+                       /**
+                               returns: Foo,
+                               require: ['req1', 'req2'],
+                               //or
+                               requireAs: {
+                                       req1: Boolean,
+                                       req2: Number,
+                                       req3: String
+                               },
+                               defaults: {
+                                       opt1: false,
+                                       opt2: 2
+                               },
+                               //simple example:
+                               setup: function(element, API){
+                                       var kids = element.getElements(API.get('selector'));
+                                       //some validation still has to occur here
+                                       if (!kids.length) API.fail('there were no child elements found that match ', API.get('selector'));
+                                       if (kids.length < 2) API.warn("there weren't more than 2 kids that match", API.get('selector'));
+                                       var fooInstance = new Foo(kids, API.get('opt1', 'opt2'));
+                                       API.onCleanup(function(){
+                                               fooInstance.destroy();
+                                       });
+                                       return fooInstance;
+                               },
+                               delayUntil: 'mouseover',
+                               //OR
+                               delay: 100,
+                               //OR
+                               initializer: function(element, API){
+                                       element.addEvent('mouseover', API.runSetup); //same as specifying event
+                                       //or
+                                       API.runSetup.delay(100); //same as specifying delay
+                                       //or something completely esoteric
+                                       var timer = (function(){
+                                               if (element.hasClass('foo')){
+                                                       clearInterval(timer);
+                                                       API.runSetup();
+                                               }
+                                       }).periodical(100);
+                                       //or
+                                       API.addEvent('someBehaviorEvent', API.runSetup);
+                               });
+                               */
+               },
+
+               //Pass in an object with the following properties:
+               //name - the name of this filter
+               //setup - a function that applies the filter to the given element
+               initialize: function(name, setup){
+                       this.name = name;
+                       if (typeOf(setup) == "function"){
+                               this.setup = setup;
+                       } else {
+                               Object.append(this.config, setup);
+                               this.setup = this.config.setup;
+                       }
+                       this._cleanupFunctions = new Table();
+               },
+
+               //Stores a garbage collection pointer for a specific element.
+               //Example: if your filter enhances all the inputs in the container
+               //you might have a function that removes that enhancement for garbage collection.
+               //You would mark each input matched with its own cleanup function.
+               //NOTE: this MUST be the element passed to the filter - the element with this filters
+               //      name in its data-behavior property. I.E.:
+               //<form data-behavior="FormValidator">
+               //  <input type="text" name="email"/>
+               //</form>
+               //If this filter is FormValidator, you can mark the form for cleanup, but not, for example
+               //the input. Only elements that match this filter can be marked.
+               markForCleanup: function(element, fn){
+                       var functions = this._cleanupFunctions.get(element);
+                       if (!functions) functions = [];
+                       functions.include(fn);
+                       this._cleanupFunctions.set(element, functions);
+                       return this;
+               },
+
+               //Garbage collect a specific element.
+               //NOTE: this should be an element that has a data-behavior property that matches this filter.
+               cleanup: function(element){
+                       var marks = this._cleanupFunctions.get(element);
+                       if (marks){
+                               marks.each(function(fn){ fn(); });
+                               this._cleanupFunctions.erase(element);
+                       }
+                       return this;
+               }
+
+       });
+
+       Behavior.elementDataProperty = 'behavior';
+
+       Element.implement({
+
+               addBehaviorFilter: function(name){
+                       return this.setData(Behavior.elementDataProperty, this.getBehaviors().include(name).join(' '));
+               },
+
+               removeBehaviorFilter: function(name){
+                       return this.setData(Behavior.elementDataProperty, this.getBehaviors().erase(name).join(' '));
+               },
+
+               getBehaviors: function(){
+                       var filters = this.getData(Behavior.elementDataProperty);
+                       if (!filters) return [];
+                       return filters.trim().split(spaceOrCommaRegex);
+               },
+
+               hasBehavior: function(name){
+                       return this.getBehaviors().contains(name);
+               },
+
+               getBehaviorResult: function(name){
+                       return this.retrieve('Behavior Filter result:' + name);
+               }
+
+       });
+
+
+})();
+
+
+// Begin: Source/Drag/Drag.js
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+  - Tom Occhinno
+  - Jan Kassens
+
+requires:
+  - Core/Events
+  - Core/Options
+  - Core/Element.Event
+  - Core/Element.Style
+  - Core/Element.Dimensions
+  - /MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+       Implements: [Events, Options],
+
+       options: {/*
+               onBeforeStart: function(thisElement){},
+               onStart: function(thisElement, event){},
+               onSnap: function(thisElement){},
+               onDrag: function(thisElement, event){},
+               onCancel: function(thisElement){},
+               onComplete: function(thisElement, event){},*/
+               snap: 6,
+               unit: 'px',
+               grid: false,
+               style: true,
+               limit: false,
+               handle: false,
+               invert: false,
+               preventDefault: false,
+               stopPropagation: false,
+               modifiers: {x: 'left', y: 'top'}
+       },
+
+       initialize: function(){
+               var params = Array.link(arguments, {
+                       'options': Type.isObject,
+                       'element': function(obj){
+                               return obj != null;
+                       }
+               });
+
+               this.element = document.id(params.element);
+               this.document = this.element.getDocument();
+               this.setOptions(params.options || {});
+               var htype = typeOf(this.options.handle);
+               this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+               this.mouse = {'now': {}, 'pos': {}};
+               this.value = {'start': {}, 'now': {}};
+
+               this.selection = (Browser.ie) ? 'selectstart' : 'mousedown';
+
+
+               if (Browser.ie && !Drag.ondragstartFixed){
+                       document.ondragstart = Function.from(false);
+                       Drag.ondragstartFixed = true;
+               }
+
+               this.bound = {
+                       start: this.start.bind(this),
+                       check: this.check.bind(this),
+                       drag: this.drag.bind(this),
+                       stop: this.stop.bind(this),
+                       cancel: this.cancel.bind(this),
+                       eventStop: Function.from(false)
+               };
+               this.attach();
+       },
+
+       attach: function(){
+               this.handles.addEvent('mousedown', this.bound.start);
+               return this;
+       },
+
+       detach: function(){
+               this.handles.removeEvent('mousedown', this.bound.start);
+               return this;
+       },
+
+       start: function(event){
+               var options = this.options;
+
+               if (event.rightClick) return;
+
+               if (options.preventDefault) event.preventDefault();
+               if (options.stopPropagation) event.stopPropagation();
+               this.mouse.start = event.page;
+
+               this.fireEvent('beforeStart', this.element);
+
+               var limit = options.limit;
+               this.limit = {x: [], y: []};
+
+               var z, coordinates;
+               for (z in options.modifiers){
+                       if (!options.modifiers[z]) continue;
+
+                       var style = this.element.getStyle(options.modifiers[z]);
+
+                       // Some browsers (IE and Opera) don't always return pixels.
+                       if (style && !style.match(/px$/)){
+                               if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent());
+                               style = coordinates[options.modifiers[z]];
+                       }
+
+                       if (options.style) this.value.now[z] = (style || 0).toInt();
+                       else this.value.now[z] = this.element[options.modifiers[z]];
+
+                       if (options.invert) this.value.now[z] *= -1;
+
+                       this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+                       if (limit && limit[z]){
+                               var i = 2;
+                               while (i--){
+                                       var limitZI = limit[z][i];
+                                       if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+                               }
+                       }
+               }
+
+               if (typeOf(this.options.grid) == 'number') this.options.grid = {
+                       x: this.options.grid,
+                       y: this.options.grid
+               };
+
+               var events = {
+                       mousemove: this.bound.check,
+                       mouseup: this.bound.cancel
+               };
+               events[this.selection] = this.bound.eventStop;
+               this.document.addEvents(events);
+       },
+
+       check: function(event){
+               if (this.options.preventDefault) event.preventDefault();
+               var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+               if (distance > this.options.snap){
+                       this.cancel();
+                       this.document.addEvents({
+                               mousemove: this.bound.drag,
+                               mouseup: this.bound.stop
+                       });
+                       this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+               }
+       },
+
+       drag: function(event){
+               var options = this.options;
+
+               if (options.preventDefault) event.preventDefault();
+               this.mouse.now = event.page;
+
+               for (var z in options.modifiers){
+                       if (!options.modifiers[z]) continue;
+                       this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+                       if (options.invert) this.value.now[z] *= -1;
+
+                       if (options.limit && this.limit[z]){
+                               if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+                                       this.value.now[z] = this.limit[z][1];
+                               } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+                                       this.value.now[z] = this.limit[z][0];
+                               }
+                       }
+
+                       if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+
+                       if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+                       else this.element[options.modifiers[z]] = this.value.now[z];
+               }
+
+               this.fireEvent('drag', [this.element, event]);
+       },
+
+       cancel: function(event){
+               this.document.removeEvents({
+                       mousemove: this.bound.check,
+                       mouseup: this.bound.cancel
+               });
+               if (event){
+                       this.document.removeEvent(this.selection, this.bound.eventStop);
+                       this.fireEvent('cancel', this.element);
+               }
+       },
+
+       stop: function(event){
+               var events = {
+                       mousemove: this.bound.drag,
+                       mouseup: this.bound.stop
+               };
+               events[this.selection] = this.bound.eventStop;
+               this.document.removeEvents(events);
+               if (event) this.fireEvent('complete', [this.element, event]);
+       }
+
+});
+
+Element.implement({
+
+       makeResizable: function(options){
+               var drag = new Drag(this, Object.merge({
+                       modifiers: {
+                               x: 'width',
+                               y: 'height'
+                       }
+               }, options));
+
+               this.store('resizer', drag);
+               return drag.addEvent('drag', function(){
+                       this.fireEvent('resize', drag);
+               }.bind(this));
+       }
+
+});
+
+
+// Begin: Source/Drag/Drag.Move.js
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+  - Tom Occhinno
+  - Jan Kassens
+  - Aaron Newton
+  - Scott Kyle
+
+requires:
+  - Core/Element.Dimensions
+  - /Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+       Extends: Drag,
+
+       options: {/*
+               onEnter: function(thisElement, overed){},
+               onLeave: function(thisElement, overed){},
+               onDrop: function(thisElement, overed, event){},*/
+               droppables: [],
+               container: false,
+               precalculate: false,
+               includeMargins: true,
+               checkDroppables: true
+       },
+
+       initialize: function(element, options){
+               this.parent(element, options);
+               element = this.element;
+
+               this.droppables = $$(this.options.droppables);
+               this.container = document.id(this.options.container);
+
+               if (this.container && typeOf(this.container) != 'element')
+                       this.container = document.id(this.container.getDocument().body);
+
+               if (this.options.style){
+                       if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+                               var parent = element.getOffsetParent(),
+                                       styles = element.getStyles('left', 'top');
+                               if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+                                       element.setPosition(element.getPosition(parent));
+                               }
+                       }
+
+                       if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+               }
+
+               this.addEvent('start', this.checkDroppables, true);
+               this.overed = null;
+       },
+
+       start: function(event){
+               if (this.container) this.options.limit = this.calculateLimit();
+
+               if (this.options.precalculate){
+                       this.positions = this.droppables.map(function(el){
+                               return el.getCoordinates();
+                       });
+               }
+
+               this.parent(event);
+       },
+
+       calculateLimit: function(){
+               var element = this.element,
+                       container = this.container,
+
+                       offsetParent = document.id(element.getOffsetParent()) || document.body,
+                       containerCoordinates = container.getCoordinates(offsetParent),
+                       elementMargin = {},
+                       elementBorder = {},
+                       containerMargin = {},
+                       containerBorder = {},
+                       offsetParentPadding = {};
+
+               ['top', 'right', 'bottom', 'left'].each(function(pad){
+                       elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+                       elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+                       containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+                       containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+                       offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+               }, this);
+
+               var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+                       height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+                       left = 0,
+                       top = 0,
+                       right = containerCoordinates.right - containerBorder.right - width,
+                       bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+               if (this.options.includeMargins){
+                       left += elementMargin.left;
+                       top += elementMargin.top;
+               } else {
+                       right += elementMargin.right;
+                       bottom += elementMargin.bottom;
+               }
+
+               if (element.getStyle('position') == 'relative'){
+                       var coords = element.getCoordinates(offsetParent);
+                       coords.left -= element.getStyle('left').toInt();
+                       coords.top -= element.getStyle('top').toInt();
+
+                       left -= coords.left;
+                       top -= coords.top;
+                       if (container.getStyle('position') != 'relative'){
+                               left += containerBorder.left;
+                               top += containerBorder.top;
+                       }
+                       right += elementMargin.left - coords.left;
+                       bottom += elementMargin.top - coords.top;
+
+                       if (container != offsetParent){
+                               left += containerMargin.left + offsetParentPadding.left;
+                               top += ((Browser.ie6 || Browser.ie7) ? 0 : containerMargin.top) + offsetParentPadding.top;
+                       }
+               } else {
+                       left -= elementMargin.left;
+                       top -= elementMargin.top;
+                       if (container != offsetParent){
+                               left += containerCoordinates.left + containerBorder.left;
+                               top += containerCoordinates.top + containerBorder.top;
+                       }
+               }
+
+               return {
+                       x: [left, right],
+                       y: [top, bottom]
+               };
+       },
+
+       getDroppableCoordinates: function(element){
+               var position = element.getCoordinates();
+               if (element.getStyle('position') == 'fixed'){
+                       var scroll = window.getScroll();
+                       position.left += scroll.x;
+                       position.right += scroll.x;
+                       position.top += scroll.y;
+                       position.bottom += scroll.y;
+               }
+               return position;
+       },
+
+       checkDroppables: function(){
+               var overed = this.droppables.filter(function(el, i){
+                       el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+                       var now = this.mouse.now;
+                       return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+               }, this).getLast();
+
+               if (this.overed != overed){
+                       if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+                       if (overed) this.fireEvent('enter', [this.element, overed]);
+                       this.overed = overed;
+               }
+       },
+
+       drag: function(event){
+               this.parent(event);
+               if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+       },
+
+       stop: function(event){
+               this.checkDroppables();
+               this.fireEvent('drop', [this.element, this.overed, event]);
+               this.overed = null;
+               return this.parent(event);
+       }
+
+});
+
+Element.implement({
+
+       makeDraggable: function(options){
+               var drag = new Drag.Move(this, options);
+               this.store('dragger', drag);
+               return drag;
+       }
+
+});
+
+
+// Begin: Source/UI/StickyWin.Drag.js
+/*
+---
+
+name: StickyWin.Drag
+
+description: Implements drag and resize functionaity into StickyWin.Fx. See StickyWin.Fx for the options.
+
+license: MIT-Style License
+
+requires: [More/Class.Refactor, More/Drag.Move, StickyWin]
+
+provides: StickyWin.Drag
+
+...
+*/
+StickyWin = Class.refactor(StickyWin, {
+       options: {
+               draggable: false,
+               dragOptions: {
+                       onComplete: function(){}
+               },
+               dragHandleSelector: '.dragHandle',
+               resizable: false,
+               resizeOptions: {
+                       onComplete: function(){}
+               },
+               resizeHandleSelector: ''
+       },
+       setContent: function(){
+               this.previous.apply(this, arguments);
+               if (this.options.draggable) this.makeDraggable();
+               if (this.options.resizable) this.makeResizable();
+               return this;
+       },
+       makeDraggable: function(){
+               var toggled = this.toggleVisible(true);
+               if (this.options.useIframeShim) {
+                       this.makeIframeShim();
+                       var onComplete = (this.options.dragOptions.onComplete);
+                       this.options.dragOptions.onComplete = function(){
+                               onComplete();
+                               this.shim.position();
+                       }.bind(this);
+               }
+               if (this.options.dragHandleSelector) {
+                       var handle = this.win.getElement(this.options.dragHandleSelector);
+                       if (handle) {
+                               handle.setStyle('cursor','move');
+                               this.options.dragOptions.handle = handle;
+                       }
+               }
+               this.win.makeDraggable(this.options.dragOptions);
+               if (toggled) this.toggleVisible(false);
+       },
+       makeResizable: function(){
+               var toggled = this.toggleVisible(true);
+               if (this.options.useIframeShim) {
+                       this.makeIframeShim();
+                       var onComplete = (this.options.resizeOptions.onComplete);
+                       this.options.resizeOptions.onComplete = function(){
+                               onComplete();
+                               this.shim.position();
+                       }.bind(this);
+               }
+               if (this.options.resizeHandleSelector) {
+                       var handle = this.win.getElement(this.options.resizeHandleSelector);
+                       if (handle) this.options.resizeOptions.handle = this.win.getElement(this.options.resizeHandleSelector);
+               }
+               this.win.makeResizable(this.options.resizeOptions);
+               if (toggled) this.toggleVisible(false);
+       },
+       toggleVisible: function(show){
+               if (!this.visible && show == null || show) {
+                       this.win.setStyles({
+                               display: 'block',
+                               opacity: 0
+                       });
+                       return true;
+               } else if (show != null && !show){
+                       this.win.setStyles({
+                               display: 'none',
+                               opacity: 1
+                       });
+                       return false;
+               }
+               return false;
+       }
+});
+StickyWin.Fx = StickyWin;
+
+// Begin: Source/Behaviors/Behavior.StickyWin.js
+/*
+---
+name: Behavior.StickyWin
+description: Behaviors for StickyWin instances.
+provides: [Behavior.StickyWin]
+requires: [Behavior/Behavior, /StickyWin, /StickyWin.Modal, /StickyWin.Fx, /StickyWin.Drag, More/Array.Extras, More/Object.Extras]
+script: Behavior.Tabs.js
+
+...
+*/
+
+Behavior.addGlobalFilters({
+
+       'StickyWin.Modal': {
+               defaults: {
+                       destroyOnClose: true,
+                       closeOnClickOut: true,
+                       closeOnEsc: true,
+                       draggable: false,
+                       resizable: false
+               },
+               returns: StickyWin.Modal,
+               setup: function(element, api) {
+                       var flex = element.getElement('.flex'),
+                           height = api.getAs(Number, 'height') || (window.getSize().y * .9);
+
+                       if (flex){
+                               element.measure(function(){
+                                       var tmp = new Element('span', { styles: { display: 'none' }}).replaces(flex),
+                                           remainder = element.getSize().y;
+                                       var padding = ['padding-top', 'padding-bottom', 'margin-top', 'margin-bottom', 'border-top-width', 'border-bottom-width'].map(function(style){
+                                               return flex.getStyle(style).toInt();
+                                       }).sum();
+                                       flex.setStyle('max-height', height - remainder - padding);
+                                       flex.replaces(tmp);
+                               });
+                       }
+
+                       var options = Object.merge({
+                                       content: element
+                               },
+                               Object.cleanValues(
+                                       api.getAs({
+                                               closeClassName: String,
+                                               pinClassName: String,
+                                               className: String,
+                                               edge: String,
+                                               position: String,
+                                               offset: Object,
+                                               relativeTo: String,
+                                               width: Number,
+                                               height: Number,
+                                               timeout: Number,
+                                               destroyOnClose: Boolean,
+                                               closeOnClickOut: Boolean,
+                                               closeOnEsc: Boolean,
+                                               //modal options
+                                               maskOptions: Object,
+                                               //draggable options
+                                               draggable: Boolean,
+                                               dragHandleSelector: String,
+                                               resizable: Boolean,
+                                               resizeHandleSelector: String
+                                       })
+                               )
+                       );
+
+                       if (options.mask) options.closeOnClickOut = false;
+
+                       var sw = new StickyWin.Modal(options);
+                       api.onCleanup(function(){
+                               if (!sw.destroyed) sw.destroy();
+                       });
+                       sw.addEvent('destroy', function(){
+                               api.cleanup(element);
+                       });
+                       return sw;
+               }
+       }
+
+});
+
+
+// Begin: Source/Utilities/Color.js
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+
+requires:
+  - Core/Array
+  - Core/String
+  - Core/Number
+  - Core/Hash
+  - Core/Function
+  - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+       if (arguments.length >= 3){
+               type = 'rgb'; color = Array.slice(arguments, 0, 3);
+       } else if (typeof color == 'string'){
+               if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+               else if (color.match(/hsb/)) color = color.hsbToRgb();
+               else color = color.hexToRgb(true);
+       }
+       type = type || 'rgb';
+       switch (type){
+               case 'hsb':
+                       var old = color;
+                       color = color.hsbToRgb();
+                       color.hsb = old;
+               break;
+               case 'hex': color = color.hexToRgb(true); break;
+       }
+       color.rgb = color.slice(0, 3);
+       color.hsb = color.hsb || color.rgbToHsb();
+       color.hex = color.rgbToHex();
+       return Object.append(color, this);
+});
+
+Color.implement({
+
+       mix: function(){
+               var colors = Array.slice(arguments);
+               var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+               var rgb = this.slice();
+               colors.each(function(color){
+                       color = new Color(color);
+                       for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+               });
+               return new Color(rgb, 'rgb');
+       },
+
+       invert: function(){
+               return new Color(this.map(function(value){
+                       return 255 - value;
+               }));
+       },
+
+       setHue: function(value){
+               return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+       },
+
+       setSaturation: function(percent){
+               return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+       },
+
+       setBrightness: function(percent){
+               return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+       }
+
+});
+
+this.$RGB = function(r, g, b){
+       return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+       return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+       return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+       rgbToHsb: function(){
+               var red = this[0],
+                               green = this[1],
+                               blue = this[2],
+                               hue = 0;
+               var max = Math.max(red, green, blue),
+                               min = Math.min(red, green, blue);
+               var delta = max - min;
+               var brightness = max / 255,
+                               saturation = (max != 0) ? delta / max : 0;
+               if (saturation != 0){
+                       var rr = (max - red) / delta;
+                       var gr = (max - green) / delta;
+                       var br = (max - blue) / delta;
+                       if (red == max) hue = br - gr;
+                       else if (green == max) hue = 2 + rr - br;
+                       else hue = 4 + gr - rr;
+                       hue /= 6;
+                       if (hue < 0) hue++;
+               }
+               return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+       },
+
+       hsbToRgb: function(){
+               var br = Math.round(this[2] / 100 * 255);
+               if (this[1] == 0){
+                       return [br, br, br];
+               } else {
+                       var hue = this[0] % 360;
+                       var f = hue % 60;
+                       var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+                       var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+                       var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+                       switch (Math.floor(hue / 60)){
+                               case 0: return [br, t, p];
+                               case 1: return [q, br, p];
+                               case 2: return [p, br, t];
+                               case 3: return [p, q, br];
+                               case 4: return [t, p, br];
+                               case 5: return [br, p, q];
+                       }
+               }
+               return false;
+       }
+
+});
+
+String.implement({
+
+       rgbToHsb: function(){
+               var rgb = this.match(/\d{1,3}/g);
+               return (rgb) ? rgb.rgbToHsb() : null;
+       },
+
+       hsbToRgb: function(){
+               var hsb = this.match(/\d{1,3}/g);
+               return (hsb) ? hsb.hsbToRgb() : null;
+       }
+
+});
+
+})();
+
+
+
+// Begin: Source/Class/Events.Pseudos.js
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+  - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, More/MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+       var storeKey = '_monitorEvents:';
+
+       var storageOf = function(object){
+               return {
+                       store: object.store ? function(key, value){
+                               object.store(storeKey + key, value);
+                       } : function(key, value){
+                               (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+                       },
+                       retrieve: object.retrieve ? function(key, dflt){
+                               return object.retrieve(storeKey + key, dflt);
+                       } : function(key, dflt){
+                               if (!object._monitorEvents) return dflt;
+                               return object._monitorEvents[key] || dflt;
+                       }
+               };
+       };
+
+       var splitType = function(type){
+               if (type.indexOf(':') == -1 || !pseudos) return null;
+
+               var parsed = Slick.parse(type).expressions[0][0],
+                       parsedPseudos = parsed.pseudos,
+                       l = parsedPseudos.length,
+                       splits = [];
+
+               while (l--){
+                       var pseudo = parsedPseudos[l].key,
+                               listener = pseudos[pseudo];
+                       if (listener != null) splits.push({
+                               event: parsed.tag,
+                               value: parsedPseudos[l].value,
+                               pseudo: pseudo,
+                               original: type,
+                               listener: listener
+                       });
+               }
+               return splits.length ? splits : null;
+       };
+
+       return {
+
+               addEvent: function(type, fn, internal){
+                       var split = splitType(type);
+                       if (!split) return addEvent.call(this, type, fn, internal);
+
+                       var storage = storageOf(this),
+                               events = storage.retrieve(type, []),
+                               eventType = split[0].event,
+                               args = Array.slice(arguments, 2),
+                               stack = fn,
+                               self = this;
+
+                       split.each(function(item){
+                               var listener = item.listener,
+                                       stackFn = stack;
+                               if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+                               else stack = function(){
+                                       listener.call(self, item, stackFn, arguments, stack);
+                               };
+                       });
+
+                       events.include({type: eventType, event: fn, monitor: stack});
+                       storage.store(type, events);
+
+                       if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+                       return addEvent.apply(this, [eventType, stack].concat(args));
+               },
+
+               removeEvent: function(type, fn){
+                       var split = splitType(type);
+                       if (!split) return removeEvent.call(this, type, fn);
+
+                       var storage = storageOf(this),
+                               events = storage.retrieve(type);
+                       if (!events) return this;
+
+                       var args = Array.slice(arguments, 2);
+
+                       removeEvent.apply(this, [type, fn].concat(args));
+                       events.each(function(monitor, i){
+                               if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+                               delete events[i];
+                       }, this);
+
+                       storage.store(type, events);
+                       return this;
+               }
+
+       };
+
+};
+
+var pseudos = {
+
+       once: function(split, fn, args, monitor){
+               fn.apply(this, args);
+               this.removeEvent(split.event, monitor)
+                       .removeEvent(split.original, fn);
+       },
+
+       throttle: function(split, fn, args){
+               if (!fn._throttled){
+                       fn.apply(this, args);
+                       fn._throttled = setTimeout(function(){
+                               fn._throttled = false;
+                       }, split.value || 250);
+               }
+       },
+
+       pause: function(split, fn, args){
+               clearTimeout(fn._pause);
+               fn._pause = fn.delay(split.value || 250, this, args);
+       }
+
+};
+
+Events.definePseudo = function(key, listener){
+       pseudos[key] = listener;
+       return this;
+};
+
+Events.lookupPseudo = function(key){
+       return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+       if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+
+// Begin: Source/Element/Element.Event.Pseudos.js
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+  - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+       copyFromEvents = ['once', 'throttle', 'pause'],
+       count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+       pseudos[key] = listener;
+       return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+
+// Begin: Source/Element/Element.Event.Pseudos.Keys.js
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+  - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+       keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+       var event = args[0],
+               keys = [],
+               pressed = this.retrieve(keysStoreKey, []);
+
+       keys.append(split.value.replace('++', function(){
+               keys.push('+'); // shift++ and shift+++a
+               return '';
+       }).split('+'));
+
+       pressed.include(event.key);
+
+       if (keys.every(function(key){
+               return pressed.contains(key);
+       })) fn.apply(this, args);
+
+       this.store(keysStoreKey, pressed);
+
+       if (!this.retrieve(keysKeyupStoreKey)){
+               var keyup = function(event){
+                       (function(){
+                               pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+                               this.store(keysStoreKey, pressed);
+                       }).delay(0, this); // Fix for IE
+               };
+               this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+       }
+
+});
+
+DOMEvent.defineKeys({
+       '16': 'shift',
+       '17': 'control',
+       '18': 'alt',
+       '20': 'capslock',
+       '33': 'pageup',
+       '34': 'pagedown',
+       '35': 'end',
+       '36': 'home',
+       '144': 'numlock',
+       '145': 'scrolllock',
+       '186': ';',
+       '187': '=',
+       '188': ',',
+       '190': '.',
+       '191': '/',
+       '192': '`',
+       '219': '[',
+       '220': '\\',
+       '221': ']',
+       '222': "'",
+       '107': '+'
+}).defineKey(Browser.firefox ? 109 : 189, '-');
+
+})();
+
+
+// Begin: Source/Interface/Keyboard.js
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+  - Perrin Westrich
+  - Aaron Newton
+  - Scott Kyle
+
+requires:
+  - Core/Events
+  - Core/Options
+  - Core/Element.Event
+  - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+       var Keyboard = this.Keyboard = new Class({
+
+               Extends: Events,
+
+               Implements: [Options],
+
+               options: {/*
+                       onActivate: function(){},
+                       onDeactivate: function(){},*/
+                       defaultEventType: 'keydown',
+                       active: false,
+                       manager: null,
+                       events: {},
+                       nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+               },
+
+               initialize: function(options){
+                       if (options && options.manager){
+                               this._manager = options.manager;
+                               delete options.manager;
+                       }
+                       this.setOptions(options);
+                       this._setup();
+               },
+
+               addEvent: function(type, fn, internal){
+                       return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+               },
+
+               removeEvent: function(type, fn){
+                       return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+               },
+
+               toggleActive: function(){
+                       return this[this.isActive() ? 'deactivate' : 'activate']();
+               },
+
+               activate: function(instance){
+                       if (instance){
+                               if (instance.isActive()) return this;
+                               //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+                               if (this._activeKB && instance != this._activeKB){
+                                       this.previous = this._activeKB;
+                                       this.previous.fireEvent('deactivate');
+                               }
+                               //if we're enabling a child, assign it so that events are now passed to it
+                               this._activeKB = instance.fireEvent('activate');
+                               Keyboard.manager.fireEvent('changed');
+                       } else if (this._manager){
+                               //else we're enabling ourselves, we must ask our parent to do it for us
+                               this._manager.activate(this);
+                       }
+                       return this;
+               },
+
+               isActive: function(){
+                       return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+               },
+
+               deactivate: function(instance){
+                       if (instance){
+                               if (instance === this._activeKB){
+                                       this._activeKB = null;
+                                       instance.fireEvent('deactivate');
+                                       Keyboard.manager.fireEvent('changed');
+                               }
+                       } else if (this._manager){
+                               this._manager.deactivate(this);
+                       }
+                       return this;
+               },
+
+               relinquish: function(){
+                       if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+                       else this.deactivate();
+                       return this;
+               },
+
+               //management logic
+               manage: function(instance){
+                       if (instance._manager) instance._manager.drop(instance);
+                       this._instances.push(instance);
+                       instance._manager = this;
+                       if (!this._activeKB) this.activate(instance);
+                       return this;
+               },
+
+               drop: function(instance){
+                       instance.relinquish();
+                       this._instances.erase(instance);
+                       if (this._activeKB == instance){
+                               if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+                               else this._activeKB = this._instances[0];
+                       }
+                       return this;
+               },
+
+               trace: function(){
+                       Keyboard.trace(this);
+               },
+
+               each: function(fn){
+                       Keyboard.each(this, fn);
+               },
+
+               /*
+                       PRIVATE METHODS
+               */
+
+               _instances: [],
+
+               _disable: function(instance){
+                       if (this._activeKB == instance) this._activeKB = null;
+               },
+
+               _setup: function(){
+                       this.addEvents(this.options.events);
+                       //if this is the root manager, nothing manages it
+                       if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+                       if (this.options.active) this.activate();
+                       else this.relinquish();
+               },
+
+               _handle: function(event, type){
+                       //Keyboard.stop(event) prevents key propagation
+                       if (event.preventKeyboardPropagation) return;
+
+                       var bubbles = !!this._manager;
+                       if (bubbles && this._activeKB){
+                               this._activeKB._handle(event, type);
+                               if (event.preventKeyboardPropagation) return;
+                       }
+                       this.fireEvent(type, event);
+
+                       if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+               }
+
+       });
+
+       var parsed = {};
+       var modifiers = ['shift', 'control', 'alt', 'meta'];
+       var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+       Keyboard.parse = function(type, eventType, ignore){
+               if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+               type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+                       eventType = $1;
+                       return '';
+               });
+
+               if (!parsed[type]){
+                       var key, mods = {};
+                       type.split('+').each(function(part){
+                               if (regex.test(part)) mods[part] = true;
+                               else key = part;
+                       });
+
+                       mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+                       var keys = [];
+                       modifiers.each(function(mod){
+                               if (mods[mod]) keys.push(mod);
+                       });
+
+                       if (key) keys.push(key);
+                       parsed[type] = keys.join('+');
+               }
+
+               return eventType + ':keys(' + parsed[type] + ')';
+       };
+
+       Keyboard.each = function(keyboard, fn){
+               var current = keyboard || Keyboard.manager;
+               while (current){
+                       fn.run(current);
+                       current = current._activeKB;
+               }
+       };
+
+       Keyboard.stop = function(event){
+               event.preventKeyboardPropagation = true;
+       };
+
+       Keyboard.manager = new Keyboard({
+               active: true
+       });
+
+       Keyboard.trace = function(keyboard){
+               keyboard = keyboard || Keyboard.manager;
+               var hasConsole = window.console && console.log;
+               if (hasConsole) console.log('the following items have focus: ');
+               Keyboard.each(keyboard, function(current){
+                       if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+               });
+       };
+
+       var handler = function(event){
+               var keys = [];
+               modifiers.each(function(mod){
+                       if (event[mod]) keys.push(mod);
+               });
+
+               if (!regex.test(event.key)) keys.push(event.key);
+               Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+       };
+
+       document.addEvents({
+               'keyup': handler,
+               'keydown': handler
+       });
+
+})();
+
+
+// Begin: Source/Interface/Keyboard.Extras.js
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+  - Perrin Westrich
+
+requires:
+  - /Keyboard
+  - /MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+       /*
+               shortcut should be in the format of:
+               {
+                       'keys': 'shift+s', // the default to add as an event.
+                       'description': 'blah blah blah', // a brief description of the functionality.
+                       'handler': function(){} // the event handler to run when keys are pressed.
+               }
+       */
+       addShortcut: function(name, shortcut){
+               this._shortcuts = this._shortcuts || [];
+               this._shortcutIndex = this._shortcutIndex || {};
+
+               shortcut.getKeyboard = Function.from(this);
+               shortcut.name = name;
+               this._shortcutIndex[name] = shortcut;
+               this._shortcuts.push(shortcut);
+               if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+               return this;
+       },
+
+       addShortcuts: function(obj){
+               for (var name in obj) this.addShortcut(name, obj[name]);
+               return this;
+       },
+
+       removeShortcut: function(name){
+               var shortcut = this.getShortcut(name);
+               if (shortcut && shortcut.keys){
+                       this.removeEvent(shortcut.keys, shortcut.handler);
+                       delete this._shortcutIndex[name];
+                       this._shortcuts.erase(shortcut);
+               }
+               return this;
+       },
+
+       removeShortcuts: function(names){
+               names.each(this.removeShortcut, this);
+               return this;
+       },
+
+       getShortcuts: function(){
+               return this._shortcuts || [];
+       },
+
+       getShortcut: function(name){
+               return (this._shortcutIndex || {})[name];
+       }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+       Array.from(shortcuts).each(function(shortcut){
+               shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+               shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+               shortcut.keys = newKeys;
+               shortcut.getKeyboard().fireEvent('rebound');
+       });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+       var activeKBS = [], activeSCS = [];
+       Keyboard.each(keyboard, [].push.bind(activeKBS));
+       activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+       return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+       opts = opts || {};
+       var shortcuts = opts.many ? [] : null,
+               set = opts.many ? function(kb){
+                               var shortcut = kb.getShortcut(name);
+                               if (shortcut) shortcuts.push(shortcut);
+                       } : function(kb){
+                               if (!shortcuts) shortcuts = kb.getShortcut(name);
+                       };
+       Keyboard.each(keyboard, set);
+       return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+       return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+
+// Begin: Source/Locale/Locale.js
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+  - Arian Stolwijk
+
+requires:
+  - Core/Events
+  - /Object.Extras
+  - /MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+       locales = {},
+       inherits = {};
+
+var getSet = function(set){
+       if (instanceOf(set, Locale.Set)) return set;
+       else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+       define: function(locale, set, key, value){
+               var name;
+               if (instanceOf(locale, Locale.Set)){
+                       name = locale.name;
+                       if (name) locales[name] = locale;
+               } else {
+                       name = locale;
+                       if (!locales[name]) locales[name] = new Locale.Set(name);
+                       locale = locales[name];
+               }
+
+               if (set) locale.define(set, key, value);
+
+               /*<1.2compat>*/
+               if (set == 'cascade') return Locale.inherit(name, key);
+               /*</1.2compat>*/
+
+               if (!current) current = locale;
+
+               return locale;
+       },
+
+       use: function(locale){
+               locale = getSet(locale);
+
+               if (locale){
+                       current = locale;
+
+                       this.fireEvent('change', locale);
+
+                       /*<1.2compat>*/
+                       this.fireEvent('langChange', locale.name);
+                       /*</1.2compat>*/
+               }
+
+               return this;
+       },
+
+       getCurrent: function(){
+               return current;
+       },
+
+       get: function(key, args){
+               return (current) ? current.get(key, args) : '';
+       },
+
+       inherit: function(locale, inherits, set){
+               locale = getSet(locale);
+
+               if (locale) locale.inherit(inherits, set);
+               return this;
+       },
+
+       list: function(){
+               return Object.keys(locales);
+       }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+       sets: {},
+
+       inherits: {
+               locales: [],
+               sets: {}
+       },
+
+       initialize: function(name){
+               this.name = name || '';
+       },
+
+       define: function(set, key, value){
+               var defineData = this.sets[set];
+               if (!defineData) defineData = {};
+
+               if (key){
+                       if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+                       else defineData[key] = value;
+               }
+               this.sets[set] = defineData;
+
+               return this;
+       },
+
+       get: function(key, args, _base){
+               var value = Object.getFromPath(this.sets, key);
+               if (value != null){
+                       var type = typeOf(value);
+                       if (type == 'function') value = value.apply(null, Array.from(args));
+                       else if (type == 'object') value = Object.clone(value);
+                       return value;
+               }
+
+               // get value of inherited locales
+               var index = key.indexOf('.'),
+                       set = index < 0 ? key : key.substr(0, index),
+                       names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+               if (!_base) _base = [];
+
+               for (var i = 0, l = names.length; i < l; i++){
+                       if (_base.contains(names[i])) continue;
+                       _base.include(names[i]);
+
+                       var locale = locales[names[i]];
+                       if (!locale) continue;
+
+                       value = locale.get(key, args, _base);
+                       if (value != null) return value;
+               }
+
+               return '';
+       },
+
+       inherit: function(names, set){
+               names = Array.from(names);
+
+               if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+               var l = names.length;
+               while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+               return this;
+       }
+
+});
+
+/*<1.2compat>*/
+var lang = MooTools.lang = {};
+
+Object.append(lang, Locale, {
+       setLanguage: Locale.use,
+       getCurrentLanguage: function(){
+               var current = Locale.getCurrent();
+               return (current) ? current.name : null;
+       },
+       set: function(){
+               Locale.define.apply(this, arguments);
+               return this;
+       },
+       get: function(set, key, args){
+               if (key) set += '.' + key;
+               return Locale.get(set, args);
+       }
+});
+/*</1.2compat>*/
+
+})();
+
+
+// Begin: Source/Locale/Locale.en-US.Number.js
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+  - Arian Stolwijk
+
+requires:
+  - /Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+       decimal: '.',
+       group: ',',
+
+/*     Commented properties are the defaults for Number.format
+       decimals: 0,
+       precision: 0,
+       scientific: null,
+
+       prefix: null,
+       suffic: null,
+
+       // Negative/Currency/percentage will mixin Number
+       negative: {
+               prefix: '-'
+       },*/
+
+       currency: {
+//             decimals: 2,
+               prefix: '$ '
+       }/*,
+
+       percentage: {
+               decimals: 2,
+               suffix: '%'
+       }*/
+
+});
+
+
+
+
+// Begin: Source/Types/Number.Format.js
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+       format: function(options){
+               // Thanks dojo and YUI for some inspiration
+               var value = this;
+               options = options ? Object.clone(options) : {};
+               var getOption = function(key){
+                       if (options[key] != null) return options[key];
+                       return Locale.get('Number.' + key);
+               };
+
+               var negative = value < 0,
+                       decimal = getOption('decimal'),
+                       precision = getOption('precision'),
+                       group = getOption('group'),
+                       decimals = getOption('decimals');
+
+               if (negative){
+                       var negativeLocale = getOption('negative') || {};
+                       if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+                       ['prefix', 'suffix'].each(function(key){
+                               if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+                       });
+
+                       value = -value;
+               }
+
+               var prefix = getOption('prefix'),
+                       suffix = getOption('suffix');
+
+               if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+               if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+               value += '';
+               var index;
+               if (getOption('scientific') === false && value.indexOf('e') > -1){
+                       var match = value.split('e'),
+                               zeros = +match[1];
+                       value = match[0].replace('.', '');
+
+                       if (zeros < 0){
+                               zeros = -zeros - 1;
+                               index = match[0].indexOf('.');
+                               if (index > -1) zeros -= index - 1;
+                               while (zeros--) value = '0' + value;
+                               value = '0.' + value;
+                       } else {
+                               index = match[0].lastIndexOf('.');
+                               if (index > -1) zeros -= match[0].length - index - 1;
+                               while (zeros--) value += '0';
+                       }
+               }
+
+               if (decimal != '.') value = value.replace('.', decimal);
+
+               if (group){
+                       index = value.lastIndexOf(decimal);
+                       index = (index > -1) ? index : value.length;
+                       var newOutput = value.substring(index),
+                               i = index;
+
+                       while (i--){
+                               if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+                               newOutput = value.charAt(i) + newOutput;
+                       }
+
+                       value = newOutput;
+               }
+
+               if (prefix) value = prefix + value;
+               if (suffix) value += suffix;
+
+               return value;
+       },
+
+       formatCurrency: function(decimals){
+               var locale = Locale.get('Number.currency') || {};
+               if (locale.scientific == null) locale.scientific = false;
+               locale.decimals = decimals != null ? decimals
+                       : (locale.decimals == null ? 2 : locale.decimals);
+
+               return this.format(locale);
+       },
+
+       formatPercentage: function(decimals){
+               var locale = Locale.get('Number.percentage') || {};
+               if (locale.suffix == null) locale.suffix = '%';
+               locale.decimals = decimals != null ? decimals
+                       : (locale.decimals == null ? 2 : locale.decimals);
+
+               return this.format(locale);
+       }
+
+});
+
+
+// Begin: Source/Request/Request.Periodical.js
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+  - Christoph Pojer
+
+requires:
+  - Core/Request
+  - /MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+       options: {
+               initialDelay: 5000,
+               delay: 5000,
+               limit: 60000
+       },
+
+       startTimer: function(data){
+               var fn = function(){
+                       if (!this.running) this.send({data: data});
+               };
+               this.lastDelay = this.options.initialDelay;
+               this.timer = fn.delay(this.lastDelay, this);
+               this.completeCheck = function(response){
+                       clearTimeout(this.timer);
+                       this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+                       this.timer = fn.delay(this.lastDelay, this);
+               };
+               return this.addEvent('complete', this.completeCheck);
+       },
+
+       stopTimer: function(){
+               clearTimeout(this.timer);
+               return this.removeEvent('complete', this.completeCheck);
+       }
+
+});
+
+
+// Begin: Source/Request/Request.JSONP.js
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+  - Guillermo Rauch
+  - Arian Stolwijk
+
+requires:
+  - Core/Element
+  - Core/Request
+  - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+       Implements: [Chain, Events, Options],
+
+       options: {/*
+               onRequest: function(src, scriptElement){},
+               onComplete: function(data){},
+               onSuccess: function(data){},
+               onCancel: function(){},
+               onTimeout: function(){},
+               onError: function(){}, */
+               onRequest: function(src){
+                       if (this.options.log && window.console && console.log){
+                               console.log('JSONP retrieving script with url:' + src);
+                       }
+               },
+               onError: function(src){
+                       if (this.options.log && window.console && console.warn){
+                               console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+                       }
+               },
+               url: '',
+               callbackKey: 'callback',
+               injectScript: document.head,
+               data: '',
+               link: 'ignore',
+               timeout: 0,
+               log: false
+       },
+
+       initialize: function(options){
+               this.setOptions(options);
+       },
+
+       send: function(options){
+               if (!Request.prototype.check.call(this, options)) return this;
+               this.running = true;
+
+               var type = typeOf(options);
+               if (type == 'string' || type == 'element') options = {data: options};
+               options = Object.merge(this.options, options || {});
+
+               var data = options.data;
+               switch (typeOf(data)){
+                       case 'element': data = document.id(data).toQueryString(); break;
+                       case 'object': case 'hash': data = Object.toQueryString(data);
+               }
+
+               var index = this.index = Request.JSONP.counter++;
+
+               var src = options.url +
+                       (options.url.test('\\?') ? '&' :'?') +
+                       (options.callbackKey) +
+                       '=Request.JSONP.request_map.request_'+ index +
+                       (data ? '&' + data : '');
+
+               if (src.length > 2083) this.fireEvent('error', src);
+
+               Request.JSONP.request_map['request_' + index] = function(){
+                       this.success(arguments, index);
+               }.bind(this);
+
+               var script = this.getScript(src).inject(options.injectScript);
+               this.fireEvent('request', [src, script]);
+
+               if (options.timeout) this.timeout.delay(options.timeout, this);
+
+               return this;
+       },
+
+       getScript: function(src){
+               if (!this.script) this.script = new Element('script', {
+                       type: 'text/javascript',
+                       async: true,
+                       src: src
+               });
+               return this.script;
+       },
+
+       success: function(args, index){
+               if (!this.running) return;
+               this.clear()
+                       .fireEvent('complete', args).fireEvent('success', args)
+                       .callChain();
+       },
+
+       cancel: function(){
+               if (this.running) this.clear().fireEvent('cancel');
+               return this;
+       },
+
+       isRunning: function(){
+               return !!this.running;
+       },
+
+       clear: function(){
+               this.running = false;
+               if (this.script){
+                       this.script.destroy();
+                       this.script = null;
+               }
+               return this;
+       },
+
+       timeout: function(){
+               if (this.running){
+                       this.running = false;
+                       this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+               }
+               return this;
+       }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+
+// Begin: Source/Types/String.QueryString.js
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+  - Sebastian Markbåge
+  - Aaron Newton
+  - Lennart Pilon
+  - Valerio Proietti
+
+requires:
+  - Core/Array
+  - Core/String
+  - /MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+       parseQueryString: function(decodeKeys, decodeValues){
+               if (decodeKeys == null) decodeKeys = true;
+               if (decodeValues == null) decodeValues = true;
+
+               var vars = this.split(/[&;]/),
+                       object = {};
+               if (!vars.length) return object;
+
+               vars.each(function(val){
+                       var index = val.indexOf('=') + 1,
+                               value = index ? val.substr(index) : '',
+                               keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+                               obj = object;
+                       if (!keys) return;
+                       if (decodeValues) value = decodeURIComponent(value);
+                       keys.each(function(key, i){
+                               if (decodeKeys) key = decodeURIComponent(key);
+                               var current = obj[key];
+
+                               if (i < keys.length - 1) obj = obj[key] = current || {};
+                               else if (typeOf(current) == 'array') current.push(value);
+                               else obj[key] = current != null ? [current, value] : value;
+                       });
+               });
+
+               return object;
+       },
+
+       cleanQueryString: function(method){
+               return this.split('&').filter(function(val){
+                       var index = val.indexOf('='),
+                               key = index < 0 ? '' : val.substr(0, index),
+                               value = val.substr(index + 1);
+
+                       return method ? method.call(null, key, value) : (value || value === 0);
+               }).join('&');
+       }
+
+});
+
+
+// Begin: Source/Types/URI.js
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+  - Sebastian Markbåge
+  - Aaron Newton
+
+requires:
+  - Core/Object
+  - Core/Class
+  - Core/Class.Extras
+  - Core/Element
+  - /String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+       return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+       Implements: Options,
+
+       options: {
+               /*base: false*/
+       },
+
+       regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+       parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+       schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+       initialize: function(uri, options){
+               this.setOptions(options);
+               var base = this.options.base || URI.base;
+               if (!uri) uri = base;
+
+               if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+               else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+       },
+
+       parse: function(value, base){
+               var bits = value.match(this.regex);
+               if (!bits) return false;
+               bits.shift();
+               return this.merge(bits.associate(this.parts), base);
+       },
+
+       merge: function(bits, base){
+               if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+               if (base){
+                       this.parts.every(function(part){
+                               if (bits[part]) return false;
+                               bits[part] = base[part] || '';
+                               return true;
+                       });
+               }
+               bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+               bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+               return bits;
+       },
+
+       parseDirectory: function(directory, baseDirectory){
+               directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+               if (!directory.test(URI.regs.directoryDot)) return directory;
+               var result = [];
+               directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+                       if (dir == '..' && result.length > 0) result.pop();
+                       else if (dir != '.') result.push(dir);
+               });
+               return result.join('/') + '/';
+       },
+
+       combine: function(bits){
+               return bits.value || bits.scheme + '://' +
+                       (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+                       (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+                       (bits.directory || '/') + (bits.file || '') +
+                       (bits.query ? '?' + bits.query : '') +
+                       (bits.fragment ? '#' + bits.fragment : '');
+       },
+
+       set: function(part, value, base){
+               if (part == 'value'){
+                       var scheme = value.match(URI.regs.scheme);
+                       if (scheme) scheme = scheme[1];
+                       if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+                       else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+               } else if (part == 'data'){
+                       this.setData(value);
+               } else {
+                       this.parsed[part] = value;
+               }
+               return this;
+       },
+
+       get: function(part, base){
+               switch (part){
+                       case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+                       case 'data' : return this.getData();
+               }
+               return this.parsed[part] || '';
+       },
+
+       go: function(){
+               document.location.href = this.toString();
+       },
+
+       toURI: function(){
+               return this;
+       },
+
+       getData: function(key, part){
+               var qs = this.get(part || 'query');
+               if (!(qs || qs === 0)) return key ? null : {};
+               var obj = qs.parseQueryString();
+               return key ? obj[key] : obj;
+       },
+
+       setData: function(values, merge, part){
+               if (typeof values == 'string'){
+                       var data = this.getData();
+                       data[arguments[0]] = arguments[1];
+                       values = data;
+               } else if (merge){
+                       values = Object.merge(this.getData(), values);
+               }
+               return this.set(part || 'query', Object.toQueryString(values));
+       },
+
+       clearData: function(part){
+               return this.set(part || 'query', '');
+       },
+
+       toString: toString,
+       valueOf: toString
+
+});
+
+URI.regs = {
+       endSlash: /\/$/,
+       scheme: /^(\w+):/,
+       directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+       toURI: function(options){
+               return new URI(this, options);
+       }
+
+});
+
+})();
+
+
+// Begin: Source/Types/URI.Relative.js
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+  - Sebastian Markbåge
+
+
+requires:
+  - /Class.refactor
+  - /URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+       combine: function(bits, base){
+               if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+                       return this.previous.apply(this, arguments);
+               var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+               if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+               var baseDir = base.directory.split('/'),
+                       relDir = bits.directory.split('/'),
+                       path = '',
+                       offset;
+
+               var i = 0;
+               for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+               for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+               for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+               return (path || (bits.file ? '' : './')) + end;
+       },
+
+       toAbsolute: function(base){
+               base = new URI(base);
+               if (base) base.set('directory', '').set('file', '');
+               return this.toRelative(base);
+       },
+
+       toRelative: function(base){
+               return this.get('value', new URI(base));
+       }
+
+});
+
+
+// Begin: Source/Utilities/Cookie.js
+/*
+---
+
+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();
+};
+
+
+// Begin: Source/Locale/Locale.en-US.Date.js
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - /Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+       months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+       months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+       days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+       days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+       // Culture's date order: MM/DD/YYYY
+       dateOrder: ['month', 'date', 'year'],
+       shortDate: '%m/%d/%Y',
+       shortTime: '%I:%M%p',
+       AM: 'AM',
+       PM: 'PM',
+       firstDayOfWeek: 0,
+
+       // Date.Extras
+       ordinal: function(dayOfMonth){
+               // 1st, 2nd, 3rd, etc.
+               return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+       },
+
+       lessThanMinuteAgo: 'less than a minute ago',
+       minuteAgo: 'about a minute ago',
+       minutesAgo: '{delta} minutes ago',
+       hourAgo: 'about an hour ago',
+       hoursAgo: 'about {delta} hours ago',
+       dayAgo: '1 day ago',
+       daysAgo: '{delta} days ago',
+       weekAgo: '1 week ago',
+       weeksAgo: '{delta} weeks ago',
+       monthAgo: '1 month ago',
+       monthsAgo: '{delta} months ago',
+       yearAgo: '1 year ago',
+       yearsAgo: '{delta} years ago',
+
+       lessThanMinuteUntil: 'less than a minute from now',
+       minuteUntil: 'about a minute from now',
+       minutesUntil: '{delta} minutes from now',
+       hourUntil: 'about an hour from now',
+       hoursUntil: 'about {delta} hours from now',
+       dayUntil: '1 day from now',
+       daysUntil: '{delta} days from now',
+       weekUntil: '1 week from now',
+       weeksUntil: '{delta} weeks from now',
+       monthUntil: '1 month from now',
+       monthsUntil: '{delta} months from now',
+       yearUntil: '1 year from now',
+       yearsUntil: '{delta} years from now'
+
+});
+
+
+// Begin: Source/Types/Date.js
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+  - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+  - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+  - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+  - Core/Array
+  - Core/String
+  - Core/Number
+  - MooTools.More
+  - Locale
+  - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+       ms: 'Milliseconds',
+       year: 'FullYear',
+       min: 'Minutes',
+       mo: 'Month',
+       sec: 'Seconds',
+       hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+       'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+       'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+       Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+       if (digits == 1) return n;
+       return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+       set: function(prop, value){
+               prop = prop.toLowerCase();
+               var method = DateMethods[prop] && 'set' + DateMethods[prop];
+               if (method && this[method]) this[method](value);
+               return this;
+       }.overloadSetter(),
+
+       get: function(prop){
+               prop = prop.toLowerCase();
+               var method = DateMethods[prop] && 'get' + DateMethods[prop];
+               if (method && this[method]) return this[method]();
+               return null;
+       }.overloadGetter(),
+
+       clone: function(){
+               return new Date(this.get('time'));
+       },
+
+       increment: function(interval, times){
+               interval = interval || 'day';
+               times = times != null ? times : 1;
+
+               switch (interval){
+                       case 'year':
+                               return this.increment('month', times * 12);
+                       case 'month':
+                               var d = this.get('date');
+                               this.set('date', 1).set('mo', this.get('mo') + times);
+                               return this.set('date', d.min(this.get('lastdayofmonth')));
+                       case 'week':
+                               return this.increment('day', times * 7);
+                       case 'day':
+                               return this.set('date', this.get('date') + times);
+               }
+
+               if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+               return this.set('time', this.get('time') + times * Date.units[interval]());
+       },
+
+       decrement: function(interval, times){
+               return this.increment(interval, -1 * (times != null ? times : 1));
+       },
+
+       isLeapYear: function(){
+               return Date.isLeapYear(this.get('year'));
+       },
+
+       clearTime: function(){
+               return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+       },
+
+       diff: function(date, resolution){
+               if (typeOf(date) == 'string') date = Date.parse(date);
+
+               return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+       },
+
+       getLastDayOfMonth: function(){
+               return Date.daysInMonth(this.get('mo'), this.get('year'));
+       },
+
+       getDayOfYear: function(){
+               return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+                       - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+       },
+
+       setDay: function(day, firstDayOfWeek){
+               if (firstDayOfWeek == null){
+                       firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+                       if (firstDayOfWeek === '') firstDayOfWeek = 1;
+               }
+
+               day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+               var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+               return this.increment('day', day - currentDay);
+       },
+
+       getWeek: function(firstDayOfWeek){
+               if (firstDayOfWeek == null){
+                       firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+                       if (firstDayOfWeek === '') firstDayOfWeek = 1;
+               }
+
+               var date = this,
+                       dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+                       dividend = 0,
+                       firstDayOfYear;
+
+               if (firstDayOfWeek == 1){
+                       // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+                       var month = date.get('month'),
+                               startOfWeek = date.get('date') - dayOfWeek;
+
+                       if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+                       if (month == 0 && startOfWeek < -2){
+                               // Use a date from last year to determine the week
+                               date = new Date(date).decrement('day', dayOfWeek);
+                               dayOfWeek = 0;
+                       }
+
+                       firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+                       if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+               } else {
+                       // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+                       // Days in the same week can have a different weeknumber if the week spreads across two years.
+                       firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+               }
+
+               dividend += date.get('dayofyear');
+               dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+               dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+               return (dividend / 7);
+       },
+
+       getOrdinal: function(day){
+               return Date.getMsg('ordinal', day || this.get('date'));
+       },
+
+       getTimezone: function(){
+               return this.toString()
+                       .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+                       .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+       },
+
+       getGMTOffset: function(){
+               var off = this.get('timezoneOffset');
+               return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+       },
+
+       setAMPM: function(ampm){
+               ampm = ampm.toUpperCase();
+               var hr = this.get('hr');
+               if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+               else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+               return this;
+       },
+
+       getAMPM: function(){
+               return (this.get('hr') < 12) ? 'AM' : 'PM';
+       },
+
+       parse: function(str){
+               this.set('time', Date.parse(str));
+               return this;
+       },
+
+       isValid: function(date){
+               if (!date) date = this;
+               return typeOf(date) == 'date' && !isNaN(date.valueOf());
+       },
+
+       format: function(format){
+               if (!this.isValid()) return 'invalid date';
+
+               if (!format) format = '%x %X';
+               if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+               if (typeof format == 'function') return format(this);
+
+               var d = this;
+               return format.replace(/%([a-z%])/gi,
+                       function($0, $1){
+                               switch ($1){
+                                       case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+                                       case 'A': return Date.getMsg('days')[d.get('day')];
+                                       case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+                                       case 'B': return Date.getMsg('months')[d.get('month')];
+                                       case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+                                       case 'd': return pad(d.get('date'), 2);
+                                       case 'e': return pad(d.get('date'), 2, ' ');
+                                       case 'H': return pad(d.get('hr'), 2);
+                                       case 'I': return pad((d.get('hr') % 12) || 12, 2);
+                                       case 'j': return pad(d.get('dayofyear'), 3);
+                                       case 'k': return pad(d.get('hr'), 2, ' ');
+                                       case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+                                       case 'L': return pad(d.get('ms'), 3);
+                                       case 'm': return pad((d.get('mo') + 1), 2);
+                                       case 'M': return pad(d.get('min'), 2);
+                                       case 'o': return d.get('ordinal');
+                                       case 'p': return Date.getMsg(d.get('ampm'));
+                                       case 's': return Math.round(d / 1000);
+                                       case 'S': return pad(d.get('seconds'), 2);
+                                       case 'T': return d.format('%H:%M:%S');
+                                       case 'U': return pad(d.get('week'), 2);
+                                       case 'w': return d.get('day');
+                                       case 'x': return d.format(Date.getMsg('shortDate'));
+                                       case 'X': return d.format(Date.getMsg('shortTime'));
+                                       case 'y': return d.get('year').toString().substr(2);
+                                       case 'Y': return d.get('year');
+                                       case 'z': return d.get('GMTOffset');
+                                       case 'Z': return d.get('Timezone');
+                               }
+                               return $1;
+                       }
+               );
+       },
+
+       toISOString: function(){
+               return this.format('iso8601');
+       }
+
+}).alias({
+       toJSON: 'toISOString',
+       compare: 'diff',
+       strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+       rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+       db: '%Y-%m-%d %H:%M:%S',
+       compact: '%Y%m%dT%H%M%S',
+       'short': '%d %b %H:%M',
+       'long': '%B %d, %Y %H:%M',
+       rfc822: function(date){
+               return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+       },
+       rfc2822: function(date){
+               return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+       },
+       iso8601: function(date){
+               return (
+                       date.getUTCFullYear() + '-' +
+                       pad(date.getUTCMonth() + 1, 2) + '-' +
+                       pad(date.getUTCDate(), 2) + 'T' +
+                       pad(date.getUTCHours(), 2) + ':' +
+                       pad(date.getUTCMinutes(), 2) + ':' +
+                       pad(date.getUTCSeconds(), 2) + '.' +
+                       pad(date.getUTCMilliseconds(), 3) + 'Z'
+               );
+       }
+};
+
+var parsePatterns = [],
+       nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+       var ret = -1,
+               translated = Date.getMsg(type + 's');
+       switch (typeOf(word)){
+               case 'object':
+                       ret = translated[word.get(type)];
+                       break;
+               case 'number':
+                       ret = translated[word];
+                       if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+                       break;
+               case 'string':
+                       var match = translated.filter(function(name){
+                               return this.test(name);
+                       }, new RegExp('^' + word, 'i'));
+                       if (!match.length) throw new Error('Invalid ' + type + ' string');
+                       if (match.length > 1) throw new Error('Ambiguous ' + type);
+                       ret = match[0];
+       }
+
+       return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+       startYear = 70;
+
+Date.extend({
+
+       getMsg: function(key, args){
+               return Locale.get('Date.' + key, args);
+       },
+
+       units: {
+               ms: Function.from(1),
+               second: Function.from(1000),
+               minute: Function.from(60000),
+               hour: Function.from(3600000),
+               day: Function.from(86400000),
+               week: Function.from(608400000),
+               month: function(month, year){
+                       var d = new Date;
+                       return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+               },
+               year: function(year){
+                       year = year || new Date().get('year');
+                       return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+               }
+       },
+
+       daysInMonth: function(month, year){
+               return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+       },
+
+       isLeapYear: function(year){
+               return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+       },
+
+       parse: function(from){
+               var t = typeOf(from);
+               if (t == 'number') return new Date(from);
+               if (t != 'string') return from;
+               from = from.clean();
+               if (!from.length) return null;
+
+               var parsed;
+               parsePatterns.some(function(pattern){
+                       var bits = pattern.re.exec(from);
+                       return (bits) ? (parsed = pattern.handler(bits)) : false;
+               });
+
+               if (!(parsed && parsed.isValid())){
+                       parsed = new Date(nativeParse(from));
+                       if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+               }
+               return parsed;
+       },
+
+       parseDay: function(day, num){
+               return parseWord('day', day, num);
+       },
+
+       parseMonth: function(month, num){
+               return parseWord('month', month, num);
+       },
+
+       parseUTC: function(value){
+               var localDate = new Date(value);
+               var utcSeconds = Date.UTC(
+                       localDate.get('year'),
+                       localDate.get('mo'),
+                       localDate.get('date'),
+                       localDate.get('hr'),
+                       localDate.get('min'),
+                       localDate.get('sec'),
+                       localDate.get('ms')
+               );
+               return new Date(utcSeconds);
+       },
+
+       orderIndex: function(unit){
+               return Date.getMsg('dateOrder').indexOf(unit) + 1;
+       },
+
+       defineFormat: function(name, format){
+               formats[name] = format;
+               return this;
+       },
+
+       //<1.2compat>
+       parsePatterns: parsePatterns,
+       //</1.2compat>
+
+       defineParser: function(pattern){
+               parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+               return this;
+       },
+
+       defineParsers: function(){
+               Array.flatten(arguments).each(Date.defineParser);
+               return this;
+       },
+
+       define2DigitYearStart: function(year){
+               startYear = year % 100;
+               startCentury = year - startYear;
+               return this;
+       }
+
+}).extend({
+       defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+       return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+               return name.substr(0, 3);
+       }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+       switch (key){
+               case 'T':
+                       return '%H:%M:%S';
+               case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+                       return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+               case 'X':
+                       return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+       }
+       return null;
+};
+
+var keys = {
+       d: /[0-2]?[0-9]|3[01]/,
+       H: /[01]?[0-9]|2[0-3]/,
+       I: /0?[1-9]|1[0-2]/,
+       M: /[0-5]?\d/,
+       s: /\d+/,
+       o: /[a-z]*/,
+       p: /[ap]\.?m\.?/,
+       y: /\d{2}|\d{4}/,
+       Y: /\d{4}/,
+       z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+       currentLanguage = language;
+
+       keys.a = keys.A = regexOf('days');
+       keys.b = keys.B = regexOf('months');
+
+       parsePatterns.each(function(pattern, i){
+               if (pattern.format) parsePatterns[i] = build(pattern.format);
+       });
+};
+
+var build = function(format){
+       if (!currentLanguage) return {format: format};
+
+       var parsed = [];
+       var re = (format.source || format) // allow format to be regex
+        .replace(/%([a-z])/gi,
+               function($0, $1){
+                       return replacers($1) || $0;
+               }
+       ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+        .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+        .replace(/%([a-z%])/gi,
+               function($0, $1){
+                       var p = keys[$1];
+                       if (!p) return $1;
+                       parsed.push($1);
+                       return '(' + p.source + ')';
+               }
+       ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+       return {
+               format: format,
+               re: new RegExp('^' + re + '$', 'i'),
+               handler: function(bits){
+                       bits = bits.slice(1).associate(parsed);
+                       var date = new Date().clearTime(),
+                               year = bits.y || bits.Y;
+
+                       if (year != null) handle.call(date, 'y', year); // need to start in the right year
+                       if ('d' in bits) handle.call(date, 'd', 1);
+                       if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+                       for (var key in bits) handle.call(date, key, bits[key]);
+                       return date;
+               }
+       };
+};
+
+var handle = function(key, value){
+       if (!value) return this;
+
+       switch (key){
+               case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+               case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+               case 'd': return this.set('date', value);
+               case 'H': case 'I': return this.set('hr', value);
+               case 'm': return this.set('mo', value - 1);
+               case 'M': return this.set('min', value);
+               case 'p': return this.set('ampm', value.replace(/\./g, ''));
+               case 'S': return this.set('sec', value);
+               case 's': return this.set('ms', ('0.' + value) * 1000);
+               case 'w': return this.set('day', value);
+               case 'Y': return this.set('year', value);
+               case 'y':
+                       value = +value;
+                       if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+                       return this.set('year', value);
+               case 'z':
+                       if (value == 'Z') value = '+00';
+                       var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+                       offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+                       return this.set('time', this - offset * 60000);
+       }
+
+       return this;
+};
+
+Date.defineParsers(
+       '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+       '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+       '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+       '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+       '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+       '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+       '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+       '%T', // %H:%M:%S
+       '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+       if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+
+// Begin: Source/Types/Date.Extras.js
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+  - Scott Kyle
+
+requires:
+  - /Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+       timeDiffInWords: function(to){
+               return Date.distanceOfTimeInWords(this, to || new Date);
+       },
+
+       timeDiff: function(to, separator){
+               if (to == null) to = new Date;
+               var delta = ((to - this) / 1000).floor().abs();
+
+               var vals = [],
+                       durations = [60, 60, 24, 365, 0],
+                       names = ['s', 'm', 'h', 'd', 'y'],
+                       value, duration;
+
+               for (var item = 0; item < durations.length; item++){
+                       if (item && !delta) break;
+                       value = delta;
+                       if ((duration = durations[item])){
+                               value = (delta % duration);
+                               delta = (delta / duration).floor();
+                       }
+                       vals.unshift(value + (names[item] || ''));
+               }
+
+               return vals.join(separator || ':');
+       }
+
+}).extend({
+
+       distanceOfTimeInWords: function(from, to){
+               return Date.getTimePhrase(((to - from) / 1000).toInt());
+       },
+
+       getTimePhrase: function(delta){
+               var suffix = (delta < 0) ? 'Until' : 'Ago';
+               if (delta < 0) delta *= -1;
+
+               var units = {
+                       minute: 60,
+                       hour: 60,
+                       day: 24,
+                       week: 7,
+                       month: 52 / 12,
+                       year: 12,
+                       eon: Infinity
+               };
+
+               var msg = 'lessThanMinute';
+
+               for (var unit in units){
+                       var interval = units[unit];
+                       if (delta < 1.5 * interval){
+                               if (delta > 0.75 * interval) msg = unit;
+                               break;
+                       }
+                       delta /= interval;
+                       msg = unit + 's';
+               }
+
+               delta = delta.round();
+               return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+       }
+
+}).defineParsers(
+
+       {
+               // "today", "tomorrow", "yesterday"
+               re: /^(?:tod|tom|yes)/i,
+               handler: function(bits){
+                       var d = new Date().clearTime();
+                       switch (bits[0]){
+                               case 'tom': return d.increment();
+                               case 'yes': return d.decrement();
+                               default: return d;
+                       }
+               }
+       },
+
+       {
+               // "next Wednesday", "last Thursday"
+               re: /^(next|last) ([a-z]+)$/i,
+               handler: function(bits){
+                       var d = new Date().clearTime();
+                       var day = d.getDay();
+                       var newDay = Date.parseDay(bits[2], true);
+                       var addDays = newDay - day;
+                       if (newDay <= day) addDays += 7;
+                       if (bits[1] == 'last') addDays -= 7;
+                       return d.set('date', d.getDate() + addDays);
+               }
+       }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+
+// Begin: Source/Fx/Fx.Elements.js
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+
+requires:
+  - Core/Fx.CSS
+  - /MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+       Extends: Fx.CSS,
+
+       initialize: function(elements, options){
+               this.elements = this.subject = $$(elements);
+               this.parent(options);
+       },
+
+       compute: function(from, to, delta){
+               var now = {};
+
+               for (var i in from){
+                       var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+                       for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+               }
+
+               return now;
+       },
+
+       set: function(now){
+               for (var i in now){
+                       if (!this.elements[i]) continue;
+
+                       var iNow = now[i];
+                       for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+               }
+
+               return this;
+       },
+
+       start: function(obj){
+               if (!this.check(obj)) return this;
+               var from = {}, to = {};
+
+               for (var i in obj){
+                       if (!this.elements[i]) continue;
+
+                       var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+                       for (var p in iProps){
+                               var parsed = this.prepare(this.elements[i], p, iProps[p]);
+                               iFrom[p] = parsed.from;
+                               iTo[p] = parsed.to;
+                       }
+               }
+
+               return this.parent(from, to);
+       }
+
+});
+
+
+// Begin: Source/Fx/Fx.Accordion.js
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+
+requires:
+  - Core/Element.Event
+  - /Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+       Extends: Fx.Elements,
+
+       options: {/*
+               onActive: function(toggler, section){},
+               onBackground: function(toggler, section){},*/
+               fixedHeight: false,
+               fixedWidth: false,
+               display: 0,
+               show: false,
+               height: true,
+               width: false,
+               opacity: true,
+               alwaysHide: false,
+               trigger: 'click',
+               initialDisplayFx: true,
+               resetHeight: true
+       },
+
+       initialize: function(){
+               var defined = function(obj){
+                       return obj != null;
+               };
+
+               var params = Array.link(arguments, {
+                       'container': Type.isElement, //deprecated
+                       'options': Type.isObject,
+                       'togglers': defined,
+                       'elements': defined
+               });
+               this.parent(params.elements, params.options);
+
+               var options = this.options,
+                       togglers = this.togglers = $$(params.togglers);
+
+               this.previous = -1;
+               this.internalChain = new Chain();
+
+               if (options.alwaysHide) this.options.link = 'chain';
+
+               if (options.show || this.options.show === 0){
+                       options.display = false;
+                       this.previous = options.show;
+               }
+
+               if (options.start){
+                       options.display = false;
+                       options.show = false;
+               }
+
+               var effects = this.effects = {};
+
+               if (options.opacity) effects.opacity = 'fullOpacity';
+               if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+               if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+               for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+               this.elements.each(function(el, i){
+                       if (options.show === i){
+                               this.fireEvent('active', [togglers[i], el]);
+                       } else {
+                               for (var fx in effects) el.setStyle(fx, 0);
+                       }
+               }, this);
+
+               if (options.display || options.display === 0 || options.initialDisplayFx === false){
+                       this.display(options.display, options.initialDisplayFx);
+               }
+
+               if (options.fixedHeight !== false) options.resetHeight = false;
+               this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+       },
+
+       addSection: function(toggler, element){
+               toggler = document.id(toggler);
+               element = document.id(element);
+               this.togglers.include(toggler);
+               this.elements.include(element);
+
+               var togglers = this.togglers,
+                       options = this.options,
+                       test = togglers.contains(toggler),
+                       idx = togglers.indexOf(toggler),
+                       displayer = this.display.pass(idx, this);
+
+               toggler.store('accordion:display', displayer)
+                       .addEvent(options.trigger, displayer);
+
+               if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+               if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+               element.fullOpacity = 1;
+               if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+               if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+               element.setStyle('overflow', 'hidden');
+
+               if (!test) for (var fx in this.effects){
+                       element.setStyle(fx, 0);
+               }
+               return this;
+       },
+
+       removeSection: function(toggler, displayIndex){
+               var togglers = this.togglers,
+                       idx = togglers.indexOf(toggler),
+                       element = this.elements[idx];
+
+               var remover = function(){
+                       togglers.erase(toggler);
+                       this.elements.erase(element);
+                       this.detach(toggler);
+               }.bind(this);
+
+               if (this.now == idx || displayIndex != null){
+                       this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+               } else {
+                       remover();
+               }
+               return this;
+       },
+
+       detach: function(toggler){
+               var remove = function(toggler){
+                       toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+               }.bind(this);
+
+               if (!toggler) this.togglers.each(remove);
+               else remove(toggler);
+               return this;
+       },
+
+       display: function(index, useFx){
+               if (!this.check(index, useFx)) return this;
+
+               var obj = {},
+                       elements = this.elements,
+                       options = this.options,
+                       effects = this.effects;
+
+               if (useFx == null) useFx = true;
+               if (typeOf(index) == 'element') index = elements.indexOf(index);
+               if (index == this.previous && !options.alwaysHide) return this;
+
+               if (options.resetHeight){
+                       var prev = elements[this.previous];
+                       if (prev && !this.selfHidden){
+                               for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+                       }
+               }
+
+               if ((this.timer && options.link == 'chain') || (index === this.previous && !options.alwaysHide)) return this;
+
+               this.previous = index;
+               this.selfHidden = false;
+
+               elements.each(function(el, i){
+                       obj[i] = {};
+                       var hide;
+                       if (i != index){
+                               hide = true;
+                       } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+                               hide = true;
+                               this.selfHidden = true;
+                       }
+                       this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+                       for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+                       if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+               }, this);
+
+               this.internalChain.clearChain();
+               this.internalChain.chain(function(){
+                       if (options.resetHeight && !this.selfHidden){
+                               var el = elements[index];
+                               if (el) el.setStyle('height', 'auto');
+                       }
+               }.bind(this));
+
+               return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+       }
+
+});
+
+/*<1.2compat>*/
+/*
+       Compatibility with 1.2.0
+*/
+var Accordion = new Class({
+
+       Extends: Fx.Accordion,
+
+       initialize: function(){
+               this.parent.apply(this, arguments);
+               var params = Array.link(arguments, {'container': Type.isElement});
+               this.container = params.container;
+       },
+
+       addSection: function(toggler, element, pos){
+               toggler = document.id(toggler);
+               element = document.id(element);
+
+               var test = this.togglers.contains(toggler);
+               var len = this.togglers.length;
+               if (len && (!test || pos)){
+                       pos = pos != null ? pos : len - 1;
+                       toggler.inject(this.togglers[pos], 'before');
+                       element.inject(toggler, 'after');
+               } else if (this.container && !test){
+                       toggler.inject(this.container);
+                       element.inject(this.container);
+               }
+               return this.parent.apply(this, arguments);
+       }
+
+});
+/*</1.2compat>*/
+
+
+// Begin: Source/Types/Hash.js
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+  - Core/Object
+  - /MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+       if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+       for (var key in object) this[key] = object[key];
+       return this;
+});
+
+this.$H = function(object){
+       return new Hash(object);
+};
+
+Hash.implement({
+
+       forEach: function(fn, bind){
+               Object.forEach(this, fn, bind);
+       },
+
+       getClean: function(){
+               var clean = {};
+               for (var key in this){
+                       if (this.hasOwnProperty(key)) clean[key] = this[key];
+               }
+               return clean;
+       },
+
+       getLength: function(){
+               var length = 0;
+               for (var key in this){
+                       if (this.hasOwnProperty(key)) length++;
+               }
+               return length;
+       }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+       has: Object.prototype.hasOwnProperty,
+
+       keyOf: function(value){
+               return Object.keyOf(this, value);
+       },
+
+       hasValue: function(value){
+               return Object.contains(this, value);
+       },
+
+       extend: function(properties){
+               Hash.each(properties || {}, function(value, key){
+                       Hash.set(this, key, value);
+               }, this);
+               return this;
+       },
+
+       combine: function(properties){
+               Hash.each(properties || {}, function(value, key){
+                       Hash.include(this, key, value);
+               }, this);
+               return this;
+       },
+
+       erase: function(key){
+               if (this.hasOwnProperty(key)) delete this[key];
+               return this;
+       },
+
+       get: function(key){
+               return (this.hasOwnProperty(key)) ? this[key] : null;
+       },
+
+       set: function(key, value){
+               if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+               return this;
+       },
+
+       empty: function(){
+               Hash.each(this, function(value, key){
+                       delete this[key];
+               }, this);
+               return this;
+       },
+
+       include: function(key, value){
+               if (this[key] == undefined) this[key] = value;
+               return this;
+       },
+
+       map: function(fn, bind){
+               return new Hash(Object.map(this, fn, bind));
+       },
+
+       filter: function(fn, bind){
+               return new Hash(Object.filter(this, fn, bind));
+       },
+
+       every: function(fn, bind){
+               return Object.every(this, fn, bind);
+       },
+
+       some: function(fn, bind){
+               return Object.some(this, fn, bind);
+       },
+
+       getKeys: function(){
+               return Object.keys(this);
+       },
+
+       getValues: function(){
+               return Object.values(this);
+       },
+
+       toQueryString: function(base){
+               return Object.toQueryString(this, base);
+       }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+
+// Begin: Source/Utilities/Hash.Cookie.js
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+  - Aaron Newton
+
+requires:
+  - Core/Cookie
+  - Core/JSON
+  - /MooTools.More
+  - /Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+       Extends: Cookie,
+
+       options: {
+               autoSave: true
+       },
+
+       initialize: function(name, options){
+               this.parent(name, options);
+               this.load();
+       },
+
+       save: function(){
+               var value = JSON.encode(this.hash);
+               if (!value || value.length > 4096) return false; //cookie would be truncated!
+               if (value == '{}') this.dispose();
+               else this.write(value);
+               return true;
+       },
+
+       load: function(){
+               this.hash = new Hash(JSON.decode(this.read(), true));
+               return this;
+       }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+       if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+               var value = method.apply(this.hash, arguments);
+               if (this.options.autoSave) this.save();
+               return value;
+       });
+});
+
+
+// Begin: Source/Utilities/Assets.js
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+
+requires:
+  - Core/Element.Event
+  - /MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+       javascript: function(source, properties){
+               if (!properties) properties = {};
+
+               var script = new Element('script', {src: source, type: 'text/javascript'}),
+                       doc = properties.document || document,
+                       load = properties.onload || properties.onLoad;
+
+               delete properties.onload;
+               delete properties.onLoad;
+               delete properties.document;
+
+               if (load){
+                       if (!script.addEventListener){
+                               script.addEvent('readystatechange', function(){
+                                       if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+                               });
+                       } else {
+                               script.addEvent('load', load);
+                       }
+               }
+
+               return script.set(properties).inject(doc.head);
+       },
+
+       css: function(source, properties){
+               if (!properties) properties = {};
+
+               var link = new Element('link', {
+                       rel: 'stylesheet',
+                       media: 'screen',
+                       type: 'text/css',
+                       href: source
+               });
+
+               var load = properties.onload || properties.onLoad,
+                       doc = properties.document || document;
+
+               delete properties.onload;
+               delete properties.onLoad;
+               delete properties.document;
+
+               if (load) link.addEvent('load', load);
+               return link.set(properties).inject(doc.head);
+       },
+
+       image: function(source, properties){
+               if (!properties) properties = {};
+
+               var image = new Image(),
+                       element = document.id(image) || new Element('img');
+
+               ['load', 'abort', 'error'].each(function(name){
+                       var type = 'on' + name,
+                               cap = 'on' + name.capitalize(),
+                               event = properties[type] || properties[cap] || function(){};
+
+                       delete properties[cap];
+                       delete properties[type];
+
+                       image[type] = function(){
+                               if (!image) return;
+                               if (!element.parentNode){
+                                       element.width = image.width;
+                                       element.height = image.height;
+                               }
+                               image = image.onload = image.onabort = image.onerror = null;
+                               event.delay(1, element, element);
+                               element.fireEvent(name, element, 1);
+                       };
+               });
+
+               image.src = element.src = source;
+               if (image && image.complete) image.onload.delay(1);
+               return element.set(properties);
+       },
+
+       images: function(sources, options){
+               sources = Array.from(sources);
+
+               var fn = function(){},
+                       counter = 0;
+
+               options = Object.merge({
+                       onComplete: fn,
+                       onProgress: fn,
+                       onError: fn,
+                       properties: {}
+               }, options);
+
+               return new Elements(sources.map(function(source, index){
+                       return Asset.image(source, Object.append(options.properties, {
+                               onload: function(){
+                                       counter++;
+                                       options.onProgress.call(this, counter, index, source);
+                                       if (counter == sources.length) options.onComplete();
+                               },
+                               onerror: function(){
+                                       counter++;
+                                       options.onError.call(this, counter, index, source);
+                                       if (counter == sources.length) options.onComplete();
+                               }
+                       }));
+               }));
+       }
+
+};
+
+
+// Begin: Source/Interface/Spinner.js
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+  - Aaron Newton
+
+requires:
+  - Core/Fx.Tween
+  - Core/Request
+  - /Class.refactor
+  - /Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+       Extends: Mask,
+
+       Implements: Chain,
+
+       options: {/*
+               message: false,*/
+               'class': 'spinner',
+               containerPosition: {},
+               content: {
+                       'class': 'spinner-content'
+               },
+               messageContainer: {
+                       'class': 'spinner-msg'
+               },
+               img: {
+                       'class': 'spinner-img'
+               },
+               fxOptions: {
+                       link: 'chain'
+               }
+       },
+
+       initialize: function(target, options){
+               this.target = document.id(target) || document.id(document.body);
+               this.target.store('spinner', this);
+               this.setOptions(options);
+               this.render();
+               this.inject();
+
+               // Add this to events for when noFx is true; parent methods handle hide/show.
+               var deactivate = function(){ this.active = false; }.bind(this);
+               this.addEvents({
+                       hide: deactivate,
+                       show: deactivate
+               });
+       },
+
+       render: function(){
+               this.parent();
+
+               this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+               this.content = document.id(this.options.content) || new Element('div', this.options.content);
+               this.content.inject(this.element);
+
+               if (this.options.message){
+                       this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+                       this.msg.inject(this.content);
+               }
+
+               if (this.options.img){
+                       this.img = document.id(this.options.img) || new Element('div', this.options.img);
+                       this.img.inject(this.content);
+               }
+
+               this.element.set('tween', this.options.fxOptions);
+       },
+
+       show: function(noFx){
+               if (this.active) return this.chain(this.show.bind(this));
+               if (!this.hidden){
+                       this.callChain.delay(20, this);
+                       return this;
+               }
+
+               this.active = true;
+
+               return this.parent(noFx);
+       },
+
+       showMask: function(noFx){
+               var pos = function(){
+                       this.content.position(Object.merge({
+                               relativeTo: this.element
+                       }, this.options.containerPosition));
+               }.bind(this);
+
+               if (noFx){
+                       this.parent();
+                       pos();
+               } else {
+                       if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+                       this.element.setStyles({
+                               display: 'block',
+                               opacity: 0
+                       }).tween('opacity', this.options.style.opacity);
+                       pos();
+                       this.hidden = false;
+                       this.fireEvent('show');
+                       this.callChain();
+               }
+       },
+
+       hide: function(noFx){
+               if (this.active) return this.chain(this.hide.bind(this));
+               if (this.hidden){
+                       this.callChain.delay(20, this);
+                       return this;
+               }
+               this.active = true;
+               return this.parent(noFx);
+       },
+
+       hideMask: function(noFx){
+               if (noFx) return this.parent();
+               this.element.tween('opacity', 0).get('tween').chain(function(){
+                       this.element.setStyle('display', 'none');
+                       this.hidden = true;
+                       this.fireEvent('hide');
+                       this.callChain();
+               }.bind(this));
+       },
+
+       destroy: function(){
+               this.content.destroy();
+               this.parent();
+               this.target.eliminate('spinner');
+       }
+
+});
+
+Request = Class.refactor(Request, {
+
+       options: {
+               useSpinner: false,
+               spinnerOptions: {},
+               spinnerTarget: false
+       },
+
+       initialize: function(options){
+               this._send = this.send;
+               this.send = function(options){
+                       var spinner = this.getSpinner();
+                       if (spinner) spinner.chain(this._send.pass(options, this)).show();
+                       else this._send(options);
+                       return this;
+               };
+               this.previous(options);
+       },
+
+       getSpinner: function(){
+               if (!this.spinner){
+                       var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+                       if (this.options.useSpinner && update){
+                               update.set('spinner', this.options.spinnerOptions);
+                               var spinner = this.spinner = update.get('spinner');
+                               ['complete', 'exception', 'cancel'].each(function(event){
+                                       this.addEvent(event, spinner.hide.bind(spinner));
+                               }, this);
+                       }
+               }
+               return this.spinner;
+       }
+
+});
+
+Element.Properties.spinner = {
+
+       set: function(options){
+               var spinner = this.retrieve('spinner');
+               if (spinner) spinner.destroy();
+               return this.eliminate('spinner').store('spinner:options', options);
+       },
+
+       get: function(){
+               var spinner = this.retrieve('spinner');
+               if (!spinner){
+                       spinner = new Spinner(this, this.retrieve('spinner:options'));
+                       this.store('spinner', spinner);
+               }
+               return spinner;
+       }
+
+};
+
+Element.implement({
+
+       spin: function(options){
+               if (options) this.set('spinner', options);
+               this.get('spinner').show();
+               return this;
+       },
+
+       unspin: function(){
+               this.get('spinner').hide();
+               return this;
+       }
+
+});
+
+
+// Begin: Source/Fx/Fx.Slide.js
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+  - Valerio Proietti
+
+requires:
+  - Core/Fx
+  - Core/Element.Style
+  - /MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+       Extends: Fx,
+
+       options: {
+               mode: 'vertical',
+               wrapper: false,
+               hideOverflow: true,
+               resetHeight: false
+       },
+
+       initialize: function(element, options){
+               element = this.element = this.subject = document.id(element);
+               this.parent(options);
+               options = this.options;
+
+               var wrapper = element.retrieve('wrapper'),
+                       styles = element.getStyles('margin', 'position', 'overflow');
+
+               if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+               if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+               if (!wrapper) wrapper = new Element('div', {
+                       styles: styles
+               }).wraps(element);
+
+               element.store('wrapper', wrapper).setStyle('margin', 0);
+               if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+               this.now = [];
+               this.open = true;
+               this.wrapper = wrapper;
+
+               this.addEvent('complete', function(){
+                       this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+                       if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+               }, true);
+       },
+
+       vertical: function(){
+               this.margin = 'margin-top';
+               this.layout = 'height';
+               this.offset = this.element.offsetHeight;
+       },
+
+       horizontal: function(){
+               this.margin = 'margin-left';
+               this.layout = 'width';
+               this.offset = this.element.offsetWidth;
+       },
+
+       set: function(now){
+               this.element.setStyle(this.margin, now[0]);
+               this.wrapper.setStyle(this.layout, now[1]);
+               return this;
+       },
+
+       compute: function(from, to, delta){
+               return [0, 1].map(function(i){
+                       return Fx.compute(from[i], to[i], delta);
+               });
+       },
+
+       start: function(how, mode){
+               if (!this.check(how, mode)) return this;
+               this[mode || this.options.mode]();
+
+               var margin = this.element.getStyle(this.margin).toInt(),
+                       layout = this.wrapper.getStyle(this.layout).toInt(),
+                       caseIn = [[margin, layout], [0, this.offset]],
+                       caseOut = [[margin, layout], [-this.offset, 0]],
+                       start;
+
+               switch (how){
+                       case 'in': start = caseIn; break;
+                       case 'out': start = caseOut; break;
+                       case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+               }
+               return this.parent(start[0], start[1]);
+       },
+
+       slideIn: function(mode){
+               return this.start('in', mode);
+       },
+
+       slideOut: function(mode){
+               return this.start('out', mode);
+       },
+
+       hide: function(mode){
+               this[mode || this.options.mode]();
+               this.open = false;
+               return this.set([-this.offset, 0]);
+       },
+
+       show: function(mode){
+               this[mode || this.options.mode]();
+               this.open = true;
+               return this.set([0, this.offset]);
+       },
+
+       toggle: function(mode){
+               return this.start('toggle', mode);
+       }
+
+});
+
+Element.Properties.slide = {
+
+       set: function(options){
+               this.get('slide').cancel().setOptions(options);
+               return this;
+       },
+
+       get: function(){
+               var slide = this.retrieve('slide');
+               if (!slide){
+                       slide = new Fx.Slide(this, {link: 'cancel'});
+                       this.store('slide', slide);
+               }
+               return slide;
+       }
+
+};
+
+Element.implement({
+
+       slide: function(how, mode){
+               how = how || 'toggle';
+               var slide = this.get('slide'), toggle;
+               switch (how){
+                       case 'hide': slide.hide(mode); break;
+                       case 'show': slide.show(mode); break;
+                       case 'toggle':
+                               var flag = this.retrieve('slide:flag', slide.open);
+                               slide[flag ? 'slideOut' : 'slideIn'](mode);
+                               this.store('slide:flag', !flag);
+                               toggle = true;
+                       break;
+                       default: slide.start(how, mode);
+               }
+               if (!toggle) this.eliminate('slide:flag');
+               return this;
+       }
+
+});
+
+
+// Begin: Source/UI/StickyWin.UI.Pointy.js
+/*
+---
+name: StickyWin.UI.Pointy
+
+description: Creates an html holder for in-page popups using a default style - this one including a pointer in the specified direction.
+
+license: MIT-Style License
+
+requires: [More/Element.Shortcuts, More/Element.Position, StickyWin.UI]
+
+provides: [StickyWin.UI.Pointy, StickyWin.UI.pointy]
+...
+*/
+StickyWin.UI.Pointy = new Class({
+       Extends: StickyWin.UI,
+       options: {
+               theme: 'dark',
+               themes: {
+                       dark: {
+                               bgColor: '#333',
+                               fgColor: '#ddd',
+                               imgset: 'dark'
+                       },
+                       light: {
+                               bgColor: '#ccc',
+                               fgColor: '#333',
+                               imgset: 'light'
+                       }
+               },
+               css: "div.DefaultPointyTip {vertical-align: auto; position: relative;}"+
+               "div.DefaultPointyTip * {text-align:left !important}"+
+               "div.DefaultPointyTip .pointyWrapper div.body{background: {%bgColor%}; color: {%fgColor%}; left: 0px; right: 0px !important;padding:  0px 10px !important;margin-left: 0px !important;font-family: verdana;font-size: 11px;line-height: 13px;position: relative;}"+
+               "div.DefaultPointyTip .pointyWrapper div.top {position: relative;height: 25px; overflow: visible;}"+
+               "div.DefaultPointyTip .pointyWrapper div.top_ul{background: url({%baseHref%}{%imgset%}_back.png) top left no-repeat;width: 8px;height: 25px; position: absolute; left: 0px;}"+
+               "div.DefaultPointyTip .pointyWrapper div.top_ur{background: url({%baseHref%}{%imgset%}_back.png) top right !important;margin: 0 0 0 8px !important;height: 25px;position: relative;left: 0px !important;padding: 0;}"+
+               "div.DefaultPointyTip .pointyWrapper h1.caption{color: {%fgColor%};left: 0px !important;top: 4px !important;clear: none !important;overflow: hidden;font-weight: 700;font-size: 12px !important;position: relative;float: left;height: 22px !important;margin: 0 !important;padding: 0 !important;}"+
+               "div.DefaultPointyTip .pointyWrapper div.middle, div.DefaultPointyTip .pointyWrapper div.closeBody{background:  {%bgColor%};margin: 0 0px 0 0 !important;position: relative;top: 0 !important;}"+
+               "div.DefaultPointyTip .pointyWrapper div.middle {min-height: 16px; background:  {%bgColor%};margin: 0 0px 0 0 !important;position: relative;top: 0 !important;}"+
+               "div.DefaultPointyTip .pointyWrapper div.bottom {clear: both; width: 100% !important; background: none; height: 6px} "+
+               "div.DefaultPointyTip .pointyWrapper div.bottom_ll{font-size:1; background: url({%baseHref%}{%imgset%}_back.png) bottom left no-repeat;width: 6px;height: 6px;position: absolute; left: 0px;}"+
+               "div.DefaultPointyTip .pointyWrapper div.bottom_lr{font-size:1; background: url({%baseHref%}{%imgset%}_back.png) bottom right;height: 6px;margin: 0 0 0 6px !important;position: relative;left: 0 !important;}"+
+               "div.DefaultPointyTip .pointyWrapper div.noCaption{ height: 6px; overflow: hidden}"+
+               "div.DefaultPointyTip .pointyWrapper div.closeButton{width:13px; height:13px; background:url({%baseHref%}{%imgset%}_x.png) no-repeat; position: absolute; right: 0px; margin:0px !important; cursor:pointer; z-index: 1; top: 4px;}"+
+               "div.DefaultPointyTip .pointyWrapper div.pointyDivot {background: url({%divot%}) no-repeat;}",
+               divot: '{%baseHref%}{%imgset%}_divot.png',
+               divotSize: 22,
+               direction: 12,
+               cssId: 'defaultPointyTipStyle',
+               cssClassName: 'DefaultPointyTip'
+       },
+       initialize: function() {
+               var args = this.getArgs(arguments);
+               this.setOptions(args.options);
+               Object.append(this.options, this.options.themes[this.options.theme]);
+               this.options.baseHref = this.options.baseHref || Clientcide.assetLocation + '/PointyTip/';
+               this.options.divot = this.options.divot.substitute(this.options, /\\?\{%([^}]+)%\}/g);
+               if (Browser.ie) this.options.divot = this.options.divot.replace(/png/g, 'gif');
+               this.options.css = this.options.css.substitute(this.options, /\\?\{%([^}]+)%\}/g);
+               if (args.options && args.options.theme) {
+                       while (!this.id) {
+                               var id = Number.random(0, 999999999);
+                               if (!StickyWin.UI.Pointy[id]) {
+                                       StickyWin.UI.Pointy[id] = this;
+                                       this.id = id;
+                               }
+                       }
+                       this.options.css = this.options.css.replace(/div\.DefaultPointyTip/g, "div#pointy_"+this.id);
+                       this.options.cssId = "pointyTipStyle_" + this.id;
+               }
+               if (typeOf(this.options.direction) == 'string') {
+                       var map = {
+                               left: 9,
+                               right: 3,
+                               up: 12,
+                               down: 6
+                       };
+                       this.options.direction = map[this.options.direction];
+               }
+
+               this.parent(args.caption, args.body, this.options);
+               if (this.id) document.id(this).set('id', "pointy_"+this.id);
+       },
+       build: function(){
+               this.parent();
+               var opt = this.options;
+               this.pointyWrapper = new Element('div', {
+                       'class': 'pointyWrapper'
+               }).inject(document.id(this));
+               document.id(this).getChildren().each(function(el){
+                       if (el != this.pointyWrapper) this.pointyWrapper.grab(el);
+               }, this);
+
+               var w = opt.divotSize;
+               var h = w;
+               var left = (opt.width.toInt() - opt.divotSize)/2;
+               var orient = function(){
+                       switch(opt.direction) {
+                               case 12: case 1: case 11:
+                                       return {
+                                               height: h/2
+                                       };
+                               case 5: case 6: case 7:
+                                       return {
+                                               height: h/2,
+                                               backgroundPosition: '0 -'+h/2+'px'
+                                       };
+                               case 8: case 9: case 10:
+                                       return {
+                                               width: w/2
+                                       };
+                               case 2: case 3: case 4:
+                                       return {
+                                               width: w/2,
+                                               backgroundPosition: '100%'
+                                       };
+                       };
+               };
+               this.pointer = new Element('div', {
+                       styles: Object.append({
+                               width: w,
+                               height: h,
+                               overflow: 'hidden'
+                       }, orient()),
+                       'class': 'pointyDivot pointy_'+opt.direction
+               }).inject(this.pointyWrapper);
+       },
+       expose: function(){
+               if (document.id(this).getStyle('display') != 'none' &&
+                 document.body != document.id(this) &&
+                 document.id(document.body).contains(document.id(this))) return function(){};
+               document.id(this).setStyles({
+                       visibility: 'hidden',
+                       position: 'absolute'
+               });
+               var dispose;
+               if (document.body != document.id(this) && !document.body.contains(document.id(this))) {
+                       document.id(this).inject(document.body);
+                       dispose = true;
+               }
+               return (function(){
+                       if (dispose) document.id(this).dispose();
+                       document.id(this).setStyles({
+                               visibility: 'visible',
+                               position: 'relative'
+                       });
+               }).bind(this);
+       },
+       positionPointer: function(options){
+               if (!this.pointer) return;
+               var opt = options || this.options;
+               var pos;
+               var d = opt.direction;
+               switch (d){
+                       case 12: case 1: case 11:
+                               pos = {
+                                       edge: {x: 'center', y: 'bottom'},
+                                       position: {
+                                               x: d==12?'center':d==1?'right':'left',
+                                               y: 'top'
+                                       },
+                                       offset: {
+                                               x: (d==12?0:d==1?-1:1)*opt.divotSize,
+                                               y: 1
+                                       }
+                               };
+                               break;
+                       case 2: case 3: case 4:
+                               pos = {
+                                       edge: {x: 'left', y: 'center'},
+                                       position: {
+                                               x: 'right',
+                                               y: d==3?'center':d==2?'top':'bottom'
+                                       },
+                                       offset: {
+                                               x: -1,
+                                               y: (d==3?0:d==4?-1:1)*opt.divotSize
+                                       }
+                               };
+                               break;
+                       case 5: case 6: case 7:
+                               pos = {
+                                       edge: {x: 'center', y: 'top'},
+                                       position: {
+                                               x: d==6?'center':d==5?'right':'left',
+                                               y: 'bottom'
+                                       },
+                                       offset: {
+                                               x: (d==6?0:d==5?-1:1)*opt.divotSize,
+                                               y: -1
+                                       }
+                               };
+                               break;
+                       case 8: case 9: case 10:
+                               pos = {
+                                       edge: {x: 'right', y: 'center'},
+                                       position: {
+                                               x: 'left',
+                                               y: d==9?'center':d==10?'top':'bottom'
+                                       },
+                                       offset: {
+                                               x: 1,
+                                               y: (d==9?0:d==8?-1:1)*opt.divotSize
+                                       }
+                               };
+                               break;
+               };
+               var putItBack = this.expose();
+               this.pointer.position(Object.append({
+                       relativeTo: this.pointyWrapper,
+                       allowNegative: true
+               }, pos, options));
+               putItBack();
+       },
+       setContent: function(a1, a2){
+               this.parent(a1, a2);
+               this.top[this.h1?'removeClass':'addClass']('noCaption');
+               if (Browser.ie) document.id(this).getElements('.bottom_ll, .bottom_lr').setStyle('font-size', 1); //IE6 bullshit
+               if (this.options.closeButton) this.body.setStyle('margin-right', 6);
+               this.positionPointer();
+               return this;
+       },
+       makeCaption: function(caption){
+               this.parent(caption);
+               if (this.options.width && this.h1) this.h1.setStyle('width', (this.options.width.toInt()-(this.options.closeButton?25:15)));
+       }
+});
+
+StickyWin.UI.pointy = function(caption, body, options){
+       return document.id(new StickyWin.UI.Pointy(caption, body, options));
+};
+StickyWin.ui.pointy = StickyWin.UI.pointy;
+
+// Begin: Source/UI/StickyWin.PointyTip.js
+/*
+---
+name: StickyWin.PointyTip
+
+description: Positions a pointy tip relative to the element you specify.
+
+license: MIT-Style License
+
+requires: StickyWin.UI.Pointy
+
+provides: StickyWin.PointyTip
+
+...
+*/
+StickyWin.PointyTip = new Class({
+       Extends: StickyWin,
+       options: {
+               point: "left",
+               pointyOptions: {}
+       },
+       initialize: function(){
+               var args = this.getArgs(arguments);
+               this.setOptions(args.options);
+               var popts = this.options.pointyOptions;
+               var d = popts.direction;
+               if (!d) {
+                       var map = {
+                               left: 9,
+                               right: 3,
+                               up: 12,
+                               down: 6
+                       };
+                       d = map[this.options.point];
+                       if (!d) d = this.options.point;
+                       popts.direction = d;
+               }
+               if (!popts.width) popts.width = this.options.width;
+               this.pointy = new StickyWin.UI.Pointy(args.caption, args.body, popts);
+               this.options.content = null;
+               this.setOptions(args.options, this.getPositionSettings());
+               this.parent(this.options);
+               this.win.empty().adopt(document.id(this.pointy));
+               this.attachHandlers(this.win);
+               if (this.options.showNow) this.position();
+       },
+       getArgs: function(){
+               return StickyWin.UI.getArgs.apply(this, arguments);
+       },
+       getPositionSettings: function(){
+               var s = this.pointy.options.divotSize;
+               var d = this.options.point;
+               var offset = this.options.offset || {};
+               switch(d) {
+                       case "left": case 8: case 9: case 10:
+                               return {
+                                       edge: {
+                                               x: 'left',
+                                               y: d==10?'top':d==8?'bottom':'center'
+                                       },
+                                       position: {x: 'right', y: 'center'},
+                                       offset: {
+                                               x: s + (offset.x || 0),
+                                               y: offset.y || 0
+                                       }
+                               };
+                       case "right": case 2:  case 3: case 4:
+                               return {
+                                       edge: {
+                                               x: 'right',
+                                               y: (d==2?'top':d==4?'bottom':'center') + (offset.y || 0)
+                                       },
+                                       position: {x: 'left', y: 'center'},
+                                       offset: {
+                                               x: -s + (offset.x || 0),
+                                               y: offset.y || 0
+                                       }
+                               };
+                       case "up": case 11: case 12: case 1:
+                               return {
+                                       edge: {
+                                               x: d==11?'left':d==1?'right':'center',
+                                               y: 'top'
+                                       },
+                                       position: {     x: 'center', y: 'bottom' },
+                                       offset: {
+                                               y: s + (offset.y || 0),
+                                               x: (d==11?-s:d==1?s:0) + (offset.x || 0)
+                                       }
+                               };
+                       case "down": case 5: case 6: case 7:
+                               return {
+                                       edge: {
+                                               x: (d==7?'left':d==5?'right':'center') + (offset.x || 0),
+                                               y: 'bottom'
+                                       },
+                                       position: {x: 'center', y: 'top'},
+                                       offset: {
+                                               y: -s + (offset.y || 0),
+                                               x: (d==7?-s:d==5?s:0) + (offset.x || 0)
+                                       }
+                               };
+               };
+       },
+       setContent: function() {
+               var args = this.getArgs(arguments);
+               this.pointy.setContent(args.caption, args.body);
+               [this.pointy.h1, this.pointy.body].each(this.attachHandlers, this);
+               if (this.visible) this.position();
+               return this;
+       },
+       showWin: function(){
+               this.parent();
+               this.pointy.positionPointer();
+       },
+       position: function(options){
+               this.parent(options);
+               this.pointy.positionPointer();
+       },
+       attachHandlers: function(content) {
+               if (!content) return;
+               content.getElements('.'+this.options.closeClassName).addEvent('click', function(){ this.hide(); }.bind(this));
+               content.getElements('.'+this.options.pinClassName).addEvent('click', function(){ this.togglepin(); }.bind(this));
+       }
+});
+
+// Begin: Source/UI/StickyWin.Ajax.js
+/*
+---
+
+name: StickyWin.Ajax
+
+description: Adds ajax functionality to all the StickyWin classes.
+
+license: MIT-Style License
+
+requires: [Core/Request, StickyWin, StickyWin.UI, StickyWin.PointyTip]
+
+provides: [StickyWin.Ajax, StickyWin.Modal.Ajax, StickyWin.PointyTip.Ajax]
+
+...
+*/
+(function(){
+       var SWA = function(extend){
+               return {
+                       Extends: extend,
+                       options: {
+                               //onUpdate: function(){},
+                               url: '',
+                               showNow: false,
+                               cacheRequest: false,
+                               requestOptions: {
+                                       method: 'get',
+                                       evalScripts: true
+                               },
+                               wrapWithUi: false,
+                               caption: '',
+                               uiOptions:{},
+                               cacheRequest: false,
+                               handleResponse: function(response){
+                                       if(this.options.cacheRequest) {
+                                               this.element.store(this.Request.options.url, response);
+                                       }
+                                       var responseScript = "";
+                                       this.Request.response.text.stripScripts(function(script){       responseScript += script; });
+                                       if (this.options.wrapWithUi) response = StickyWin.ui(this.options.caption, response, this.options.uiOptions);
+                                       this.setContent(response);
+                                       this.show();
+                                       if (this.evalScripts) Browser.exec(responseScript);
+                                       this.fireEvent('update');
+                               }
+                       },
+                       initialize: function(options){
+                               var showNow;
+                               if (options && options.showNow) {
+                                       showNow = true;
+                                       options.showNow = false;
+                               }
+                               this.parent(options);
+                               this.evalScripts = this.options.requestOptions.evalScripts;
+                               this.options.requestOptions.evalScripts = false;
+                               this.createRequest();
+                               if (showNow) this.update();
+                       },
+                       createRequest: function(){
+                               this.Request = new Request(this.options.requestOptions).addEvent('onSuccess',
+                                       this.options.handleResponse.bind(this));
+                       },
+                       update: function(url, options){
+                               this.Request.options.url = url || options.url;
+                               var cachedResponse;
+                               if(this.options.cacheRequest) {
+                                       cachedResponse = this.element.retrieve(url);
+                               }
+                               if(!cachedResponse) {
+                                       this.Request.setOptions(options).send({url: url||this.options.url});
+                                       return this;
+                               } else {
+                                       this.Request.fireEvent('onSuccess', cachedResponse);
+                                       return this;
+                               }
+                       }
+               };
+       };
+       try { StickyWin.Ajax = new Class(SWA(StickyWin)); } catch(e){}
+       try { StickyWin.Modal.Ajax = new Class(SWA(StickyWin.Modal)); } catch(e){}
+       try { StickyWin.PointyTip.Ajax = new Class(SWA(StickyWin.PointyTip)); } catch(e){}
+})();
+
+// Begin: Source/Layout/MultipleOpenAccordion.js
+/*
+---
+name: MultipleOpenAccordion
+
+description: Creates a Mootools Fx.Accordion that allows the user to open more than one element.
+
+license: MIT-Style License
+
+requires: [Core/Element.Event, More/Fx.Reveal]
+
+provides: MultipleOpenAccordion
+
+...
+*/
+var MultipleOpenAccordion = new Class({
+       Implements: [Options, Events, Chain],
+       options: {
+               togglers: [],
+               elements: [],
+               openAll: false,
+               firstElementsOpen: [0],
+               fixedHeight: null,
+               fixedWidth: null,
+               height: true,
+               opacity: true,
+               width: false
+               //onActive: function(){},
+               //onBackground: function(){}
+       },
+       togglers: [],
+       elements: [],
+       initialize: function(options){
+               var args = Array.link(arguments, {options: Type.isObject, elements: Type.isElements});
+               this.setOptions(args.options);
+               elements = $$(this.options.elements);
+               $$(this.options.togglers).each(function(toggler, idx){
+                       this.addSection(toggler, elements[idx], idx);
+               }, this);
+               if (this.togglers.length) {
+                       if (this.options.openAll) this.showAll();
+                       else this.toggleSections(this.options.firstElementsOpen, false, true);
+               }
+               this.openSections = this.showSections.bind(this);
+               this.closeSections = this.hideSections.bind(this);
+       },
+       addSection: function(toggler, element){
+               toggler = document.id(toggler);
+               element = document.id(element);
+               var test = this.togglers.contains(toggler);
+               var len = this.togglers.length;
+               this.togglers.include(toggler);
+               this.elements.include(element);
+               var idx = this.togglers.indexOf(toggler);
+               var displayer = this.toggleSection.bind(this, idx);
+               toggler.addEvent('click', displayer).store('multipleOpenAccordion:display', displayer);
+               var mode;
+               if (this.options.height && this.options.width) mode = "both";
+               else mode = (this.options.height)?"vertical":"horizontal";
+               element.store('moa:reveal', new Fx.Reveal(element, {
+                       transitionOpacity: this.options.opacity,
+                       mode: mode,
+                       heightOverride: this.options.fixedHeight,
+                       widthOverride: this.options.fixedWidth
+               }));
+               return this;
+       },
+       removeSection: function(toggler) {
+               var idx = this.togglers.indexOf(toggler);
+               var element = this.elements[idx];
+               element.dissolve();
+               this.togglers.erase(toggler);
+               this.elements.erase(element);
+               this.detach(toggler);
+               return this;
+       },
+       detach: function(toggler){
+               var remove = function(toggler) {
+                       toggler.removeEvent(this.options.trigger, toggler.retrieve('multipleOpenAccordion:display'));
+               }.bind(this);
+               if (!toggler) this.togglers.each(remove);
+               else remove(toggler);
+               return this;
+       },
+       onComplete: function(idx, callChain){
+               this.fireEvent(this.elements[idx].isDisplayed()?'onActive':'onBackground', [this.togglers[idx], this.elements[idx]]);
+               this.callChain();
+               return this;
+       },
+       showSection: function(idx, useFx){
+               this.toggleSection(idx, useFx, true);
+       },
+       hideSection: function(idx, useFx){
+               this.toggleSection(idx, useFx, false);
+       },
+       toggleSection: function(idx, useFx, show, callChain){
+               var method = show?'reveal':show != null?'dissolve':'toggle';
+               callChain = [callChain, true].pick();
+               var el = this.elements[idx];
+               if (useFx != null ? useFx : true) {
+                       el.retrieve('moa:reveal')[method]().chain(
+                               this.onComplete.bind(this, idx, callChain)
+                       );
+               } else {
+                               if (method == "toggle") el.toggle();
+                               else el[method == "reveal"?'show':'hide']();
+                               this.onComplete(idx, callChain);
+               }
+               return this;
+       },
+       toggleAll: function(useFx, show){
+               var method = show?'reveal':(show!=null)?'disolve':'toggle';
+               var last = this.elements.getLast();
+               this.elements.each(function(el, idx){
+                       this.toggleSection(idx, useFx, show, el == last);
+               }, this);
+               return this;
+       },
+       toggleSections: function(sections, useFx, show) {
+               last = sections.getLast();
+               this.elements.each(function(el,idx){
+                       this.toggleSection(idx, useFx, sections.contains(idx)?show:!show, idx == last);
+               }, this);
+               return this;
+       },
+       showSections: function(sections, useFx){
+               sections.each(function(i){
+                       this.showSection(i, useFx);
+               }, this);
+       },
+       hideSections: function(sections, useFx){
+               sections.each(function(i){
+                       this.hideSection(i, useFx);
+               }, this);
+       },
+       showAll: function(useFx){
+               return this.toggleAll(useFx, true);
+       },
+       hideAll: function(useFx){
+               return this.toggleAll(useFx, false);
+       }
+});
+
+
diff --git a/plugins/debug_platform/__init__.py b/plugins/debug_platform/__init__.py
new file mode 100644 (file)
index 0000000..5dbb2b6
--- /dev/null
@@ -0,0 +1,34 @@
+from unfold.plugin       import Plugin
+from unfold.page         import Page
+from plugins.code_editor import CodeEditor
+from plugins.hazelnut.hazelnut import Hazelnut
+
+class DebugPlatform(Plugin):
+
+    def template_file(self):
+        return "debug_platform.html"
+
+    def requirements (self):
+        reqs = {
+            'js_files' : [
+                'js/debug_platform.js',
+            ] ,
+            'css_files': [
+                'css/debug_platform.css',
+            ]
+        }
+        return reqs
+
+    def json_settings_list (self):
+        return ['plugin_uuid', 'domid']
+
+    def export_json_settings (self):
+        return True
+
+    def template_env (self, request):
+        # This part should be moved to a Layout
+        env = {}
+        env['topleft'] = CodeEditor(page=self.page, lineNumbers=True).render(request)
+        env['bottomleft'] = Hazelnut(page=self.page, columns=['dummy']).render(request)
+        return env
+
diff --git a/plugins/debug_platform/debug_platform.css b/plugins/debug_platform/debug_platform.css
new file mode 100644 (file)
index 0000000..30a6a67
--- /dev/null
@@ -0,0 +1,1017 @@
+*, html, body {
+  -webkit-font-smoothing: antialiased;
+}
+
+html {
+  height: 100%;
+}
+
+/*
+body {
+  font-family: "Helvetica", "Arial", "FreeSans", "Verdana", "Tahoma", "Lucida Sans", "Lucida Sans Unicode", "Luxi Sans", sans-serif;
+  background: #F6F6F6 url(../img/initializing.png) 50% 50% no-repeat;
+  overflow: hidden;
+  padding: 0;
+  margin: 0;
+  position: relative;
+  font-size: 14px;
+}
+
+@media
+(-webkit-min-device-pixel-ratio: 2),
+(min-resolution: 192dpi) {
+  body {
+    background: #F6F6F6 url(../img/initializing@2x.png) 50% 50% no-repeat;
+    background-size: 186px 157px;
+  }
+}
+*/
+
+body .CodeMirror * {
+  -webkit-font-smoothing: subpixel-antialiased;
+}
+
+#show-result #sidebar,
+#show-result #content {
+  transition: opacity .4s;
+  -moz-transition: opacity .4s;
+  -webkit-transition: opacity .4s;
+  -o-transition: opacity .4s;
+  -ms-transition: opacity .4s;
+  -webkit-transform: translateZ(0);
+  /*-moz-transform: translateZ(0);*/
+  opacity: 0;
+}
+
+
+/* content
+ ================================================================ */
+#content {
+  margin: 14px 15px 0 232px;
+  position: relative;
+  background: #F6F6F6;
+  height: 96%;
+}
+
+/* header
+ ================================================================ */
+#header {
+  height: 44px;
+  background: #3D6AA2;
+}
+
+#branding {
+  float: left;
+  width: 200px;
+  padding: 0 7px 0 10px;
+  font-size: 10px;
+  color: #fff;
+}
+
+#home {
+  display: block;
+  width: 200px;
+  height: 44px;
+  line-height: 44px;
+  background: transparent url(../img/logo.png) no-repeat 0 50%;
+  text-indent: 45px;
+  text-transform: uppercase;
+  color: #fff;
+  text-decoration: none;
+  font-size: 17px;
+  text-indent: -900em;
+}
+
+@media
+(-webkit-min-device-pixel-ratio: 2),
+(min-resolution: 192dpi) {
+  #home {
+    background: transparent url(../img/logo@2x.png) no-repeat 0 50%;
+    background-size: 136px 23px;
+  }
+}
+
+/* sidebar
+ ================================================================ */
+#sidebar {
+  padding: 0;
+  margin: 0;
+/*  font-size: 11px;*/
+  width: 217px;
+  position: absolute;
+/*  top: 45px;
+  left: 0;*/
+  background-color: #fff;
+  color: #444;
+  height: 100%;
+}
+
+  #sidebar h3 {
+    clear: both;
+    padding: 0;
+    font-size: 12px;
+    font-weight: bold;
+  }
+
+  #sidebar input[type='text'],
+  #sidebar input[type='password'],
+  #sidebar textarea {
+    width: 186px;
+    padding: 7px 5px;
+    color: #626262;
+    font-family: "Helvetica", "Arial", "FreeSans", "Verdana", "Tahoma", "Lucida Sans", "Lucida Sans Unicode", "Luxi Sans", sans-serif;
+    font-size: 1em;
+    background: #F6F6F6;
+    border: solid 1px #C0C0C0;
+    box-shadow: inset 0 1px 2px #e4e4e4;
+    border-radius: 2px;
+    outline: none;
+  }
+
+  #sidebar input[type='text'].warning {
+    border: solid 1px #e58b85;
+    background: #f6e4e3;
+  }
+
+  #sidebar textarea {
+    height: 40px;
+  }
+
+  #sidebar a {
+    color: #4679BD;
+  }
+
+  #sidebar p {
+    margin: 0 0 7px;
+    line-height: 160%;
+  }
+
+  #sidebar select {
+    width: 100%;
+    margin: 3px 0;
+  }
+
+  #sidebar pre,
+  #sidebar code {
+    padding: 0 2px;
+    font-family: "Monaco", "Andale Mono", "Lucida Console", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
+    font-size: .9em;
+    background: #fffadb;
+  }
+
+  #sidebar .element,
+  #sidebar .text {
+    margin: 0;
+  }
+
+/*
+  #sidebar label {
+    font-size: 11px;
+  }
+*/
+  #sidebar .element strong {
+    font-weight: bold;
+  }
+
+  #sidebar .elementBody {
+    padding-top: 0;
+    border-top-style: none;
+    padding-bottom: 0;
+    border-bottom-style: none;
+    overflow: hidden;
+    opacity: 0;
+    height: 0;
+  }
+
+  #sidebar .ebCont {
+    padding: 4px 8px 8px;
+  }
+
+    #sidebar .ebCont h3 {
+      font-size: 1em;
+      padding: 4px 0 2px;
+      color: #888;
+      border-top: 1px solid #EEEFEE;
+    }
+
+#sidebar .selectPanel {
+  position: relative;
+  height: 54px;
+}
+
+  #sidebar .selectPanel .panelChoice,
+  #sidebar .selectPanel .selectFake {
+    width: 92px;
+    position: absolute;
+  }
+
+  #sidebar .selectPanel .selectFake {
+    color: #999;
+    font-size: 1em;
+    line-height: 20px;
+  }
+
+  .panelChoice.html {
+    top: 0;
+    left: 0;
+  }
+
+  .panelChoice.css {
+    top: 0;
+    right: 0;
+  }
+
+  .panelChoice.js {
+    bottom: 0;
+    left: 0;
+  }
+
+  .panelChoice.result {
+    bottom: 0;
+    right: 0;
+  }
+
+#sidebar #id_title {
+}
+
+#sidebar .toggler {
+  color: #333;
+  height: 36px;
+  line-height: 36px;
+  padding: 0 8px;
+  font-size: 13px;
+  font-weight: normal;
+  position: relative;
+}
+
+  #sidebar .toggler em {
+    color: #AEAEAE;
+    font-style: normal;
+    font-size: .8em;
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    background: #f1f1f1;
+    height: 16px;
+    padding: 0 4px;
+    line-height: 16px;
+    border-radius: 2px;
+  }
+
+
+  #sidebar .toggler.active {
+    color: #333;
+    font-weight: bold;
+  }
+
+  #sidebar .toggler.selected {
+  }
+
+  #sidebar .toggler,
+  #sidebar .element a {
+    cursor: pointer;
+    text-decoration: none;
+  }
+
+  #sidebar .element {
+    border-bottom: 1px solid #DFDFDF;
+  }
+
+  #sidebar .element a:hover {
+    text-decoration: underline;
+  }
+
+
+
+#shell_settings li {
+  margin: 5px 0;
+}
+
+#shell_settings li input {
+  margin-right: 3px;
+}
+
+#sidebar-bottom {
+   margin: 10px 0 10px 3px;
+
+}
+
+  #sidebar #share_links label,
+  #share_links_dropdown label {
+    display: block;
+    padding: 0 0 2px;
+    font-family: "Helvetica", "Arial", "FreeSans", "Verdana", "Tahoma", "Lucida Sans", "Lucida Sans Unicode", "Luxi Sans", sans-serif;
+    font-size: 11px;
+  }
+
+  #sidebar #share_links p,
+  #share_links_dropdown p {
+    margin: 0 0 7px;
+  }
+
+  #share_links_dropdown input {
+    width: 190px;
+    padding: 7px 5px;
+    color: #626262;
+    font-family: "Helvetica", "Arial", "FreeSans", "Verdana", "Tahoma", "Lucida Sans", "Lucida Sans Unicode", "Luxi Sans", sans-serif;
+    font-size: 1em;
+    border: solid 1px #C0C0C0;
+    background: #F6F6F6;
+    box-shadow: inset 0 1px 2px #e4e4e4;
+    border-radius: 2px;
+  }
+
+.libraryTagAttributes {
+   padding: 5px 0 0;
+}
+
+/* actions */
+#actions {
+  height: 44px;
+  font-size: 13px;
+}
+
+#actions ul.actionCont {
+  float: left;
+}
+
+  #actions ul.right {
+    float: right;
+  }
+
+  #actions li.actionItem {
+    float: left;
+    position: relative;
+    margin-right: 1px;
+  }
+
+  #actions li.actionItem .aiButton,
+  #actions li.actionItem .aiButton span,
+  #actions ul.dropdown li.actionItem a.aiButton {
+    height: 44px;
+  }
+
+  #actions li.actionItem a.aiButton {
+    display: block;
+    outline: none;
+    text-decoration: none;
+    color: #fff;
+    background: #355E95;
+    padding: 0 10px;
+  }
+
+  #actions li.actionItem a.aiButton span {
+    line-height: 44px;
+    margin-right: 6px;
+    font-size: .85em;
+  }
+
+  #actions li.noIcon a.aiButton span {
+    padding: 0 0 0 6px;
+    margin-right: 0;
+  }
+
+  #actions li.actionItem #mobile.aiButton span {
+    margin-right: 0;
+  }
+
+  #actions li.actionItem a.aiButton span.selected {
+    color: #555;
+    background: #fff;
+  }
+
+
+  #actions ul.dropdown .dropdownCont li {
+    line-height: 26px;
+  }
+
+  #actions ul.dropdown .dcWrapper a {
+    color: #4679BD;
+    text-decoration: none;
+  }
+
+  #actions ul.dropdown .dcWrapper a:hover {
+    text-decoration: underline;
+  }
+
+    #actions ul.dropdown .dropdownCont li a {
+      display: block;
+      text-decoration: none;
+      font-weight: bold;
+    }
+
+      #actions ul.dropdown .dropdownCont li a span {
+        margin: 0 6px;
+      }
+
+    #actions ul.dropdown .dropdownCont li a:hover {
+      background: #eee;
+    }
+
+  #actions ul.collapsed li.actionItem {
+  }
+
+  #actions ul.dropdown li.actionItem a.aiButton {
+  }
+
+    #actions ul.dropdown li.actionItem a.aiButton span {
+      padding: 0 0 0 6px;
+      margin-right: 0;
+    }
+
+  #actions #toggleSidebarUl {
+    position: absolute;
+    top: 0;
+    left: 165px;
+    margin-left: -40px;
+  }
+
+  #actions #toggleSidebar {
+    border-width: 0px;
+    display: none;
+    outline: none;
+    text-decoration: none;
+    text-shadow: 0;
+    color: #ffffff;
+    font-weight: bold;
+    font-size: 12px;
+    background: transparent;
+  }
+
+  #actions #toggleSidebar span {
+    text-indent: -999px;
+    padding: 0 8px;
+    width: 12px;
+  }
+
+  #actions #showjscode {
+    display: none;
+  }
+
+  #actions #collaborate sup {
+    background: rgba(0, 0, 0, 0.5);
+    border-radius: 2px;
+    vertical-align: middle;
+    opacity: 0.4;
+    font-size: .7em;
+    padding: 2px 3px;
+    text-transform: uppercase;
+
+  }
+
+/* select link */
+#select_link {
+  background: transparent url(../img/sprites.png) no-repeat 0 -254px;
+  color: #fdfdfd;
+  float: right;
+  width: 229px;
+  height: 27px;
+  margin: 6px 10px 0;
+  font-size: 13px;
+  padding-left: 15px;
+  line-height: 27px;
+}
+
+/* universal dropdown container
+ ================================================================ */
+div.dropdownCont {
+  position: absolute;
+  top: 44px;
+  right: -30px;
+  z-index: 200;
+  opacity: 0;
+  overflow: hidden;
+  padding: 0 30px 30px;
+}
+
+  div.dropdownCont div.dcWrapper {
+    background-color: #fff;
+    width: 200px;
+    padding: 10px;
+    margin: 0;
+    font-size: 13px;
+    box-shadow: 0 2px 30px rgba(0, 0, 0, 0.4);
+  }
+
+    #actions div.dropdownCont div.dcWrapper ul li a {
+      font-weight: normal;
+      color: #4679BD;
+      padding: 0;
+      line-height: 22px;
+    }
+
+    #actions div.dropdownCont div.dcWrapper ul li a:hover {
+      background: #fff;
+    }
+
+/* editor
+ ================================================================ */
+#content fieldset.column {
+  display: block;
+  height: 100%;
+  width: 50%;
+}
+
+#content .window {
+  width: 100%;
+  border: 1px solid #C0C0C0;
+  box-shadow: inset 0 1px 2px #e4e4e4;
+}
+
+  #content .top {
+    height: 25%;
+    position: absolute;
+    top: -6px;
+  }
+
+  #content .bottom {
+    height: 75%;
+    position: absolute;
+    bottom: -6px;
+  }
+
+  #content .right {
+    position: absolute;
+    right: -5px;
+  }
+
+  #content .left {
+    position: absolute;
+    left: -5px;
+  }
+
+  #handler_vertical {
+    width: 8px;
+    height: 100%;
+    padding: 5px 0;
+    cursor: col-resize;
+    position: absolute;
+    top: -5px;
+    left: 0;
+    background: url(../img/handle-v.png) 3px 50% no-repeat;
+
+  }
+
+  .handler_horizontal {
+    width: 100%;
+    height: 8px;
+    padding: 0 1px;
+    cursor: row-resize;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: url(../img/handle-h.png) 50% 3px no-repeat;
+  }
+
+  #content textarea,
+  #result iframe,
+  body.editbox {
+    min-width: 100px;
+    width: 100%;
+    height: 100%;
+    line-height: 15px;
+    font-family: "Monaco", "Andale Mono", "Lucida Console", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
+    font-size: 12px;
+  }
+
+  /* editors */
+  #content textarea,
+  #content iframe {
+    background: #fff;
+    box-shadow: inset 0 1px 3px #E4E4E4;
+    border: 0 !important;
+  }
+
+  /* resizing sets display to inline, this overrides */
+  #content textarea {
+    display: block;
+  }
+
+  .heyoffline_overlay {
+    z-index: 100;
+  }
+
+  /* editor labels */
+  .window_label {
+    border: solid 1px #F1F1F1;
+    background: #fff;
+    display: inline-block;
+    height: 22px;
+    padding: 0 6px;
+    line-height: 22px;
+    position: absolute;
+    top: 7px;
+    right: 6px;
+    text-align: center;
+    font-size: 12px;
+    color: #777;
+    border-radius: 1px;
+    z-index: 30;
+  }
+
+  /* fullscreen */
+  .fullscreen {
+    background: transparent url(../img/sprites.png) no-repeat -301px -320px;
+    display: block;
+    width: 31px;
+    height: 31px;
+    text-indent: -9999px;
+    z-index: 100;
+    position: absolute;
+    bottom: 6px;
+    right: 14px;
+    cursor: pointer;
+  }
+
+  /* we want editors to fill up all the space available */
+  .CodeMirror-wrapping {
+    width: 100%;
+    height: 100%;
+  }
+
+  /* shim to cover iframes when dragging */
+  .shim {
+    display: none;
+    width: 100%;
+    height: 100%;
+    z-index: 1;
+    position: absolute;
+    top: 0;
+    left: 0;
+  }
+
+/* external resources
+ ================================================================ */
+#sidebar #external-resources-form {
+  height: 30px;
+}
+
+  /* place resource button next to input */
+  #sidebar #external-resources-form #external_resource {
+    padding-right: 0px;
+    width: 160px;
+    float: left;
+    height: 14px;
+  }
+
+    /* custom style inputs/buttons */
+    #sidebar #external-resources-form .submit,
+    #sidebar .submit {
+      border: none;
+      border: solid 1px #EDEDED;
+      float: right;
+      height: 28px;
+      width: 28px;
+      padding: 0;
+      line-height: 31px;
+      text-align: center;
+      outline: none;
+      color: #333;
+    }
+
+    #sidebar #external-resources-form .submit:hover,
+    #sidebar .submit:hover {
+      text-decoration: none;
+    }
+
+    #sidebar .commonButton {
+      width: 70%;
+      float: none;
+      display: block;
+      line-height: 28px;
+      text-align: center;
+      color: #555;
+      font-size: 12px;
+      margin-top: 5px;
+    }
+
+
+/* share socially
+ ================================================================ */
+#share-social {
+
+}
+
+  #share-social li {
+  }
+
+  #actions .actionCont .dropdownCont #share-social a {
+    text-decoration: none;
+    margin-left: 5px;
+    display: inline;
+  }
+
+  #actions .actionCont .dropdownCont #share-social a:hover {
+    text-decoration: underline;
+  }
+
+  #share-social span {
+    color: #ccc;
+  }
+
+/* license text
+ ================================================================ */
+#documentation-info {
+  margin: 20px 8px 0;
+  color: #9A9A9A;
+}
+
+  #documentation-info p {
+    font-size: 1em;
+  }
+
+  #documentation-info a {
+    text-decoration: none;
+  }
+
+  #documentation-info a:hover {
+    text-decoration: underline;
+  }
+
+
+/* style the external resources list
+ ================================================================ */
+#sidebar #external_resources_list {
+}
+
+  #sidebar #external_resources_list li {
+    padding: 5px 0 3px;
+    position: relative;
+  }
+
+  #sidebar #external_resources_list li .filename {
+  }
+
+  #sidebar #external_resources_list li .remove {
+    position: absolute;
+    top: 7px;
+    right: 0;
+    display: block;
+    text-indent: -9000em;
+    width: 10px;
+    height: 10px;
+    background: url(../img/remove-resources.png) -10px 50% no-repeat;
+  }
+
+  #sidebar #external_resources_list li a.remove:hover {
+    background: url(../img/remove-resources.png) 0 50% no-repeat;
+  }
+
+  #add_external_resource {
+  }
+
+/* info box
+ ================================================================ */
+#info {
+  background: #FEFFE5;
+  padding: 7px;
+  border: 1px solid #EBBA95;
+  font-size: 11px;
+  color: #C98657;
+  margin: 7px 8px 10px;
+  line-height: 130%;
+  text-shadow: 0 1px 0 #FFF7F4;
+  position: relative;
+  display: none;
+
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+}
+
+  #info #info-close {
+    width: 10px;
+    height: 10px;
+    position: absolute;
+    top: 4px;
+    right: 4px;
+    text-indent: -900em;
+    display: block;
+    background: url(../img/info-close.png);
+    overflow: hidden;
+  }
+
+  #info a {
+    font-weight: bold;
+    color: #C98657;
+  }
+
+/* modal window
+ ================================================================ */
+#modal {
+  display: none;
+}
+
+.disqus_thread {
+  width: 600px;
+}
+
+  .disqus_thread .modalBody {
+    max-height: 500px;
+    min-height: 250px;
+    overflow: auto;
+    margin-bottom: 0 !important;
+  }
+
+.modal_jslint {
+  width: 500px;
+}
+
+.modal_kbd {
+  width: 500px;
+}
+
+.modal_confirmation {
+  width: 400px;
+}
+
+  .modal_jslint code,
+  .modal_Coffee .error,
+  .modal_jslint .evidence {
+    padding: 0 2px;
+    font-family: "Monaco", "Andale Mono", "Lucida Console", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
+    font-size: .9em;
+    background: #fffadb;
+  }
+
+    .modal_Coffee .error {
+        padding: 5px 10px;
+    }
+
+  .modal_jslint #errors {
+
+  }
+
+    .modal_jslint #errors p {
+      margin: 2px 0;
+    }
+
+  .modal_jslint p.evidence {
+    display: block;
+    margin-bottom: 10px !important;
+  }
+
+
+.modalWrap {
+  padding: 10px;
+  background: #fff;
+  z-index: 300;
+  font-size: 1em;
+  transition-duration: 0.2s;
+  transition-property: webkit-transform opacity;
+  transform: scale(0.94);
+
+  -webkit-transition-duration: 0.2s;
+  -webkit-transition-property: webkit-transform opacity;
+  -webkit-transform: scale(0.94);
+
+  -moz-transition-duration: 0.2s;
+  -moz-transition-property: moz-transform opacity;
+  -moz-transform: scale(0.94);
+
+  -o-transition-duration: 0.2s;
+  -o-transition-property: o-transform opacity;
+  -o-transform: scale(0.94);
+
+  -ms-transition-duration: 0.2s;
+  -ms-transition-property: ms-transform opacity;
+  -ms-transform: scale(0.94);
+
+  -webkit-transform: translateZ(0);
+  /*-moz-transform: translateZ(0);*/
+
+  opacity: 0;
+  box-shadow: 0 5px 50px rgba(0, 0, 0, 0.3);
+  border: solid 1px #ACB3B9;
+}
+
+.modalWrap.show {
+  opacity: 1;
+  transform: scale(1.0);
+  -webkit-transform: scale(1.0);
+  -moz-transform: scale(1.0);
+  -o-transform: scale(1.0);
+  -ms-transform: scale(1.0);
+}
+
+  .modalWrap .modalHeading {
+    position: relative;
+    height: 50px;
+  }
+
+    .modalWrap .modalHeading .close {
+      background: url(../img/modal/close.png) 0 0 no-repeat;
+      width: 15px;
+      height: 15px;
+      display: block;
+      position: absolute;
+      top: 8px;
+      right: 0;
+      text-indent: -9000em;
+      cursor: pointer;
+    }
+
+    .modalWrap .modalHeading h3 {
+      font-size: 1.6em;
+      padding: 6px 0;
+      cursor: default;
+    }
+
+  .modalWrap .modalBody {
+    position: relative;
+  }
+
+    .modalWrap .modalBody strong {
+      font-weight: bold;
+    }
+
+legend {
+  display: none;
+}
+
+.keyActions {
+  position: absolute;
+  bottom: 40px;
+  left: 8px;
+  display: block;
+  padding: 0 0 0 30px;
+  text-decoration: none;
+}
+
+  .keyActions:before {
+    content: '?';
+    position: absolute;
+    top: -1px;
+    left: 0;
+    display: block;
+    border: solid 1px #ABABAB;
+    padding: 0 8px;
+    border-radius: 2px;
+    font-family: 'Lucida Grande';
+    font-size: 10px;
+    color: #999;
+  }
+
+.keyActions:hover {
+  text-decoration: underline;
+}
+
+#kbd {
+}
+
+  #kbd li {
+    padding: 10px 0;
+    border-bottom: solid 1px #E6E6E6;
+  }
+
+  #kbd li:last-child {
+    padding: 10px 0 0;
+    border-bottom: none;
+  }
+
+  #kbd kbd {
+    border: solid 1px #ABABAB;
+    font-size: 10px;
+    font-family: 'Lucida Grande';
+    padding: 2px 3px;
+    border-radius: 3px;
+    color: #999;
+  }
+
+  #kbd span {
+    padding-left: 10px;
+  }
+
+.warningTooltip {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  background: rgba(255, 234, 147, 0.9);
+  font-size: .85em;
+  line-height: 1.5em;
+  color: #6c5517;
+  width: 100%;
+  z-index: 100;
+}
+
+  .warningTooltip code,
+  .warningTooltip strong {
+    font-weight: bold;
+  }
+
+  .warningTooltip li {
+    border-top: solid 1px #eddf97;
+    padding: 5px 8px;
+  }
+
+.gittip {
+  margin: 10px;
+  color: #949494;
+}
+
+.gittip iframe {
+  border: 0;
+  margin: 0 0 0 5px;
+  padding: 0;
+  opacity: 0.6;
+  vertical-align: middle;
+}
diff --git a/plugins/debug_platform/debug_platform.html b/plugins/debug_platform/debug_platform.html
new file mode 100644 (file)
index 0000000..7562ffe
--- /dev/null
@@ -0,0 +1,49 @@
+<div id='sidebar'>
+       <label>Platform:</label>
+       <select name='platform_name' id='platform_name'>
+               <optgroup label='SFA'>
+                       <option value='ple'>PLE</option>
+                       <option value='omf'>NITOS</option>
+                       <option value='senslab'>SENSLAB</option>
+               </optgroup>
+       </select>
+
+       <label>Authentication:</label>
+       <input type="radio" name="auth" value="myslice" checked>Use MANIFOLD default<br/>
+       <input type="radio" name="auth" value="myslice" disabled>Enter manually<br/>
+
+       <input id="" type="button" name="execute" value="Execute MANIFOLD query"/>
+       <input id="" type="button" name="execute" value="Execute platform query"/>
+</div>
+
+<div id='content'>
+       <fieldset class='column left'>
+               <div class='window top' id="panel_html" data-panel_type="html">
+                       <!-- <textarea id="id_code_html" rows="10" cols="40" name="code_html"></textarea>-->
+                       {{ topleft }}
+                       <span class='window_label'>HTML</span>
+               </div>
+               <div class='handler handler_horizontal'></div>
+               <div class='window bottom' id="panel_js" data-panel_type="js">
+                       <!--<textarea id="id_code_js" rows="10" cols="40" name="code_js"></textarea>-->
+                       {{ bottomleft }}
+                       <span class='window_label'>JavaScript</span>
+               </div>
+               <div class="shim"></div>
+       </fieldset>
+
+       <div class='handler' id='handler_vertical'></div>
+
+       <fieldset class='column right'>
+               <div class='window top' id="panel_css" data-panel_type="css">
+                       <textarea id="id_code_css" rows="10" cols="40" name="code_css"></textarea>
+                       <span class='window_label'>CSS</span>
+               </div>
+               <div class='handler handler_horizontal'></div>
+               <div id='result' class='window bottom'>
+                 <iframe name='result' frameBorder='0'></iframe>
+                       <span class='window_label'>Result</span>
+               </div>
+               <div class="shim"></div>
+       </fieldset>
+</div>
diff --git a/plugins/debug_platform/debug_platform.js b/plugins/debug_platform/debug_platform.js
new file mode 100644 (file)
index 0000000..cbbd0b7
--- /dev/null
@@ -0,0 +1,109 @@
+/**
+ * Description: DebugPlatform plugin
+ * Copyright (c) 2012 UPMC Sorbonne Universite - INRIA
+ * License: GPLv3
+ */
+
+/*
+ * It's a best practice to pass jQuery to an IIFE (Immediately Invoked Function
+ * Expression) that maps it to the dollar sign so it can't be overwritten by
+ * another library in the scope of its execution.
+ */
+
+(function($){
+
+    
+    // routing calls
+    jQuery.fn.DebugPlatform = function( method ) {
+               if ( methods[method] ) {
+                       return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+               } else if ( typeof method === 'object' || ! method ) {
+                       return methods.init.apply( this, arguments );
+               } else {
+                       jQuery.error( 'Method ' +  method + ' does not exist on jQuery.DebugPlatform' );
+               }    
+    };
+
+    /***************************************************************************
+     * Public methods
+     ***************************************************************************/
+
+    var methods = {
+
+        /**
+         * @brief Plugin initialization
+         * @param options : an associative array of setting values
+         * @return : a jQuery collection of objects on which the plugin is
+         *     applied, which allows to maintain chainability of calls
+         */
+        init : function ( options ) {
+
+            var default_code_mirror_options = {
+              gutters: ["note-gutter", "CodeMirror-linenumbers"],
+              tabSize: 4,
+              indentUnit: 4,
+              matchBrackets: true,
+              lineNumbers: true,
+              lineWrapping: true,
+              tabMode: 'spaces' // or 'shift'
+            };
+
+            /* Default settings */
+            var options = $.extend( {
+                useCodeMirror: true,
+                codeMirrorOptions: default_code_mirror_options,
+                syntaxHighlighting: []
+            }, options);
+
+            return this.each(function() {
+
+                var $this = $(this);
+
+                /* An object that will hold private variables and methods */
+                var plugin = new DebugPlatform(options);
+                $this.data('Manifold', plugin);
+
+                /* Events */
+                $this.on('show.DebugPlatform', methods.show);
+
+                // This is the new plugin API meant to replace the weird publish_subscribe mechanism
+            }); // this.each
+        }, // init
+
+        /**
+         * @brief Plugin destruction
+         * @return : a jQuery collection of objects on which the plugin is
+         *     applied, which allows to maintain chainability of calls
+         */
+        destroy : function( ) {
+
+            return this.each(function() {
+                var $this = $(this);
+                var hazelnut = $this.data('Manifold');
+
+                // Unbind all events using namespacing
+                $(window).unbind('Manifold');
+
+                // Remove associated data
+                hazelnut.remove();
+                $this.removeData('Manifold');
+            });
+        }, // destroy
+
+    }; // var methods;
+
+    /***************************************************************************
+     * Plugin object
+     ***************************************************************************/
+
+    function DebugPlatform(options)
+    {
+        /* member variables */
+        this.options = options;
+
+        var object = this;
+
+    } // function DebugPlatform
+
+})( jQuery );
+
diff --git a/plugins/tabs/tabs.js b/plugins/tabs/tabs.js
new file mode 100644 (file)
index 0000000..bc39890
--- /dev/null
@@ -0,0 +1,13 @@
+(function($){
+
+    $.fn.Tabs = function( method ) {
+
+        $('a[data-toggle="tab"]').on('shown', function (e) {
+          google.maps.event.trigger(map, 'resize');
+          //e.target // current tab
+          //e.relatedTarget // previous tab
+        });
+
+    };
+
+})( jQuery );