<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>config/database.yml</filename>
    </added>
    <added>
      <filename>config/initializers/inflections.rb</filename>
    </added>
    <added>
      <filename>config/initializers/mime_types.rb</filename>
    </added>
    <added>
      <filename>db/schema.rb</filename>
    </added>
    <added>
      <filename>public/422.html</filename>
    </added>
    <added>
      <filename>public/javascripts/application.js</filename>
    </added>
    <added>
      <filename>public/system/assets/1209554508.jpg</filename>
    </added>
    <added>
      <filename>public/system/assets/1209554554.jpg</filename>
    </added>
    <added>
      <filename>public/system/assets/1209554625.jpg</filename>
    </added>
    <added>
      <filename>public/system/assets/1209554639.jpg</filename>
    </added>
    <added>
      <filename>script/performance/request</filename>
    </added>
    <added>
      <filename>script/process/inspector</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff></diff>
      <filename>.DS_Store</filename>
    </modified>
    <modified>
      <diff>@@ -1,50 +1,203 @@
-What is it?
-===========
+== Welcome to Rails
 
-Gullery is a simple photo gallery for showing your portfolio or a small photo gallery.
+Rails is a web-application and persistence framework that includes everything
+needed to create database-backed web-applications according to the
+Model-View-Control pattern of separation. This pattern splits the view (also
+called the presentation) into &quot;dumb&quot; templates that are primarily responsible
+for inserting pre-built data in between HTML tags. The model contains the
+&quot;smart&quot; domain objects (such as Account, Product, Person, Post) that holds all
+the business logic and knows how to persist themselves to a database. The
+controller handles the incoming requests (such as Save New Account, Update
+Product, Show Post) by manipulating the model and directing data to the view.
 
-It is written with Ruby on Rails and is made to use as little memory as possible (for use on shared hosts or limited memory situations). Files are stored on disk and the system can be deployed with Capistrano.
+In Rails, the model is handled by what's called an object-relational mapping
+layer entitled Active Record. This layer allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. You can read more about Active Record in
+link:files/vendor/rails/activerecord/README.html.
 
-A live version is at http://geoffreygrosenbach.com/
+The controller and view are handled by the Action Pack, which handles both
+layers by its two parts: Action View and Action Controller. These two layers
+are bundled in a single package due to their heavy interdependence. This is
+unlike the relationship between the Active Record and Action Pack that is much
+more separate. Each of these packages can be used independently outside of
+Rails.  You can read more about Action Pack in
+link:files/vendor/rails/actionpack/README.html.
 
-See also &quot;Planned Features&quot; below.
 
-Requirements
-============
+== Getting Started
 
-Currently you need all of those things to get Gullery to run:
+1. At the command prompt, start a new Rails application using the &lt;tt&gt;rails&lt;/tt&gt; command
+   and your application name. Ex: rails myapp
+   (If you've downloaded Rails in a complete tgz or zip, this step is already done)
+2. Change directory into myapp and start the web server: &lt;tt&gt;script/server&lt;/tt&gt; (run with --help for options)
+3. Go to http://localhost:3000/ and get &quot;Welcome aboard: You&#8217;re riding the Rails!&quot;
+4. Follow the guidelines to start developing your application
 
- * Ruby -v of 1.8.2 (25-05-2004) or greater.
- * A database that is supported by Rails migrations.
- * Ruby drivers for your database.
- * For best performance, you should have a web server running either
-   Apache or Lighttpd along with FastCGI, although these aren't
-   strictly required--you can use Ruby's built-in web server for
-   low-volume testing.
- * ImageMagick's command line tools. Most shared hosts have this.
 
-NOTE: RMagick is not required! A current version of the Rails trunk is included for stability.
+== Web Servers
 
-Installation
-============
+By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise
+Rails will use WEBrick, the webserver that ships with Ruby. When you run script/server,
+Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures
+that you can always get up and running quickly.
 
-Create a database and customize config/deploy.rb and config/database.yml. Samples are provided.
+Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is
+suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
+getting up and running with mongrel is as easy as: &lt;tt&gt;gem install mongrel&lt;/tt&gt;.
+More info at: http://mongrel.rubyforge.org
 
- * Run 'rake migrate'
+If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than
+Mongrel and WEBrick and also suited for production use, but requires additional
+installation and currently only works well on OS X/Unix (Windows users are encouraged
+to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from
+http://www.lighttpd.net.
 
-To run on a remote server:
+And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby
+web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not
+for production.
 
- * Run 'rake remote:setup'
- * Copy your database.yml to 'shared/config/database.yml' on the remote server. This will be copied to the app when it is deployed.
- * Run 'rake deploy'
+But of course its also possible to run Rails on any platform that supports FCGI.
+Apache, LiteSpeed, IIS are just a few. For more information on FCGI,
+please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI
 
-In either case:
 
- * Navigate to 'http://your_site.com/' and you will be asked to pick a name and password.
+== Debugging Rails
 
-Planned Features
-============
+Sometimes your application goes wrong.  Fortunately there are a lot of tools that
+will help you debug it and get it back on the rails.
 
- * CSS stored in the database for easy customization.
- * 45&#730; thumbnail option
+First area to check is the application log files.  Have &quot;tail -f&quot; commands running
+on the server.log and development.log. Rails will automatically display debugging
+and runtime information to these files. Debugging info will also be shown in the
+browser on requests from 127.0.0.1.
 
+You can also log your own messages directly into the log file from your code using
+the Ruby logger class from inside your controllers. Example:
+
+  class WeblogController &lt; ActionController::Base
+    def destroy
+      @weblog = Weblog.find(params[:id])
+      @weblog.destroy
+      logger.info(&quot;#{Time.now} Destroyed Weblog ID ##{@weblog.id}!&quot;)
+    end
+  end
+
+The result will be a message in your log file along the lines of:
+
+  Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1
+
+More information on how to use the logger is at http://www.ruby-doc.org/core/
+
+Also, Ruby documentation can be found at http://www.ruby-lang.org/ including:
+
+* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/
+* Learn to Program: http://pine.fm/LearnToProgram/  (a beginners guide)
+
+These two online (and free) books will bring you up to speed on the Ruby language
+and also on programming in general.
+
+
+== Debugger
+
+Debugger support is available through the debugger command when you start your Mongrel or
+Webrick server with --debugger. This means that you can break out of execution at any point
+in the code, investigate and change the model, AND then resume execution! Example:
+
+  class WeblogController &lt; ActionController::Base
+    def index
+      @posts = Post.find(:all)
+      debugger
+    end
+  end
+
+So the controller will accept the action, run the first line, then present you
+with a IRB prompt in the server window. Here you can do things like:
+
+  &gt;&gt; @posts.inspect
+  =&gt; &quot;[#&lt;Post:0x14a6be8 @attributes={\&quot;title\&quot;=&gt;nil, \&quot;body\&quot;=&gt;nil, \&quot;id\&quot;=&gt;\&quot;1\&quot;}&gt;,
+       #&lt;Post:0x14a6620 @attributes={\&quot;title\&quot;=&gt;\&quot;Rails you know!\&quot;, \&quot;body\&quot;=&gt;\&quot;Only ten..\&quot;, \&quot;id\&quot;=&gt;\&quot;2\&quot;}&gt;]&quot;
+  &gt;&gt; @posts.first.title = &quot;hello from a debugger&quot;
+  =&gt; &quot;hello from a debugger&quot;
+
+...and even better is that you can examine how your runtime objects actually work:
+
+  &gt;&gt; f = @posts.first
+  =&gt; #&lt;Post:0x13630c4 @attributes={&quot;title&quot;=&gt;nil, &quot;body&quot;=&gt;nil, &quot;id&quot;=&gt;&quot;1&quot;}&gt;
+  &gt;&gt; f.
+  Display all 152 possibilities? (y or n)
+
+Finally, when you're ready to resume execution, you enter &quot;cont&quot;
+
+
+== Console
+
+You can interact with the domain model by starting the console through &lt;tt&gt;script/console&lt;/tt&gt;.
+Here you'll have all parts of the application configured, just like it is when the
+application is running. You can inspect domain models, change values, and save to the
+database. Starting the script without arguments will launch it in the development environment.
+Passing an argument will specify a different environment, like &lt;tt&gt;script/console production&lt;/tt&gt;.
+
+To reload your controllers and models after launching the console run &lt;tt&gt;reload!&lt;/tt&gt;
+
+
+== Description of Contents
+
+app
+  Holds all the code that's specific to this particular application.
+
+app/controllers
+  Holds controllers that should be named like weblogs_controller.rb for
+  automated URL mapping. All controllers should descend from ApplicationController
+  which itself descends from ActionController::Base.
+
+app/models
+  Holds models that should be named like post.rb.
+  Most models will descend from ActiveRecord::Base.
+
+app/views
+  Holds the template files for the view that should be named like
+  weblogs/index.erb for the WeblogsController#index action. All views use eRuby
+  syntax.
+
+app/views/layouts
+  Holds the template files for layouts to be used with views. This models the common
+  header/footer method of wrapping views. In your views, define a layout using the
+  &lt;tt&gt;layout :default&lt;/tt&gt; and create a file named default.erb. Inside default.erb,
+  call &lt;% yield %&gt; to render the view using this layout.
+
+app/helpers
+  Holds view helpers that should be named like weblogs_helper.rb. These are generated
+  for you automatically when using script/generate for controllers. Helpers can be used to
+  wrap functionality for your views into methods.
+
+config
+  Configuration files for the Rails environment, the routing map, the database, and other dependencies.
+
+db
+  Contains the database schema in schema.rb.  db/migrate contains all
+  the sequence of Migrations for your schema.
+
+doc
+  This directory is where your application documentation will be stored when generated
+  using &lt;tt&gt;rake doc:app&lt;/tt&gt;
+
+lib
+  Application specific libraries. Basically, any kind of custom code that doesn't
+  belong under controllers, models, or helpers. This directory is in the load path.
+
+public
+  The directory available for the web server. Contains subdirectories for images, stylesheets,
+  and javascripts. Also contains the dispatchers and the default HTML files. This should be
+  set as the DOCUMENT_ROOT of your web server.
+
+script
+  Helper scripts for automation and generation.
+
+test
+  Unit and functional tests along with fixtures. When using the script/generate scripts, template
+  test files will be generated for you and placed in this directory.
+
+vendor
+  External libraries that the application depends on. Also includes the plugins subdirectory.
+  This directory is in the load path.</diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 # Add your own tasks in files placed in lib/tasks ending in .rake,
-# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
 
 require(File.join(File.dirname(__FILE__), 'config', 'boot'))
 
@@ -8,6 +8,3 @@ require 'rake/testtask'
 require 'rake/rdoctask'
 
 require 'tasks/rails'
-
-# Fail if unit tests fail
-task :default =&gt; [:test_units, :test_functional]</diff>
      <filename>Rakefile</filename>
    </modified>
    <modified>
      <diff>@@ -1,12 +1,10 @@
 class AccountController &lt; ApplicationController
   include AuthenticatedSystem
 
-  observer :user_observer
-
   before_filter :login_required, :only =&gt; [:update_description]
   
   def update_description
-    current_user.description = @params[:value]
+    current_user.description = params[:value]
     if current_user.save
       render :text =&gt; textilize(current_user.description)
     end
@@ -50,7 +48,7 @@ class AccountController &lt; ApplicationController
     end
     @user = User.new(params[:user])
     return unless request.post?
-    @user.website = &quot;http://#{@params[:user][:website]}&quot;
+    @user.website = &quot;http://#{params[:user][:website]}&quot;
     if @user.save
       self.current_user = User.authenticate(params[:user][:login], params[:user][:password])
       redirect_back_or_default(:controller =&gt; '/')</diff>
      <filename>app/controllers/account_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,7 +3,8 @@
 class ApplicationController &lt; ActionController::Base
   include AuthenticatedSystem
   
-  helper_method :textilight, :textilize
+  helper_method :textilight
+  helper_method :textilize
   
   def textilight(text='')
     r = RedCloth.new text</diff>
      <filename>app/controllers/application.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,7 +3,7 @@ class AssetsController &lt; ApplicationController
   before_filter :login_required
 
   def create
-    @asset = Asset.new @params[:asset]
+    @asset = Asset.new params[:asset]
     @asset.position = Asset.count
     if @asset.save
       redirect_to projects_url(:action =&gt; 'show', :id =&gt; @asset.project_id)
@@ -13,20 +13,20 @@ class AssetsController &lt; ApplicationController
   end
 
   def destroy
-    @asset = Asset.find @params[:id]
+    @asset = Asset.find params[:id]
     @asset.destroy
   end
 
   def update_caption
-    @asset = Asset.find @params[:id]
-    @asset.caption = @params[:value]
+    @asset = Asset.find params[:id]
+    @asset.caption = params[:value]
     if @asset.save
       render :text =&gt; textilight(@asset.caption)
     end
   end
   
   def sort
-    asset_ids = @params[:asset_list]
+    asset_ids = params[:asset_list]
     asset_ids.each_with_index do |asset_id, index|
       asset = Asset.find asset_id
       asset.update_attribute(:position, index)
@@ -35,8 +35,8 @@ class AssetsController &lt; ApplicationController
   end
 
   def rotate
-    @asset = Asset.find @params[:id]
-    @asset.rotate(@params[:direction])
+    @asset = Asset.find params[:id]
+    @asset.rotate(params[:direction])
     redirect_to projects_url(:action =&gt; 'show', :id =&gt; @asset.project_id)
   end
 </diff>
      <filename>app/controllers/assets_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,16 +7,16 @@ class ProjectsController &lt; ApplicationController
       redirect_to signup_url
       return
     end
-    @user = User.find_first
+    @user = User.find :first
     @projects = Project.find :all, :order =&gt; 'position, created_at'
   end
 
   def show
-    @project = Project.find @params[:id]
+    @project = Project.find params[:id]
   end
 
   def create
-    @project = Project.new @params[:project]
+    @project = Project.new params[:project]
     @project.user_id = current_user.id
     if @project.save
       redirect_to projects_url
@@ -26,20 +26,20 @@ class ProjectsController &lt; ApplicationController
   end
   
   def update_description
-    @project = Project.find @params[:id]
-    @project.description = @params[:value]
+    @project = Project.find params[:id]
+    @project.description = params[:value]
     if @project.save
       render :text =&gt; textilize(@project.description)
     end
   end
   
   def destroy
-    @project = Project.find @params[:id]
+    @project = Project.find params[:id]
     @project.destroy
   end
 
   def sort
-    project_ids = @params[:project_list]
+    project_ids = params[:project_list]
     project_ids.each_with_index do |project_id, index|
       project = Project.find project_id
       project.update_attribute(:position, index)</diff>
      <filename>app/controllers/projects_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,12 +7,11 @@ module ApplicationHelper
   end
 
   def show_page_nav
-    user = User.find_first
+    user = User.find :first
     return 'gullery photo gallery' if user.nil?
     nav = link_to(user.company, :controller =&gt; '/')
     nav += ' ' + content_tag(:small, link_to((@project.name), projects_url(:action =&gt; 'show', :id =&gt; @project))) if @project
     nav
   end
   
-  
 end</diff>
      <filename>app/helpers/application_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,7 +4,7 @@ class Project &lt; ActiveRecord::Base
   validates_associated :user
 
   belongs_to :user
-  has_many :assets, :order =&gt; 'position, created_at', :dependent =&gt; true
+  has_many :assets, :order =&gt; 'position, created_at', :dependent =&gt; :destroy
 
   acts_as_taggable
 </diff>
      <filename>app/models/project.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,6 @@
 require 'digest/sha1'
 class User &lt; ActiveRecord::Base
+
   # Virtual attribute for the unencrypted password
   attr_accessor :password
 </diff>
      <filename>app/models/user.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,7 @@
 class UserObserver &lt; ActiveRecord::Observer
+  observe :user_observer
+
+
   def after_create(user)
     UserNotifier.deliver_signup_notification(user)
   end</diff>
      <filename>app/models/user_observer.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,12 +1,12 @@
-&lt;%= start_form_tag %&gt;
-&lt;p&gt;&lt;label for=&quot;login&quot;&gt;Login&lt;/label&gt;&lt;br/&gt;
-&lt;%= text_field_tag 'login' %&gt;&lt;/p&gt;
+&lt;% form_tag do %&gt;
+  &lt;p&gt;&lt;label for=&quot;login&quot;&gt;Login&lt;/label&gt;&lt;br/&gt;
+  &lt;%= text_field_tag 'login' %&gt;&lt;/p&gt;
 
-&lt;p&gt;&lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;&lt;br/&gt;
-&lt;%= password_field_tag 'password' %&gt;&lt;/p&gt;
+  &lt;p&gt;&lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;&lt;br/&gt;
+  &lt;%= password_field_tag 'password' %&gt;&lt;/p&gt;
 
-&lt;p&gt;&lt;label for=&quot;remember_me&quot;&gt;Remember Me?&lt;/label&gt;
-&lt;%= check_box_tag 'remember_me', 1, true %&gt;&lt;/p&gt;
+  &lt;p&gt;&lt;label for=&quot;remember_me&quot;&gt;Remember Me?&lt;/label&gt;
+  &lt;%= check_box_tag 'remember_me', 1, true %&gt;&lt;/p&gt;
 
-&lt;p&gt;&lt;%= submit_tag 'Log in' %&gt;&lt;/p&gt;
-&lt;%= end_form_tag %&gt;
\ No newline at end of file
+  &lt;p&gt;&lt;%= submit_tag 'Log in' %&gt;&lt;/p&gt;
+&lt;% end %&gt;
\ No newline at end of file</diff>
      <filename>app/views/account/login.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,17 +1,17 @@
 &lt;%= error_messages_for :user %&gt;
 
-&lt;%= start_form_tag %&gt;
-&lt;p&gt;&lt;label for=&quot;login&quot;&gt;Login&lt;/label&gt;&lt;br/&gt;
-&lt;%= text_field 'user', 'login' %&gt;&lt;/p&gt;
+&lt;% form_tag do %&gt;
+  &lt;p&gt;&lt;label for=&quot;login&quot;&gt;Login&lt;/label&gt;&lt;br/&gt;
+  &lt;%= text_field 'user', 'login' %&gt;&lt;/p&gt;
 
-&lt;p&gt;&lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;&lt;br/&gt;
-&lt;%= password_field 'user', 'password' %&gt;&lt;/p&gt;
+  &lt;p&gt;&lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;&lt;br/&gt;
+  &lt;%= password_field 'user', 'password' %&gt;&lt;/p&gt;
 
-&lt;p&gt;&lt;label for=&quot;password_confirmation&quot;&gt;Confirm Password&lt;/label&gt;&lt;br/&gt;
-&lt;%= password_field 'user', 'password_confirmation' %&gt;&lt;/p&gt;
+  &lt;p&gt;&lt;label for=&quot;password_confirmation&quot;&gt;Confirm Password&lt;/label&gt;&lt;br/&gt;
+  &lt;%= password_field 'user', 'password_confirmation' %&gt;&lt;/p&gt;
 
-&lt;p&gt;&lt;label for=&quot;company&quot;&gt;Name, Company, or Project Title&lt;/label&gt;&lt;br/&gt;
-&lt;%= text_field 'user', 'company' %&gt;&lt;/p&gt;
+  &lt;p&gt;&lt;label for=&quot;company&quot;&gt;Name, Company, or Project Title&lt;/label&gt;&lt;br/&gt;
+  &lt;%= text_field 'user', 'company' %&gt;&lt;/p&gt;
 
-&lt;p&gt;&lt;%= submit_tag 'Sign up' %&gt;&lt;/p&gt;
-&lt;%= end_form_tag %&gt;
+  &lt;p&gt;&lt;%= submit_tag 'Sign up' %&gt;&lt;/p&gt;
+&lt;% end %&gt;</diff>
      <filename>app/views/account/signup.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 @indent = 0
 li :id =&gt; &quot;asset_#{asset.id}&quot; do
-  if @session[:user]
+  if session[:user]
     div.toolbar(:width =&gt; Asset.thumbnail_width) do
       span.handle { &quot;drag&quot; }
       span.rotate do 
@@ -13,7 +13,7 @@ li :id =&gt; &quot;asset_#{asset.id}&quot; do
   end
   link_to image_tag(asset.web_path(:thumb), :width =&gt; Asset.thumbnail_width, :height =&gt; Asset.thumbnail_height), asset.web_path, :rel =&gt; 'lightbox', :title =&gt; capture { textilight(asset.caption) }
   render :partial =&gt; 'asset_caption', :locals =&gt; {:asset =&gt; asset}
-  if @session[:user]
+  if session[:user]
     in_place_editor &quot;asset_caption_#{asset.id}&quot;, :url =&gt; capture { assets_url(:action =&gt; 'update_caption', :id =&gt; asset.id) }
   end
 end</diff>
      <filename>app/views/projects/_asset.mab</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,13 @@
+&lt;%
+# hack. but markaby wont pick our our F*%&amp;ing app helpers. so whatever, this works. 
+# if you can find a better way, then please let me know --jacques
+def textilight(text='')
+  r = RedCloth.new text
+  r.hard_breaks = false
+  r.to_html.gsub(/^&lt;p&gt;/, '').gsub(/&lt;\/p&gt;$/, '')
+end
+%&gt;
+
 &lt;%= 
-content_tag(:span, textilight(asset.caption.blank? ? '&amp;nbsp;' : asset.caption), :id =&gt; &quot;asset_caption_#{asset.id}&quot;)
+  content_tag(:span, textilight(asset.caption.blank? ? '&amp;nbsp;' : asset.caption), :id =&gt; &quot;asset_caption_#{asset.id}&quot;)
  %&gt;
\ No newline at end of file</diff>
      <filename>app/views/projects/_asset_caption.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 form_tag( { :controller =&gt; 'assets', :action =&gt; 'create' }, 
-          { :multipart =&gt; true, :onsubmit =&gt; &quot;Element.show('loading')&quot; } ) 
-  hidden_field :asset, :project_id, :value =&gt; @params[:id]
+          { :multipart =&gt; true, :onsubmit =&gt; &quot;Element.show('loading')&quot; } ) do
+  hidden_field :asset, :project_id, :value =&gt; params[:id]
   self &lt;&lt; &quot;Photo: &quot;
   file_field :asset, :file_field, :size =&gt; 10
   br
@@ -8,4 +8,4 @@ form_tag( { :controller =&gt; 'assets', :action =&gt; 'create' },
   text_field :asset, :caption, :size =&gt; 20
   submit_tag &quot;Save&quot;
   image_tag 'spinner.gif', :style =&gt; 'display: none;', :id =&gt; 'loading'
-end_form_tag
+end</diff>
      <filename>app/views/projects/_asset_form.mab</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 li :id =&gt; &quot;project_#{project.id}&quot; do
-  if @session[:user]
+  if session[:user]
     div.toolbar(:width =&gt; Asset.thumbnail_width) do
       span.handle { &quot;drag&quot; }
       span.delete { link_to_remote &quot; delete &amp;#215;&quot;, :url =&gt; capture { projects_url(:action =&gt; 'destroy', :id =&gt; project.id) }, :confirm =&gt; &quot;Do you want to delete this entire project?&quot; }</diff>
      <filename>app/views/projects/_project.mab</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,7 @@
 form_tag( { :controller =&gt; 'projects', :action =&gt; 'create' }, 
-          { :onsubmit =&gt; &quot;Element.show('loading')&quot; } ) 
+          { :onsubmit =&gt; &quot;Element.show('loading')&quot; } ) do
   self &lt;&lt; &quot;New Project Name: &quot;
   text_field :project, :name, :size =&gt; 20
   submit_tag &quot;Save&quot;
   image_tag 'spinner.gif', :style =&gt; 'display: none;', :id =&gt; 'loading'
-end_form_tag
+end</diff>
      <filename>app/views/projects/_project_form.mab</filename>
    </modified>
    <modified>
      <diff>@@ -6,7 +6,7 @@ ul :id =&gt; 'project_list' do
   render :partial =&gt; 'project', :collection =&gt; @projects
 end
 
-if @session[:user]
+if session[:user]
   render(:partial =&gt; 'project_form')
   in_place_editor &quot;user_description&quot;, 
                   :url =&gt; capture { account_url(:action =&gt; 'update_description') }, </diff>
      <filename>app/views/projects/index.mab</filename>
    </modified>
    <modified>
      <diff>@@ -7,7 +7,7 @@ ul :id =&gt; 'asset_list' do
   render :partial =&gt; 'asset', :collection =&gt; @project.assets
 end
 
-if @session[:user]
+if session[:user]
   render(:partial =&gt; 'asset_form')
   sortable_element  'asset_list', 
                     :url =&gt; capture { assets_url(:action =&gt; 'sort', :project_id =&gt; @project) },</diff>
      <filename>app/views/projects/show.mab</filename>
    </modified>
    <modified>
      <diff>@@ -1,19 +1,109 @@
-# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
+# Don't change this file!
+# Configure your app in config/environment.rb and config/environments/*.rb
 
-unless defined?(RAILS_ROOT)
-  root_path = File.join(File.dirname(__FILE__), '..')
-  unless RUBY_PLATFORM =~ /mswin32/
-    require 'pathname'
-    root_path = Pathname.new(root_path).cleanpath(true).to_s
+RAILS_ROOT = &quot;#{File.dirname(__FILE__)}/..&quot; unless defined?(RAILS_ROOT)
+
+module Rails
+  class &lt;&lt; self
+    def boot!
+      unless booted?
+        preinitialize
+        pick_boot.run
+      end
+    end
+
+    def booted?
+      defined? Rails::Initializer
+    end
+
+    def pick_boot
+      (vendor_rails? ? VendorBoot : GemBoot).new
+    end
+
+    def vendor_rails?
+      File.exist?(&quot;#{RAILS_ROOT}/vendor/rails&quot;)
+    end
+
+    # FIXME : Ruby 1.9
+    def preinitialize
+      load(preinitializer_path) if File.exists?(preinitializer_path)
+    end
+
+    def preinitializer_path
+      &quot;#{RAILS_ROOT}/config/preinitializer.rb&quot;
+    end
   end
-  RAILS_ROOT = root_path
-end
 
-if File.directory?(&quot;#{RAILS_ROOT}/vendor/rails&quot;)
-  require &quot;#{RAILS_ROOT}/vendor/rails/railties/lib/initializer&quot;
-else
-  require 'rubygems'
-  require 'initializer'
+  class Boot
+    def run
+      load_initializer
+      Rails::Initializer.run(:set_load_path)
+    end
+  end
+
+  class VendorBoot &lt; Boot
+    def load_initializer
+      require &quot;#{RAILS_ROOT}/vendor/rails/railties/lib/initializer&quot;
+    end
+  end
+
+  class GemBoot &lt; Boot
+    def load_initializer
+      self.class.load_rubygems
+      load_rails_gem
+      require 'initializer'
+    end
+
+    def load_rails_gem
+      if version = self.class.gem_version
+        gem 'rails', version
+      else
+        gem 'rails'
+      end
+    rescue Gem::LoadError =&gt; load_error
+      $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
+      exit 1
+    end
+
+    class &lt;&lt; self
+      def rubygems_version
+        Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
+      end
+
+      def gem_version
+        if defined? RAILS_GEM_VERSION
+          RAILS_GEM_VERSION
+        elsif ENV.include?('RAILS_GEM_VERSION')
+          ENV['RAILS_GEM_VERSION']
+        else
+          parse_gem_version(read_environment_rb)
+        end
+      end
+
+      def load_rubygems
+        require 'rubygems'
+
+        unless rubygems_version &gt;= '0.9.4'
+          $stderr.puts %(Rails requires RubyGems &gt;= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
+          exit 1
+        end
+
+      rescue LoadError
+        $stderr.puts %(Rails requires RubyGems &gt;= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+        exit 1
+      end
+
+      def parse_gem_version(text)
+        $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*[&quot;']([!~&lt;&gt;=]*\s*[\d.]+)[&quot;']/
+      end
+
+      private
+        def read_environment_rb
+          File.read(&quot;#{RAILS_ROOT}/config/environment.rb&quot;)
+        end
+    end
+  end
 end
 
-Rails::Initializer.run(:set_load_path)
+# All that for this:
+Rails.boot!</diff>
      <filename>config/boot.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,55 +1,62 @@
-# Be sure to restart your web server when you modify this file.
+# Be sure to restart your server when you modify this file
 
-# Uncomment below to force Rails into production mode when 
+# Uncomment below to force Rails into production mode when
 # you don't control web/app server and can't set it the proper way
-#ENV['RAILS_ENV'] ||= 'production'
+# ENV['RAILS_ENV'] ||= 'production'
+
+# Specifies gem version of Rails to use when vendor/rails is not present
+RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION
 
 # Bootstrap the Rails environment, frameworks, and default configuration
 require File.join(File.dirname(__FILE__), 'boot')
 
 Rails::Initializer.run do |config|
-  # Settings in config/environments/* take precedence those specified here
-  
-  # Skip frameworks you're not going to use
-  # config.frameworks -= [ :action_web_service, :action_mailer ]
+  # Settings in config/environments/* take precedence over those specified here.
+  # Application configuration should go into files in config/initializers
+  # -- all .rb files in that directory are automatically loaded.
+  # See Rails::Configuration for more options.
+
+  # Skip frameworks you're not going to use (only works if using vendor/rails).
+  # To use Rails without a database, you must remove the Active Record framework
+  # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
+
+  # Only load the plugins named here, in the order given. By default, all plugins 
+  # in vendor/plugins are loaded in alphabetical order.
+  # :all can be used as a placeholder for all plugins not explicitly named
+  # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
 
   # Add additional load paths for your own custom dirs
   # config.load_paths += %W( #{RAILS_ROOT}/extras )
 
-  # Force all environments to use the same logger level 
+  # Force all environments to use the same logger level
   # (by default production uses :info, the others :debug)
   # config.log_level = :debug
 
-  # Use the database for sessions instead of the file system
-  # (create the session table with 'rake create_sessions_table')
-  config.action_controller.session_store = :active_record_store
-
-  # Enable page/fragment caching by setting a file-based store
-  # (remember to create the caching directory and make it readable to the application)
-  # config.action_controller.fragment_cache_store = :file_store, &quot;#{RAILS_ROOT}/cache&quot;
+  # Your secret key for verifying cookie session data integrity.
+  # If you change this key, all old sessions will become invalid!
+  # Make sure the secret is at least 30 characters and all random, 
+  # no regular words or you'll be exposed to dictionary attacks.
+  config.action_controller.session = {
+    :session_key =&gt; '_gullery_session',
+    :secret      =&gt; '4dbd2d578b2c724977e107ebfdec261266f350bbe854d605a057b823ec378fabcdc1db9f92914d75a52ceabf93537bf5d26153d828cfa8dbf27c8a97051894c2'
+  }
+
+  # Use the database for sessions instead of the cookie-based default,
+  # which shouldn't be used to store highly confidential information
+  # (create the session table with 'rake db:sessions:create')
+  # config.action_controller.session_store = :active_record_store
+
+  # Use SQL instead of Active Record's schema dumper when creating the test database.
+  # This is necessary if your schema can't be completely dumped by the schema dumper,
+  # like if you have constraints or database-specific column types
+  # config.active_record.schema_format = :sql
 
   # Activate observers that should always be running
   # config.active_record.observers = :cacher, :garbage_collector
 
   # Make Active Record use UTC-base instead of local time
-  config.active_record.default_timezone = :utc
-  
-  # Use Active Record's schema dumper instead of SQL when creating the test database
-  # (enables use of different database adapters for development and test environments)
-  config.active_record.schema_format = :ruby
-
-  # See Rails::Configuration for more options
+  # config.active_record.default_timezone = :utc
 end
 
-# Add new inflection rules using the following format 
-# (all these examples are active by default):
-# Inflector.inflections do |inflect|
-#   inflect.plural /^(ox)$/i, '\1en'
-#   inflect.singular /^(ox)en/i, '\1'
-#   inflect.irregular 'person', 'people'
-#   inflect.uncountable %w( fish sheep )
-# end
-
-# Include your application configuration below
 
-require 'taggable'
+require 'taggable'
\ No newline at end of file</diff>
      <filename>config/environment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,17 +3,16 @@
 # In the development environment your application's code is reloaded on
 # every request.  This slows down response time but is perfect for development
 # since you don't have to restart the webserver when you make code changes.
-config.cache_classes     = false
+config.cache_classes = false
 
 # Log error messages when you accidentally call methods on nil.
-config.whiny_nils        = true
-
-# Enable the breakpoint server that script/breakpointer connects to
-config.breakpoint_server = true
+config.whiny_nils = true
 
 # Show full error reports and disable caching
 config.action_controller.consider_all_requests_local = true
+config.action_view.debug_rjs                         = true
 config.action_controller.perform_caching             = false
+config.action_view.cache_template_extensions         = false
 
 # Don't care if the mailer can't send
-config.action_mailer.raise_delivery_errors = false
+config.action_mailer.raise_delivery_errors = false
\ No newline at end of file</diff>
      <filename>config/environments/development.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,15 +5,15 @@
 config.cache_classes = true
 
 # Use a different logger for distributed setups
-# config.logger        = SyslogLogger.new
-
+# config.logger = SyslogLogger.new
 
 # Full error reports are disabled and caching is turned on
 config.action_controller.consider_all_requests_local = false
 config.action_controller.perform_caching             = true
+config.action_view.cache_template_loading            = true
 
 # Enable serving of images, stylesheets, and javascripts from an asset server
 # config.action_controller.asset_host                  = &quot;http://assets.example.com&quot;
 
-# Disable delivery errors if you bad email addresses should just be ignored
+# Disable delivery errors, bad email addresses will be ignored
 # config.action_mailer.raise_delivery_errors = false</diff>
      <filename>config/environments/production.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,13 +7,16 @@
 config.cache_classes = true
 
 # Log error messages when you accidentally call methods on nil.
-config.whiny_nils    = true
+config.whiny_nils = true
 
 # Show full error reports and disable caching
 config.action_controller.consider_all_requests_local = true
 config.action_controller.perform_caching             = false
 
+# Disable request forgery protection in test environment
+config.action_controller.allow_forgery_protection    = false
+
 # Tell ActionMailer not to deliver emails to the real world.
 # The :test delivery method accumulates sent emails in the
 # ActionMailer::Base.deliveries array.
-config.action_mailer.delivery_method = :test
\ No newline at end of file
+config.action_mailer.delivery_method = :test</diff>
      <filename>config/environments/test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,5 @@
 ActionController::Routing::Routes.draw do |map|
 
-  map.connect '', :controller =&gt; &quot;projects&quot;
-
   map.signup 'signup', :controller =&gt; &quot;account&quot;, :action =&gt; 'signup'
   map.login 'login', :controller =&gt; &quot;account&quot;, :action =&gt; 'login'
   map.logout 'logout', :controller =&gt; &quot;account&quot;, :action =&gt; 'logout'
@@ -10,10 +8,16 @@ ActionController::Routing::Routes.draw do |map|
   map.assets 'assets/:action/:id', :controller =&gt; 'assets', :action =&gt; 'show'
   map.account 'account/:action', :controller =&gt; 'account'
 
+  # You can have the root of your site routed with map.root -- just remember to delete public/index.html.
+  map.root :controller =&gt; &quot;projects&quot;
+
   # Allow downloading Web Service WSDL as a file with an extension
   # instead of a file named 'wsdl'
   map.connect ':controller/service.wsdl', :action =&gt; 'wsdl'
 
   # Install the default route as the lowest priority.
   map.connect ':controller/:action/:id'
+  
+  # Install the default routes as the lowest priority.
+  map.connect ':controller/:action/:id.:format'
 end</diff>
      <filename>config/routes.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,2 +1,2 @@
 Use this README file to introduce your application and point to useful places in the API for learning more.
-Run &quot;rake appdoc&quot; to generate API documentation for your models and controllers.
\ No newline at end of file
+Run &quot;rake doc:app&quot; to generate API documentation for your models, controllers, helpers, and libraries.</diff>
      <filename>doc/README_FOR_APP</filename>
    </modified>
    <modified>
      <diff>@@ -29,7 +29,7 @@ RewriteEngine On
 RewriteRule ^$ index.html [QSA]
 RewriteRule ^([^.]+)$ $1.html [QSA]
 RewriteCond %{REQUEST_FILENAME} !-f
-RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
+RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
 
 # In case Rails experiences terminal errors
 # Instead of displaying this message you can supply a file here which will be rendered instead
@@ -37,5 +37,4 @@ RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
 # Example:
 #   ErrorDocument 500 /500.html
 
-ErrorDocument 404 /404.html
-ErrorDocument 500 /500.html
+ErrorDocument 500 &quot;&lt;h2&gt;Application error&lt;/h2&gt;Rails application failed to start properly&quot;</diff>
      <filename>public/.htaccess</filename>
    </modified>
    <modified>
      <diff>@@ -1,12 +1,30 @@
-&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;
-   &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;
-&lt;html&gt;
+&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;
+       &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
+
+&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; lang=&quot;en&quot;&gt;
+
 &lt;head&gt;
-  &lt;title&gt;404 Error&lt;/title&gt;
-  &lt;link href=&quot;/stylesheets/site.css&quot; media=&quot;screen&quot; rel=&quot;Stylesheet&quot; type=&quot;text/css&quot; /&gt;
+  &lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
+  &lt;title&gt;The page you were looking for doesn't exist (404)&lt;/title&gt;
+	&lt;style type=&quot;text/css&quot;&gt;
+		body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+		div.dialog {
+			width: 25em;
+			padding: 0 4em;
+			margin: 4em auto 0 auto;
+			border: 1px solid #ccc;
+			border-right-color: #999;
+			border-bottom-color: #999;
+		}
+		h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+	&lt;/style&gt;
 &lt;/head&gt;
+
 &lt;body&gt;
-  &lt;h1&gt;Sorry! That file was not found.&lt;/h1&gt;
-  &lt;p&gt;&lt;/p&gt;
+  &lt;!-- This file lives in public/404.html --&gt;
+  &lt;div class=&quot;dialog&quot;&gt;
+    &lt;h1&gt;The page you were looking for doesn't exist.&lt;/h1&gt;
+    &lt;p&gt;You may have mistyped the address or the page may have moved.&lt;/p&gt;
+  &lt;/div&gt;
 &lt;/body&gt;
 &lt;/html&gt;
\ No newline at end of file</diff>
      <filename>public/404.html</filename>
    </modified>
    <modified>
      <diff>@@ -1,12 +1,30 @@
-&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;
-   &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;
-&lt;html&gt;
+&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;
+       &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
+
+&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; lang=&quot;en&quot;&gt;
+
 &lt;head&gt;
-  &lt;title&gt;500 Error&lt;/title&gt;
-  &lt;link href=&quot;/stylesheets/site.css&quot; media=&quot;screen&quot; rel=&quot;Stylesheet&quot; type=&quot;text/css&quot; /&gt;
+  &lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
+  &lt;title&gt;We're sorry, but something went wrong (500)&lt;/title&gt;
+	&lt;style type=&quot;text/css&quot;&gt;
+		body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+		div.dialog {
+			width: 25em;
+			padding: 0 4em;
+			margin: 4em auto 0 auto;
+			border: 1px solid #ccc;
+			border-right-color: #999;
+			border-bottom-color: #999;
+		}
+		h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+	&lt;/style&gt;
 &lt;/head&gt;
+
 &lt;body&gt;
-  &lt;h1&gt;Sorry! There was an error.&lt;/h1&gt;
-  &lt;p&gt;&lt;/p&gt;
+  &lt;!-- This file lives in public/500.html --&gt;
+  &lt;div class=&quot;dialog&quot;&gt;
+    &lt;h1&gt;We're sorry, but something went wrong.&lt;/h1&gt;
+    &lt;p&gt;We've been notified about this issue and we'll take a look at it shortly.&lt;/p&gt;
+  &lt;/div&gt;
 &lt;/body&gt;
 &lt;/html&gt;
\ No newline at end of file</diff>
      <filename>public/500.html</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
 
 require File.dirname(__FILE__) + &quot;/../config/environment&quot; unless defined?(RAILS_ROOT)
 </diff>
      <filename>public/dispatch.cgi</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
 #
 # You may specify the path to the FastCGI crash log (a log of unhandled
 # exceptions which forced the FastCGI instance to exit, great for debugging)</diff>
      <filename>public/dispatch.fcgi</filename>
    </modified>
    <modified>
      <diff>@@ -1,12 +1,13 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
-//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
 // Contributors:
 //  Richard Livsey
 //  Rahul Bhargava
 //  Rob Wills
 // 
-// See scriptaculous.js for full license.
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
 // Autocompleter.Base handles all the autocompletion functionality 
 // that's independent of the data source for autocompletion. This
@@ -33,40 +34,50 @@
 // useful when one of the tokens is \n (a newline), as it 
 // allows smart autocompletion after linebreaks.
 
-var Autocompleter = {}
-Autocompleter.Base = function() {};
-Autocompleter.Base.prototype = {
+if(typeof Effect == 'undefined')
+  throw(&quot;controls.js requires including script.aculo.us' effects.js library&quot;);
+
+var Autocompleter = { }
+Autocompleter.Base = Class.create({
   baseInitialize: function(element, update, options) {
-    this.element     = $(element); 
+    element          = $(element)
+    this.element     = element; 
     this.update      = $(update);  
     this.hasFocus    = false; 
     this.changed     = false; 
     this.active      = false; 
     this.index       = 0;     
     this.entryCount  = 0;
+    this.oldElementValue = this.element.value;
 
-    if (this.setOptions)
+    if(this.setOptions)
       this.setOptions(options);
     else
-      this.options = options || {};
+      this.options = options || { };
 
     this.options.paramName    = this.options.paramName || this.element.name;
     this.options.tokens       = this.options.tokens || [];
     this.options.frequency    = this.options.frequency || 0.4;
     this.options.minChars     = this.options.minChars || 1;
     this.options.onShow       = this.options.onShow || 
-    function(element, update){ 
-      if(!update.style.position || update.style.position=='absolute') {
-        update.style.position = 'absolute';
-        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
-      }
-      Effect.Appear(update,{duration:0.15});
-    };
+      function(element, update){ 
+        if(!update.style.position || update.style.position=='absolute') {
+          update.style.position = 'absolute';
+          Position.clone(element, update, {
+            setHeight: false, 
+            offsetTop: element.offsetHeight
+          });
+        }
+        Effect.Appear(update,{duration:0.15});
+      };
     this.options.onHide = this.options.onHide || 
-    function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
 
-    if (typeof(this.options.tokens) == 'string') 
+    if(typeof(this.options.tokens) == 'string') 
       this.options.tokens = new Array(this.options.tokens);
+    // Force carriage returns as token delimiters anyway
+    if (!this.options.tokens.include('\n'))
+      this.options.tokens.push('\n');
 
     this.observer = null;
     
@@ -74,15 +85,14 @@ Autocompleter.Base.prototype = {
 
     Element.hide(this.update);
 
-    Event.observe(this.element, &quot;blur&quot;, this.onBlur.bindAsEventListener(this));
-    Event.observe(this.element, &quot;keypress&quot;, this.onKeyPress.bindAsEventListener(this));
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
   },
 
   show: function() {
     if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
     if(!this.iefix &amp;&amp; 
-      (navigator.appVersion.indexOf('MSIE')&gt;0) &amp;&amp;
-      (navigator.userAgent.indexOf('Opera')&lt;0) &amp;&amp;
+      (Prototype.Browser.IE) &amp;&amp;
       (Element.getStyle(this.update, 'position')=='absolute')) {
       new Insertion.After(this.update, 
        '&lt;iframe id=&quot;' + this.update.id + '_iefix&quot; '+
@@ -94,7 +104,7 @@ Autocompleter.Base.prototype = {
   },
   
   fixIEOverlapping: function() {
-    Position.clone(this.update, this.iefix);
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
     this.iefix.style.zIndex = 1;
     this.update.style.zIndex = 2;
     Element.show(this.iefix);
@@ -132,17 +142,17 @@ Autocompleter.Base.prototype = {
        case Event.KEY_UP:
          this.markPrevious();
          this.render();
-         if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) Event.stop(event);
+         Event.stop(event);
          return;
        case Event.KEY_DOWN:
          this.markNext();
          this.render();
-         if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) Event.stop(event);
+         Event.stop(event);
          return;
       }
      else 
-      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) 
-        return;
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
+         (Prototype.Browser.WebKit &gt; 0 &amp;&amp; event.keyCode == 0)) return;
 
     this.changed = true;
     this.hasFocus = true;
@@ -152,6 +162,12 @@ Autocompleter.Base.prototype = {
         setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
   },
 
+  activate: function() {
+    this.changed = false;
+    this.hasFocus = true;
+    this.getUpdatedChoices();
+  },
+
   onHover: function(event) {
     var element = Event.findElement(event, 'LI');
     if(this.index != element.autocompleteIndex) 
@@ -182,7 +198,6 @@ Autocompleter.Base.prototype = {
         this.index==i ? 
           Element.addClassName(this.getEntry(i),&quot;selected&quot;) : 
           Element.removeClassName(this.getEntry(i),&quot;selected&quot;);
-        
       if(this.hasFocus) { 
         this.show();
         this.active = true;
@@ -196,11 +211,13 @@ Autocompleter.Base.prototype = {
   markPrevious: function() {
     if(this.index &gt; 0) this.index--
       else this.index = this.entryCount-1;
+    this.getEntry(this.index).scrollIntoView(true);
   },
   
   markNext: function() {
     if(this.index &lt; this.entryCount-1) this.index++
       else this.index = 0;
+    this.getEntry(this.index).scrollIntoView(false);
   },
   
   getEntry: function(index) {
@@ -221,18 +238,24 @@ Autocompleter.Base.prototype = {
       this.options.updateElement(selectedElement);
       return;
     }
-
-    var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
-    var lastTokenPos = this.findLastToken();
-    if (lastTokenPos != -1) {
-      var newValue = this.element.value.substr(0, lastTokenPos + 1);
-      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
+    var value = '';
+    if (this.options.select) {
+      var nodes = $(selectedElement).select('.' + this.options.select) || [];
+      if(nodes.length&gt;0) value = Element.collectTextNodes(nodes[0], this.options.select);
+    } else
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+    
+    var bounds = this.getTokenBounds();
+    if (bounds[0] != -1) {
+      var newValue = this.element.value.substr(0, bounds[0]);
+      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
       if (whitespace)
         newValue += whitespace[0];
-      this.element.value = newValue + value;
+      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
     } else {
       this.element.value = value;
     }
+    this.oldElementValue = this.element.value;
     this.element.focus();
     
     if (this.options.afterUpdateElement)
@@ -243,11 +266,11 @@ Autocompleter.Base.prototype = {
     if(!this.changed &amp;&amp; this.hasFocus) {
       this.update.innerHTML = choices;
       Element.cleanWhitespace(this.update);
-      Element.cleanWhitespace(this.update.firstChild);
+      Element.cleanWhitespace(this.update.down());
 
-      if(this.update.firstChild &amp;&amp; this.update.firstChild.childNodes) {
+      if(this.update.firstChild &amp;&amp; this.update.down().childNodes) {
         this.entryCount = 
-          this.update.firstChild.childNodes.length;
+          this.update.down().childNodes.length;
         for (var i = 0; i &lt; this.entryCount; i++) {
           var entry = this.getEntry(i);
           entry.autocompleteIndex = i;
@@ -258,9 +281,14 @@ Autocompleter.Base.prototype = {
       }
 
       this.stopIndicator();
-
       this.index = 0;
-      this.render();
+      
+      if(this.entryCount==1 &amp;&amp; this.options.autoSelect) {
+        this.selectEntry();
+        this.hide();
+      } else {
+        this.render();
+      }
     }
   },
 
@@ -271,41 +299,50 @@ Autocompleter.Base.prototype = {
 
   onObserverEvent: function() {
     this.changed = false;   
+    this.tokenBounds = null;
     if(this.getToken().length&gt;=this.options.minChars) {
-      this.startIndicator();
       this.getUpdatedChoices();
     } else {
       this.active = false;
       this.hide();
     }
+    this.oldElementValue = this.element.value;
   },
 
   getToken: function() {
-    var tokenPos = this.findLastToken();
-    if (tokenPos != -1)
-      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
-    else
-      var ret = this.element.value;
-
-    return /\n/.test(ret) ? '' : ret;
-  },
-
-  findLastToken: function() {
-    var lastTokenPos = -1;
-
-    for (var i=0; i&lt;this.options.tokens.length; i++) {
-      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
-      if (thisTokenPos &gt; lastTokenPos)
-        lastTokenPos = thisTokenPos;
+    var bounds = this.getTokenBounds();
+    return this.element.value.substring(bounds[0], bounds[1]).strip();
+  },
+
+  getTokenBounds: function() {
+    if (null != this.tokenBounds) return this.tokenBounds;
+    var value = this.element.value;
+    if (value.strip().empty()) return [-1, 0];
+    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+    var offset = (diff == this.oldElementValue.length ? 1 : 0);
+    var prevTokenPos = -1, nextTokenPos = value.length;
+    var tp;
+    for (var index = 0, l = this.options.tokens.length; index &lt; l; ++index) {
+      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+      if (tp &gt; prevTokenPos) prevTokenPos = tp;
+      tp = value.indexOf(this.options.tokens[index], diff + offset);
+      if (-1 != tp &amp;&amp; tp &lt; nextTokenPos) nextTokenPos = tp;
     }
-    return lastTokenPos;
+    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
   }
-}
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+  var boundary = Math.min(newS.length, oldS.length);
+  for (var index = 0; index &lt; boundary; ++index)
+    if (newS[index] != oldS[index])
+      return index;
+  return boundary;
+};
 
-Ajax.Autocompleter = Class.create();
-Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
   initialize: function(element, update, url, options) {
-	  this.baseInitialize(element, update, options);
+    this.baseInitialize(element, update, options);
     this.options.asynchronous  = true;
     this.options.onComplete    = this.onComplete.bind(this);
     this.options.defaultParams = this.options.parameters || null;
@@ -313,7 +350,9 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
   },
 
   getUpdatedChoices: function() {
-    entry = encodeURIComponent(this.options.paramName) + '=' + 
+    this.startIndicator();
+    
+    var entry = encodeURIComponent(this.options.paramName) + '=' + 
       encodeURIComponent(this.getToken());
 
     this.options.parameters = this.options.callback ?
@@ -321,14 +360,13 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
 
     if(this.options.defaultParams) 
       this.options.parameters += '&amp;' + this.options.defaultParams;
-
+    
     new Ajax.Request(this.url, this.options);
   },
 
   onComplete: function(request) {
     this.updateChoices(request.responseText);
   }
-
 });
 
 // The local array autocompleter. Used when you'd prefer to
@@ -366,8 +404,7 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
 // In that case, the other options above will not apply unless
 // you support them.
 
-Autocompleter.Local = Class.create();
-Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+Autocompleter.Local = Class.create(Autocompleter.Base, {
   initialize: function(element, update, array, options) {
     this.baseInitialize(element, update, options);
     this.options.array = array;
@@ -423,13 +460,12 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
           ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
         return &quot;&lt;ul&gt;&quot; + ret.join('') + &quot;&lt;/ul&gt;&quot;;
       }
-    }, options || {});
+    }, options || { });
   }
 });
 
-// AJAX in-place editor
-//
-// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve &lt;tdd@tddsworld.com&gt; (April 2007).
 
 // Use this if you notice weird scrolling problems on some browsers,
 // the DOM might be a bit confused when this gets called so do this
@@ -440,295 +476,472 @@ Field.scrollFreeActivate = function(field) {
   }, 1);
 }
 
-Ajax.InPlaceEditor = Class.create();
-Ajax.InPlaceEditor.defaultHighlightColor = &quot;#FFFF99&quot;;
-Ajax.InPlaceEditor.prototype = {
+Ajax.InPlaceEditor = Class.create({
   initialize: function(element, url, options) {
     this.url = url;
-    this.element = $(element);
-
-    this.options = Object.extend({
-      okText: &quot;ok&quot;,
-      cancelText: &quot;cancel&quot;,
-      savingText: &quot;Saving...&quot;,
-      clickToEditText: &quot;Click to edit&quot;,
-      okText: &quot;ok&quot;,
-      rows: 1,
-      onComplete: function(transport, element) {
-        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
-      },
-      onFailure: function(transport) {
-        alert(&quot;Error communicating with the server: &quot; + transport.responseText.stripTags());
-      },
-      callback: function(form) {
-        return Form.serialize(form);
-      },
-      handleLineBreaks: true,
-      loadingText: 'Loading...',
-      savingClassName: 'inplaceeditor-saving',
-      loadingClassName: 'inplaceeditor-loading',
-      formClassName: 'inplaceeditor-form',
-      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
-      highlightendcolor: &quot;#FFFFFF&quot;,
-      externalControl:	null,
-      ajaxOptions: {}
-    }, options || {});
-
-    if(!this.options.formId &amp;&amp; this.element.id) {
-      this.options.formId = this.element.id + &quot;-inplaceeditor&quot;;
-      if ($(this.options.formId)) {
-        // there's already a form with that name, don't specify an id
-        this.options.formId = null;
-      }
+    this.element = element = $(element);
+    this.prepareOptions();
+    this._controls = { };
+    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+    Object.extend(this.options, options || { });
+    if (!this.options.formId &amp;&amp; this.element.id) {
+      this.options.formId = this.element.id + '-inplaceeditor';
+      if ($(this.options.formId))
+        this.options.formId = '';
     }
-    
-    if (this.options.externalControl) {
+    if (this.options.externalControl)
       this.options.externalControl = $(this.options.externalControl);
-    }
-    
-    this.originalBackground = Element.getStyle(this.element, 'background-color');
-    if (!this.originalBackground) {
-      this.originalBackground = &quot;transparent&quot;;
-    }
-    
+    if (!this.options.externalControl)
+      this.options.externalControlOnly = false;
+    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
     this.element.title = this.options.clickToEditText;
-    
-    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
-    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
-    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
-    Event.observe(this.element, 'click', this.onclickListener);
-    Event.observe(this.element, 'mouseover', this.mouseoverListener);
-    Event.observe(this.element, 'mouseout', this.mouseoutListener);
-    if (this.options.externalControl) {
-      Event.observe(this.options.externalControl, 'click', this.onclickListener);
-      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
-      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
+    this._boundCancelHandler = this.handleFormCancellation.bind(this);
+    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+    this._boundWrapperHandler = this.wrapUp.bind(this);
+    this.registerListeners();
+  },
+  checkForEscapeOrReturn: function(e) {
+    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+    if (Event.KEY_ESC == e.keyCode)
+      this.handleFormCancellation(e);
+    else if (Event.KEY_RETURN == e.keyCode)
+      this.handleFormSubmission(e);
+  },
+  createControl: function(mode, handler, extraClasses) {
+    var control = this.options[mode + 'Control'];
+    var text = this.options[mode + 'Text'];
+    if ('button' == control) {
+      var btn = document.createElement('input');
+      btn.type = 'submit';
+      btn.value = text;
+      btn.className = 'editor_' + mode + '_button';
+      if ('cancel' == mode)
+        btn.onclick = this._boundCancelHandler;
+      this._form.appendChild(btn);
+      this._controls[mode] = btn;
+    } else if ('link' == control) {
+      var link = document.createElement('a');
+      link.href = '#';
+      link.appendChild(document.createTextNode(text));
+      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+      link.className = 'editor_' + mode + '_link';
+      if (extraClasses)
+        link.className += ' ' + extraClasses;
+      this._form.appendChild(link);
+      this._controls[mode] = link;
     }
   },
-  enterEditMode: function(evt) {
-    if (this.saving) return;
-    if (this.editing) return;
-    this.editing = true;
-    this.onEnterEditMode();
-    if (this.options.externalControl) {
-      Element.hide(this.options.externalControl);
-    }
-    Element.hide(this.element);
-    this.createForm();
-    this.element.parentNode.insertBefore(this.form, this.element);
-    Field.scrollFreeActivate(this.editField);
-    // stop the event to avoid a page refresh in Safari
-    if (evt) {
-      Event.stop(evt);
-    }
-    return false;
-  },
-  createForm: function() {
-    this.form = document.createElement(&quot;form&quot;);
-    this.form.id = this.options.formId;
-    Element.addClassName(this.form, this.options.formClassName)
-    this.form.onsubmit = this.onSubmit.bind(this);
-
-    this.createEditField();
-
-    if (this.options.textarea) {
-      var br = document.createElement(&quot;br&quot;);
-      this.form.appendChild(br);
-    }
-
-    okButton = document.createElement(&quot;input&quot;);
-    okButton.type = &quot;submit&quot;;
-    okButton.value = this.options.okText;
-    this.form.appendChild(okButton);
-
-    cancelLink = document.createElement(&quot;a&quot;);
-    cancelLink.href = &quot;#&quot;;
-    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
-    cancelLink.onclick = this.onclickCancel.bind(this);
-    this.form.appendChild(cancelLink);
-  },
-  hasHTMLLineBreaks: function(string) {
-    if (!this.options.handleLineBreaks) return false;
-    return string.match(/&lt;br/i) || string.match(/&lt;p&gt;/i);
-  },
-  convertHTMLLineBreaks: function(string) {
-    return string.replace(/&lt;br&gt;/gi, &quot;\n&quot;).replace(/&lt;br\/&gt;/gi, &quot;\n&quot;).replace(/&lt;\/p&gt;/gi, &quot;\n&quot;).replace(/&lt;p&gt;/gi, &quot;&quot;);
-  },
   createEditField: function() {
-    var text;
-    if(this.options.loadTextURL) {
-      text = this.options.loadingText;
-    } else {
-      text = this.getText();
-    }
-    
-    if (this.options.rows == 1 &amp;&amp; !this.hasHTMLLineBreaks(text)) {
-      this.options.textarea = false;
-      var textField = document.createElement(&quot;input&quot;);
-      textField.type = &quot;text&quot;;
-      textField.name = &quot;value&quot;;
-      textField.value = text;
-      textField.style.backgroundColor = this.options.highlightcolor;
+    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+    var fld;
+    if (1 &gt;= this.options.rows &amp;&amp; !/\r|\n/.test(this.getText())) {
+      fld = document.createElement('input');
+      fld.type = 'text';
       var size = this.options.size || this.options.cols || 0;
-      if (size != 0) textField.size = size;
-      this.editField = textField;
+      if (0 &lt; size) fld.size = size;
     } else {
-      this.options.textarea = true;
-      var textArea = document.createElement(&quot;textarea&quot;);
-      textArea.name = &quot;value&quot;;
-      textArea.value = this.convertHTMLLineBreaks(text);
-      textArea.rows = this.options.rows;
-      textArea.cols = this.options.cols || 40;
-      this.editField = textArea;
+      fld = document.createElement('textarea');
+      fld.rows = (1 &gt;= this.options.rows ? this.options.autoRows : this.options.rows);
+      fld.cols = this.options.cols || 40;
     }
-    
-    if(this.options.loadTextURL) {
+    fld.name = this.options.paramName;
+    fld.value = text; // No HTML breaks conversion anymore
+    fld.className = 'editor_field';
+    if (this.options.submitOnBlur)
+      fld.onblur = this._boundSubmitHandler;
+    this._controls.editor = fld;
+    if (this.options.loadTextURL)
       this.loadExternalText();
-    }
-    this.form.appendChild(this.editField);
+    this._form.appendChild(this._controls.editor);
+  },
+  createForm: function() {
+    var ipe = this;
+    function addText(mode, condition) {
+      var text = ipe.options['text' + mode + 'Controls'];
+      if (!text || condition === false) return;
+      ipe._form.appendChild(document.createTextNode(text));
+    };
+    this._form = $(document.createElement('form'));
+    this._form.id = this.options.formId;
+    this._form.addClassName(this.options.formClassName);
+    this._form.onsubmit = this._boundSubmitHandler;
+    this.createEditField();
+    if ('textarea' == this._controls.editor.tagName.toLowerCase())
+      this._form.appendChild(document.createElement('br'));
+    if (this.options.onFormCustomization)
+      this.options.onFormCustomization(this, this._form);
+    addText('Before', this.options.okControl || this.options.cancelControl);
+    this.createControl('ok', this._boundSubmitHandler);
+    addText('Between', this.options.okControl &amp;&amp; this.options.cancelControl);
+    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+    addText('After', this.options.okControl || this.options.cancelControl);
+  },
+  destroy: function() {
+    if (this._oldInnerHTML)
+      this.element.innerHTML = this._oldInnerHTML;
+    this.leaveEditMode();
+    this.unregisterListeners();
+  },
+  enterEditMode: function(e) {
+    if (this._saving || this._editing) return;
+    this._editing = true;
+    this.triggerCallback('onEnterEditMode');
+    if (this.options.externalControl)
+      this.options.externalControl.hide();
+    this.element.hide();
+    this.createForm();
+    this.element.parentNode.insertBefore(this._form, this.element);
+    if (!this.options.loadTextURL)
+      this.postProcessEditField();
+    if (e) Event.stop(e);
+  },
+  enterHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.addClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onEnterHover');
   },
   getText: function() {
     return this.element.innerHTML;
   },
-  loadExternalText: function() {
-    Element.addClassName(this.form, this.options.loadingClassName);
-    this.editField.disabled = true;
-    new Ajax.Request(
-      this.options.loadTextURL,
-      Object.extend({
-        asynchronous: true,
-        onComplete: this.onLoadedExternalText.bind(this)
-      }, this.options.ajaxOptions)
-    );
-  },
-  onLoadedExternalText: function(transport) {
-    Element.removeClassName(this.form, this.options.loadingClassName);
-    this.editField.disabled = false;
-    this.editField.value = transport.responseText.stripTags();
-  },
-  onclickCancel: function() {
-    this.onComplete();
-    this.leaveEditMode();
-    return false;
-  },
-  onFailure: function(transport) {
-    this.options.onFailure(transport);
-    if (this.oldInnerHTML) {
-      this.element.innerHTML = this.oldInnerHTML;
-      this.oldInnerHTML = null;
+  handleAJAXFailure: function(transport) {
+    this.triggerCallback('onFailure', transport);
+    if (this._oldInnerHTML) {
+      this.element.innerHTML = this._oldInnerHTML;
+      this._oldInnerHTML = null;
     }
-    return false;
   },
-  onSubmit: function() {
-    // onLoading resets these so we need to save them away for the Ajax call
-    var form = this.form;
-    var value = this.editField.value;
-    
-    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
-    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
-    // to be displayed indefinitely
-    this.onLoading();
-    
-    new Ajax.Updater(
-      { 
-        success: this.element,
-         // don't update on failure (this could be an option)
-        failure: null
-      },
-      this.url,
-      Object.extend({
-        parameters: this.options.callback(form, value),
-        onComplete: this.onComplete.bind(this),
-        onFailure: this.onFailure.bind(this)
-      }, this.options.ajaxOptions)
-    );
-    // stop the event to avoid a page refresh in Safari
-    if (arguments.length &gt; 1) {
-      Event.stop(arguments[0]);
+  handleFormCancellation: function(e) {
+    this.wrapUp();
+    if (e) Event.stop(e);
+  },
+  handleFormSubmission: function(e) {
+    var form = this._form;
+    var value = $F(this._controls.editor);
+    this.prepareSubmission();
+    var params = this.options.callback(form, value) || '';
+    if (Object.isString(params))
+      params = params.toQueryParams();
+    params.editorId = this.element.id;
+    if (this.options.htmlResponse) {
+      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Updater({ success: this.element }, this.url, options);
+    } else {
+      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Request(this.url, options);
     }
-    return false;
+    if (e) Event.stop(e);
   },
-  onLoading: function() {
-    this.saving = true;
+  leaveEditMode: function() {
+    this.element.removeClassName(this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+    if (this.options.externalControl)
+      this.options.externalControl.show();
+    this._saving = false;
+    this._editing = false;
+    this._oldInnerHTML = null;
+    this.triggerCallback('onLeaveEditMode');
+  },
+  leaveHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.removeClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onLeaveHover');
+  },
+  loadExternalText: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this._controls.editor.disabled = true;
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._form.removeClassName(this.options.loadingClassName);
+        var text = transport.responseText;
+        if (this.options.stripLoadedTextTags)
+          text = text.stripTags();
+        this._controls.editor.value = text;
+        this._controls.editor.disabled = false;
+        this.postProcessEditField();
+      }.bind(this),
+      onFailure: this._boundFailureHandler
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+  postProcessEditField: function() {
+    var fpc = this.options.fieldPostCreation;
+    if (fpc)
+      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+  },
+  prepareOptions: function() {
+    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+      Object.extend(this.options, defs);
+    }.bind(this));
+  },
+  prepareSubmission: function() {
+    this._saving = true;
     this.removeForm();
     this.leaveHover();
     this.showSaving();
   },
+  registerListeners: function() {
+    this._listeners = { };
+    var listener;
+    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+      listener = this[pair.value].bind(this);
+      this._listeners[pair.key] = listener;
+      if (!this.options.externalControlOnly)
+        this.element.observe(pair.key, listener);
+      if (this.options.externalControl)
+        this.options.externalControl.observe(pair.key, listener);
+    }.bind(this));
+  },
+  removeForm: function() {
+    if (!this._form) return;
+    this._form.remove();
+    this._form = null;
+    this._controls = { };
+  },
   showSaving: function() {
-    this.oldInnerHTML = this.element.innerHTML;
+    this._oldInnerHTML = this.element.innerHTML;
     this.element.innerHTML = this.options.savingText;
-    Element.addClassName(this.element, this.options.savingClassName);
-    this.element.style.backgroundColor = this.originalBackground;
-    Element.show(this.element);
+    this.element.addClassName(this.options.savingClassName);
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
   },
-  removeForm: function() {
-    if(this.form) {
-      if (this.form.parentNode) Element.remove(this.form);
-      this.form = null;
+  triggerCallback: function(cbName, arg) {
+    if ('function' == typeof this.options[cbName]) {
+      this.options[cbName](this, arg);
     }
   },
-  enterHover: function() {
-    if (this.saving) return;
-    this.element.style.backgroundColor = this.options.highlightcolor;
-    if (this.effect) {
-      this.effect.cancel();
-    }
-    Element.addClassName(this.element, this.options.hoverClassName)
+  unregisterListeners: function() {
+    $H(this._listeners).each(function(pair) {
+      if (!this.options.externalControlOnly)
+        this.element.stopObserving(pair.key, pair.value);
+      if (this.options.externalControl)
+        this.options.externalControl.stopObserving(pair.key, pair.value);
+    }.bind(this));
   },
-  leaveHover: function() {
-    if (this.options.backgroundColor) {
-      this.element.style.backgroundColor = this.oldBackground;
-    }
-    Element.removeClassName(this.element, this.options.hoverClassName)
-    if (this.saving) return;
-    this.effect = new Effect.Highlight(this.element, {
-      startcolor: this.options.highlightcolor,
-      endcolor: this.options.highlightendcolor,
-      restorecolor: this.originalBackground
+  wrapUp: function(transport) {
+    this.leaveEditMode();
+    // Can't use triggerCallback due to backward compatibility: requires
+    // binding + direct element
+    this._boundComplete(transport, this.element);
+  }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+  dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+  initialize: function($super, element, url, options) {
+    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+    $super(element, url, options);
+  },
+
+  createEditField: function() {
+    var list = document.createElement('select');
+    list.name = this.options.paramName;
+    list.size = 1;
+    this._controls.editor = list;
+    this._collection = this.options.collection || [];
+    if (this.options.loadCollectionURL)
+      this.loadCollection();
+    else
+      this.checkForExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+
+  loadCollection: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this.showLoadingText(this.options.loadingCollectionText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        var js = transport.responseText.strip();
+        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+          throw 'Server returned an invalid collection representation.';
+        this._collection = eval(js);
+        this.checkForExternalText();
+      }.bind(this),
+      onFailure: this.onFailure
     });
+    new Ajax.Request(this.options.loadCollectionURL, options);
   },
-  leaveEditMode: function() {
-    Element.removeClassName(this.element, this.options.savingClassName);
-    this.removeForm();
-    this.leaveHover();
-    this.element.style.backgroundColor = this.originalBackground;
-    Element.show(this.element);
-    if (this.options.externalControl) {
-      Element.show(this.options.externalControl);
+
+  showLoadingText: function(text) {
+    this._controls.editor.disabled = true;
+    var tempOption = this._controls.editor.firstChild;
+    if (!tempOption) {
+      tempOption = document.createElement('option');
+      tempOption.value = '';
+      this._controls.editor.appendChild(tempOption);
+      tempOption.selected = true;
     }
-    this.editing = false;
-    this.saving = false;
-    this.oldInnerHTML = null;
-    this.onLeaveEditMode();
+    tempOption.update((text || '').stripScripts().stripTags());
   },
-  onComplete: function(transport) {
-    this.leaveEditMode();
-    this.options.onComplete.bind(this)(transport, this.element);
+
+  checkForExternalText: function() {
+    this._text = this.getText();
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    else
+      this.buildOptionList();
   },
-  onEnterEditMode: function() {},
-  onLeaveEditMode: function() {},
-  dispose: function() {
-    if (this.oldInnerHTML) {
-      this.element.innerHTML = this.oldInnerHTML;
-    }
-    this.leaveEditMode();
-    Event.stopObserving(this.element, 'click', this.onclickListener);
-    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
-    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
-    if (this.options.externalControl) {
-      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
-      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
-      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
+
+  loadExternalText: function() {
+    this.showLoadingText(this.options.loadingText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._text = transport.responseText.strip();
+        this.buildOptionList();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+
+  buildOptionList: function() {
+    this._form.removeClassName(this.options.loadingClassName);
+    this._collection = this._collection.map(function(entry) {
+      return 2 === entry.length ? entry : [entry, entry].flatten();
+    });
+    var marker = ('value' in this.options) ? this.options.value : this._text;
+    var textFound = this._collection.any(function(entry) {
+      return entry[0] == marker;
+    }.bind(this));
+    this._controls.editor.update('');
+    var option;
+    this._collection.each(function(entry, index) {
+      option = document.createElement('option');
+      option.value = entry[0];
+      option.selected = textFound ? entry[0] == marker : 0 == index;
+      option.appendChild(document.createTextNode(entry[1]));
+      this._controls.editor.appendChild(option);
+    }.bind(this));
+    this._controls.editor.disabled = false;
+    Field.scrollFreeActivate(this._controls.editor);
+  }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only  exists for a while,  in order to  let ****
+//**** users adapt to  the new API.  Read up on the new ****
+//**** API and convert your code to it ASAP!            ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+  if (!options) return;
+  function fallback(name, expr) {
+    if (name in options || expr === undefined) return;
+    options[name] = expr;
+  };
+  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+    options.cancelLink == options.cancelButton == false ? false : undefined)));
+  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+    options.okLink == options.okButton == false ? false : undefined)));
+  fallback('highlightColor', options.highlightcolor);
+  fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+  DefaultOptions: {
+    ajaxOptions: { },
+    autoRows: 3,                                // Use when multi-line w/ rows == 1
+    cancelControl: 'link',                      // 'link'|'button'|false
+    cancelText: 'cancel',
+    clickToEditText: 'Click to edit',
+    externalControl: null,                      // id|elt
+    externalControlOnly: false,
+    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
+    formClassName: 'inplaceeditor-form',
+    formId: null,                               // id|elt
+    highlightColor: '#ffff99',
+    highlightEndColor: '#ffffff',
+    hoverClassName: '',
+    htmlResponse: true,
+    loadingClassName: 'inplaceeditor-loading',
+    loadingText: 'Loading...',
+    okControl: 'button',                        // 'link'|'button'|false
+    okText: 'ok',
+    paramName: 'value',
+    rows: 1,                                    // If 1 and multi-line, uses autoRows
+    savingClassName: 'inplaceeditor-saving',
+    savingText: 'Saving...',
+    size: 0,
+    stripLoadedTextTags: false,
+    submitOnBlur: false,
+    textAfterControls: '',
+    textBeforeControls: '',
+    textBetweenControls: ''
+  },
+  DefaultCallbacks: {
+    callback: function(form) {
+      return Form.serialize(form);
+    },
+    onComplete: function(transport, element) {
+      // For backward compatibility, this one is bound to the IPE, and passes
+      // the element directly.  It was too often customized, so we don't break it.
+      new Effect.Highlight(element, {
+        startcolor: this.options.highlightColor, keepBackgroundImage: true });
+    },
+    onEnterEditMode: null,
+    onEnterHover: function(ipe) {
+      ipe.element.style.backgroundColor = ipe.options.highlightColor;
+      if (ipe._effect)
+        ipe._effect.cancel();
+    },
+    onFailure: function(transport, ipe) {
+      alert('Error communication with the server: ' + transport.responseText.stripTags());
+    },
+    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+    onLeaveEditMode: null,
+    onLeaveHover: function(ipe) {
+      ipe._effect = new Effect.Highlight(ipe.element, {
+        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+        restorecolor: ipe._originalBackground, keepBackgroundImage: true
+      });
     }
+  },
+  Listeners: {
+    click: 'enterEditMode',
+    keydown: 'checkForEscapeOrReturn',
+    mouseover: 'enterHover',
+    mouseout: 'leaveHover'
   }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+  loadingCollectionText: 'Loading options...'
 };
 
 // Delayed observer, like Form.Element.Observer, 
 // but waits for delay after last key input
 // Ideal for live-search fields
 
-Form.Element.DelayedObserver = Class.create();
-Form.Element.DelayedObserver.prototype = {
+Form.Element.DelayedObserver = Class.create({
   initialize: function(element, delay, callback) {
     this.delay     = delay || 0.5;
     this.element   = $(element);
@@ -747,4 +960,4 @@ Form.Element.DelayedObserver.prototype = {
     this.timer = null;
     this.callback(this.element, $F(this.element));
   }
-};
\ No newline at end of file
+});</diff>
      <filename>public/javascripts/controls.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,11 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
 // 
-// See scriptaculous.js for full license.
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
-/*--------------------------------------------------------------------------*/
+if(Object.isUndefined(Effect))
+  throw(&quot;dragdrop.js requires including script.aculo.us' effects.js library&quot;);
 
 var Droppables = {
   drops: [],
@@ -15,15 +18,15 @@ var Droppables = {
     element = $(element);
     var options = Object.extend({
       greedy:     true,
-      hoverclass: null  
-    }, arguments[1] || {});
+      hoverclass: null,
+      tree:       false
+    }, arguments[1] || { });
 
     // cache containers
     if(options.containment) {
       options._containers = [];
       var containment = options.containment;
-      if((typeof containment == 'object') &amp;&amp; 
-        (containment.constructor == Array)) {
+      if(Object.isArray(containment)) {
         containment.each( function(c) { options._containers.push($(c)) });
       } else {
         options._containers.push($(containment));
@@ -37,12 +40,27 @@ var Droppables = {
 
     this.drops.push(options);
   },
+  
+  findDeepestChild: function(drops) {
+    deepest = drops[0];
+      
+    for (i = 1; i &lt; drops.length; ++i)
+      if (Element.isParent(drops[i].element, deepest.element))
+        deepest = drops[i];
+    
+    return deepest;
+  },
 
   isContained: function(element, drop) {
-    var parentNode = element.parentNode;
-    return drop._containers.detect(function(c) { return parentNode == c });
+    var containmentNode;
+    if(drop.tree) {
+      containmentNode = element.treeNode; 
+    } else {
+      containmentNode = element.parentNode;
+    }
+    return drop._containers.detect(function(c) { return containmentNode == c });
   },
-
+  
   isAffected: function(point, element, drop) {
     return (
       (drop.element!=element) &amp;&amp;
@@ -68,18 +86,24 @@ var Droppables = {
 
   show: function(point, element) {
     if(!this.drops.length) return;
+    var drop, affected = [];
     
-    if(this.last_active) this.deactivate(this.last_active);
     this.drops.each( function(drop) {
-      if(Droppables.isAffected(point, element, drop)) {
-        if(drop.onHover)
-           drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
-        if(drop.greedy) { 
-          Droppables.activate(drop);
-          throw $break;
-        }
-      }
+      if(Droppables.isAffected(point, element, drop))
+        affected.push(drop);
     });
+        
+    if(affected.length&gt;0)
+      drop = Droppables.findDeepestChild(affected);
+
+    if(this.last_active &amp;&amp; this.last_active != drop) this.deactivate(this.last_active);
+    if (drop) {
+      Position.within(drop.element, point[0], point[1]);
+      if(drop.onHover)
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+      
+      if (drop != this.last_active) Droppables.activate(drop);
+    }
   },
 
   fire: function(event, element) {
@@ -87,8 +111,10 @@ var Droppables = {
     Position.prepare();
 
     if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
-      if (this.last_active.onDrop) 
-        this.last_active.onDrop(element, this.last_active.element, event);
+      if (this.last_active.onDrop) {
+        this.last_active.onDrop(element, this.last_active.element, event); 
+        return true; 
+      }
   },
 
   reset: function() {
@@ -124,11 +150,19 @@ var Draggables = {
   },
   
   activate: function(draggable) {
-    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
-    this.activeDraggable = draggable;
+    if(draggable.options.delay) { 
+      this._timeout = setTimeout(function() { 
+        Draggables._timeout = null; 
+        window.focus(); 
+        Draggables.activeDraggable = draggable; 
+      }.bind(this), draggable.options.delay); 
+    } else {
+      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+      this.activeDraggable = draggable;
+    }
   },
   
-  deactivate: function(draggbale) {
+  deactivate: function() {
     this.activeDraggable = null;
   },
   
@@ -139,13 +173,19 @@ var Draggables = {
     // the same coordinates, prevent needless redrawing (moz bug?)
     if(this._lastPointer &amp;&amp; (this._lastPointer.inspect() == pointer.inspect())) return;
     this._lastPointer = pointer;
+    
     this.activeDraggable.updateDrag(event, pointer);
   },
   
   endDrag: function(event) {
+    if(this._timeout) { 
+      clearTimeout(this._timeout); 
+      this._timeout = null; 
+    }
     if(!this.activeDraggable) return;
     this._lastPointer = null;
     this.activeDraggable.endDrag(event);
+    this.activeDraggable = null;
   },
   
   keyPress: function(event) {
@@ -168,6 +208,7 @@ var Draggables = {
       this.observers.each( function(o) {
         if(o[eventName]) o[eventName](eventName, draggable, event);
       });
+    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
   },
   
   _cacheObserverCallbacks: function() {
@@ -181,36 +222,61 @@ var Draggables = {
 
 /*--------------------------------------------------------------------------*/
 
-var Draggable = Class.create();
-Draggable.prototype = {
+var Draggable = Class.create({
   initialize: function(element) {
-    var options = Object.extend({
+    var defaults = {
       handle: false,
-      starteffect: function(element) { 
-        new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 
-      },
       reverteffect: function(element, top_offset, left_offset) {
         var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
-        element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
+        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
+          queue: {scope:'_draggable', position:'end'}
+        });
       },
-      endeffect: function(element) { 
-        new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
+      endeffect: function(element) {
+        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
+          queue: {scope:'_draggable', position:'end'},
+          afterFinish: function(){ 
+            Draggable._dragging[element] = false 
+          }
+        }); 
       },
       zindex: 1000,
       revert: false,
-      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
-    }, arguments[1] || {});
+      quiet: false,
+      scroll: false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
+      delay: 0
+    };
+    
+    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+      Object.extend(defaults, {
+        starteffect: function(element) {
+          element._opacity = Element.getOpacity(element);
+          Draggable._dragging[element] = true;
+          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
+        }
+      });
+    
+    var options = Object.extend(defaults, arguments[1] || { });
 
     this.element = $(element);
     
-    if(options.handle &amp;&amp; (typeof options.handle == 'string'))
-      this.handle = Element.childrenWithClassName(this.element, options.handle)[0];  
+    if(options.handle &amp;&amp; Object.isString(options.handle))
+      this.handle = this.element.down('.'+options.handle, 0);
+    
     if(!this.handle) this.handle = $(options.handle);
     if(!this.handle) this.handle = this.element;
+    
+    if(options.scroll &amp;&amp; !options.scroll.scrollTo &amp;&amp; !options.scroll.outerHTML) {
+      options.scroll = $(options.scroll);
+      this._isScrollChild = Element.childOf(this.element, options.scroll);
+    }
 
     Element.makePositioned(this.element); // fix IE    
 
-    this.delta    = this.currentDelta();
     this.options  = options;
     this.dragging = false;   
 
@@ -227,25 +293,23 @@ Draggable.prototype = {
   
   currentDelta: function() {
     return([
-      parseInt(this.element.style.left || '0'),
-      parseInt(this.element.style.top || '0')]);
+      parseInt(Element.getStyle(this.element,'left') || '0'),
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
   },
   
   initDrag: function(event) {
+    if(!Object.isUndefined(Draggable._dragging[this.element]) &amp;&amp;
+      Draggable._dragging[this.element]) return;
     if(Event.isLeftClick(event)) {    
       // abort on form elements, fixes a Firefox issue
       var src = Event.element(event);
-      if(src.tagName &amp;&amp; (
-        src.tagName=='INPUT' ||
-        src.tagName=='SELECT' ||
-        src.tagName=='BUTTON' ||
-        src.tagName=='TEXTAREA')) return;
+      if((tag_name = src.tagName.toUpperCase()) &amp;&amp; (
+        tag_name=='INPUT' ||
+        tag_name=='SELECT' ||
+        tag_name=='OPTION' ||
+        tag_name=='BUTTON' ||
+        tag_name=='TEXTAREA')) return;
         
-      if(this.element._revert) {
-        this.element._revert.cancel();
-        this.element._revert = null;
-      }
-      
       var pointer = [Event.pointerX(event), Event.pointerY(event)];
       var pos     = Position.cumulativeOffset(this.element);
       this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
@@ -257,6 +321,8 @@ Draggable.prototype = {
   
   startDrag: function(event) {
     this.dragging = true;
+    if(!this.delta)
+      this.delta = this.currentDelta();
     
     if(this.options.zindex) {
       this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
@@ -265,46 +331,101 @@ Draggable.prototype = {
     
     if(this.options.ghosting) {
       this._clone = this.element.cloneNode(true);
-      Position.absolutize(this.element);
+      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+      if (!this.element._originallyAbsolute)
+        Position.absolutize(this.element);
       this.element.parentNode.insertBefore(this._clone, this.element);
     }
     
+    if(this.options.scroll) {
+      if (this.options.scroll == window) {
+        var where = this._getWindowScroll(this.options.scroll);
+        this.originalScrollLeft = where.left;
+        this.originalScrollTop = where.top;
+      } else {
+        this.originalScrollLeft = this.options.scroll.scrollLeft;
+        this.originalScrollTop = this.options.scroll.scrollTop;
+      }
+    }
+    
     Draggables.notify('onStart', this, event);
+        
     if(this.options.starteffect) this.options.starteffect(this.element);
   },
   
   updateDrag: function(event, pointer) {
     if(!this.dragging) this.startDrag(event);
-    Position.prepare();
-    Droppables.show(pointer, this.element);
+    
+    if(!this.options.quiet){
+      Position.prepare();
+      Droppables.show(pointer, this.element);
+    }
+    
     Draggables.notify('onDrag', this, event);
+    
     this.draw(pointer);
     if(this.options.change) this.options.change(this);
     
+    if(this.options.scroll) {
+      this.stopScrolling();
+      
+      var p;
+      if (this.options.scroll == window) {
+        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
+      } else {
+        p = Position.page(this.options.scroll);
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
+        p.push(p[0]+this.options.scroll.offsetWidth);
+        p.push(p[1]+this.options.scroll.offsetHeight);
+      }
+      var speed = [0,0];
+      if(pointer[0] &lt; (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
+      if(pointer[1] &lt; (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
+      if(pointer[0] &gt; (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
+      if(pointer[1] &gt; (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
+      this.startScrolling(speed);
+    }
+    
     // fix AppleWebKit rendering
-    if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0);
+    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+    
     Event.stop(event);
   },
   
   finishDrag: function(event, success) {
     this.dragging = false;
+    
+    if(this.options.quiet){
+      Position.prepare();
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      Droppables.show(pointer, this.element);
+    }
 
     if(this.options.ghosting) {
-      Position.relativize(this.element);
+      if (!this.element._originallyAbsolute)
+        Position.relativize(this.element);
+      delete this.element._originallyAbsolute;
       Element.remove(this._clone);
       this._clone = null;
     }
 
-    if(success) Droppables.fire(event, this.element);
+    var dropped = false; 
+    if(success) { 
+      dropped = Droppables.fire(event, this.element); 
+      if (!dropped) dropped = false; 
+    }
+    if(dropped &amp;&amp; this.options.onDropped) this.options.onDropped(this.element);
     Draggables.notify('onEnd', this, event);
 
     var revert = this.options.revert;
-    if(revert &amp;&amp; typeof revert == 'function') revert = revert(this.element);
+    if(revert &amp;&amp; Object.isFunction(revert)) revert = revert(this.element);
     
     var d = this.currentDelta();
     if(revert &amp;&amp; this.options.reverteffect) {
-      this.options.reverteffect(this.element, 
-        d[1]-this.delta[1], d[0]-this.delta[0]);
+      if (dropped == 0 || revert != 'failure')
+        this.options.reverteffect(this.element,
+          d[1]-this.delta[1], d[0]-this.delta[0]);
     } else {
       this.delta = d;
     }
@@ -314,40 +435,53 @@ Draggable.prototype = {
 
     if(this.options.endeffect) 
       this.options.endeffect(this.element);
-
+      
     Draggables.deactivate(this);
     Droppables.reset();
   },
   
   keyPress: function(event) {
-    if(!event.keyCode==Event.KEY_ESC) return;
+    if(event.keyCode!=Event.KEY_ESC) return;
     this.finishDrag(event, false);
     Event.stop(event);
   },
   
   endDrag: function(event) {
     if(!this.dragging) return;
+    this.stopScrolling();
     this.finishDrag(event, true);
     Event.stop(event);
   },
   
   draw: function(point) {
     var pos = Position.cumulativeOffset(this.element);
+    if(this.options.ghosting) {
+      var r   = Position.realOffset(this.element);
+      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+    }
+    
     var d = this.currentDelta();
     pos[0] -= d[0]; pos[1] -= d[1];
     
-    var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
+    if(this.options.scroll &amp;&amp; (this.options.scroll != window &amp;&amp; this._isScrollChild)) {
+      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+    }
+    
+    var p = [0,1].map(function(i){ 
+      return (point[i]-pos[i]-this.offset[i]) 
+    }.bind(this));
     
     if(this.options.snap) {
-      if(typeof this.options.snap == 'function') {
-        p = this.options.snap(p[0],p[1]);
+      if(Object.isFunction(this.options.snap)) {
+        p = this.options.snap(p[0],p[1],this);
       } else {
-      if(this.options.snap instanceof Array) {
+      if(Object.isArray(this.options.snap)) {
         p = p.map( function(v, i) {
-          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
+          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
       } else {
         p = p.map( function(v) {
-          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
+          return (v/this.options.snap).round()*this.options.snap }.bind(this))
       }
     }}
     
@@ -356,14 +490,88 @@ Draggable.prototype = {
       style.left = p[0] + &quot;px&quot;;
     if((!this.options.constraint) || (this.options.constraint=='vertical'))
       style.top  = p[1] + &quot;px&quot;;
+    
     if(style.visibility==&quot;hidden&quot;) style.visibility = &quot;&quot;; // fix gecko rendering
+  },
+  
+  stopScrolling: function() {
+    if(this.scrollInterval) {
+      clearInterval(this.scrollInterval);
+      this.scrollInterval = null;
+      Draggables._lastScrollPointer = null;
+    }
+  },
+  
+  startScrolling: function(speed) {
+    if(!(speed[0] || speed[1])) return;
+    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
+    this.lastScrolled = new Date();
+    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+  },
+  
+  scroll: function() {
+    var current = new Date();
+    var delta = current - this.lastScrolled;
+    this.lastScrolled = current;
+    if(this.options.scroll == window) {
+      with (this._getWindowScroll(this.options.scroll)) {
+        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+          var d = delta / 1000;
+          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
+        }
+      }
+    } else {
+      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
+    }
+    
+    Position.prepare();
+    Droppables.show(Draggables._lastPointer, this.element);
+    Draggables.notify('onDrag', this);
+    if (this._isScrollChild) {
+      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
+      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+      if (Draggables._lastScrollPointer[0] &lt; 0)
+        Draggables._lastScrollPointer[0] = 0;
+      if (Draggables._lastScrollPointer[1] &lt; 0)
+        Draggables._lastScrollPointer[1] = 0;
+      this.draw(Draggables._lastScrollPointer);
+    }
+    
+    if(this.options.change) this.options.change(this);
+  },
+  
+  _getWindowScroll: function(w) {
+    var T, L, W, H;
+    with (w.document) {
+      if (w.document.documentElement &amp;&amp; documentElement.scrollTop) {
+        T = documentElement.scrollTop;
+        L = documentElement.scrollLeft;
+      } else if (w.document.body) {
+        T = body.scrollTop;
+        L = body.scrollLeft;
+      }
+      if (w.innerWidth) {
+        W = w.innerWidth;
+        H = w.innerHeight;
+      } else if (w.document.documentElement &amp;&amp; documentElement.clientWidth) {
+        W = documentElement.clientWidth;
+        H = documentElement.clientHeight;
+      } else {
+        W = body.offsetWidth;
+        H = body.offsetHeight
+      }
+    }
+    return { top: T, left: L, width: W, height: H };
   }
-}
+});
+
+Draggable._dragging = { };
 
 /*--------------------------------------------------------------------------*/
 
-var SortableObserver = Class.create();
-SortableObserver.prototype = {
+var SortableObserver = Class.create({
   initialize: function(element, observer) {
     this.element   = $(element);
     this.observer  = observer;
@@ -379,44 +587,68 @@ SortableObserver.prototype = {
     if(this.lastValue != Sortable.serialize(this.element))
       this.observer(this.element)
   }
-}
+});
 
 var Sortable = {
-  sortables: new Array(),
+  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
   
-  options: function(element){
-    element = $(element);
-    return this.sortables.detect(function(s) { return s.element == element });
+  sortables: { },
+  
+  _findRootElement: function(element) {
+    while (element.tagName.toUpperCase() != &quot;BODY&quot;) {  
+      if(element.id &amp;&amp; Sortable.sortables[element.id]) return element;
+      element = element.parentNode;
+    }
+  },
+
+  options: function(element) {
+    element = Sortable._findRootElement($(element));
+    if(!element) return;
+    return Sortable.sortables[element.id];
   },
   
   destroy: function(element){
-    element = $(element);
-    this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
+    var s = Sortable.options(element);
+    
+    if(s) {
       Draggables.removeObserver(s.element);
       s.droppables.each(function(d){ Droppables.remove(d) });
       s.draggables.invoke('destroy');
-    });
-    this.sortables = this.sortables.reject(function(s) { return s.element == element });
+      
+      delete Sortable.sortables[s.element.id];
+    }
   },
-  
+
   create: function(element) {
     element = $(element);
     var options = Object.extend({ 
       element:     element,
       tag:         'li',       // assumes li children, override with tag: 'tagname'
       dropOnEmpty: false,
-      tree:        false,      // fixme: unimplemented
+      tree:        false,
+      treeTag:     'ul',
       overlap:     'vertical', // one of 'vertical', 'horizontal'
       constraint:  'vertical', // one of 'vertical', 'horizontal', false
       containment: element,    // also takes array of elements (or id's); or false
       handle:      false,      // or a CSS class
       only:        false,
+      delay:       0,
       hoverclass:  null,
       ghosting:    false,
-      format:      null,
+      quiet:       false, 
+      scroll:      false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      format:      this.SERIALIZE_RULE,
+      
+      // these take arrays of elements or ids and can be 
+      // used for better initialization performance
+      elements:    false,
+      handles:     false,
+      
       onChange:    Prototype.emptyFunction,
       onUpdate:    Prototype.emptyFunction
-    }, arguments[1] || {});
+    }, arguments[1] || { });
 
     // clear any old sortable with same element
     this.destroy(element);
@@ -424,6 +656,11 @@ var Sortable = {
     // build options for the draggables
     var options_for_draggable = {
       revert:      true,
+      quiet:       options.quiet,
+      scroll:      options.scroll,
+      scrollSpeed: options.scrollSpeed,
+      scrollSensitivity: options.scrollSensitivity,
+      delay:       options.delay,
       ghosting:    options.ghosting,
       constraint:  options.constraint,
       handle:      options.handle };
@@ -449,9 +686,16 @@ var Sortable = {
     var options_for_droppable = {
       overlap:     options.overlap,
       containment: options.containment,
+      tree:        options.tree,
       hoverclass:  options.hoverclass,
-      onHover:     Sortable.onHover,
-      greedy:      !options.dropOnEmpty
+      onHover:     Sortable.onHover
+    }
+    
+    var options_for_tree = {
+      onHover:      Sortable.onEmptyHover,
+      overlap:      options.overlap,
+      containment:  options.containment,
+      hoverclass:   options.hoverclass
     }
 
     // fix for gecko engine
@@ -460,27 +704,32 @@ var Sortable = {
     options.draggables = [];
     options.droppables = [];
 
-    // make it so
-
     // drop on empty handling
-    if(options.dropOnEmpty) {
-      Droppables.add(element,
-        {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
+    if(options.dropOnEmpty || options.tree) {
+      Droppables.add(element, options_for_tree);
       options.droppables.push(element);
     }
 
-    (this.findElements(element, options) || []).each( function(e) {
-      // handles are per-draggable
-      var handle = options.handle ? 
-        Element.childrenWithClassName(e, options.handle)[0] : e;    
+    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
+      var handle = options.handles ? $(options.handles[i]) :
+        (options.handle ? $(e).select('.' + options.handle)[0] : e); 
       options.draggables.push(
         new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
       Droppables.add(e, options_for_droppable);
+      if(options.tree) e.treeNode = element;
       options.droppables.push(e);      
     });
+    
+    if(options.tree) {
+      (Sortable.findTreeElements(element, options) || []).each( function(e) {
+        Droppables.add(e, options_for_tree);
+        e.treeNode = element;
+        options.droppables.push(e);
+      });
+    }
 
     // keep reference
-    this.sortables.push(options);
+    this.sortables[element.id] = options;
 
     // for onupdate
     Draggables.addObserver(new SortableObserver(element, options.onUpdate));
@@ -489,23 +738,21 @@ var Sortable = {
 
   // return all suitable-for-sortable elements in a guaranteed order
   findElements: function(element, options) {
-    if(!element.hasChildNodes()) return null;
-    var elements = [];
-    $A(element.childNodes).each( function(e) {
-      if(e.tagName &amp;&amp; e.tagName.toUpperCase()==options.tag.toUpperCase() &amp;&amp;
-        (!options.only || (Element.hasClassName(e, options.only))))
-          elements.push(e);
-      if(options.tree) {
-        var grandchildren = this.findElements(e, options);
-        if(grandchildren) elements.push(grandchildren);
-      }
-    });
-
-    return (elements.length&gt;0 ? elements.flatten() : null);
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.tag);
+  },
+  
+  findTreeElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.treeTag);
   },
 
   onHover: function(element, dropon, overlap) {
-    if(overlap&gt;0.5) {
+    if(Element.isParent(dropon, element)) return;
+
+    if(overlap &gt; .33 &amp;&amp; overlap &lt; .66 &amp;&amp; Sortable.options(dropon).tree) {
+      return;
+    } else if(overlap&gt;0.5) {
       Sortable.mark(dropon, 'before');
       if(dropon.previousSibling != element) {
         var oldParentNode = element.parentNode;
@@ -528,18 +775,42 @@ var Sortable = {
       }
     }
   },
-
-  onEmptyHover: function(element, dropon) {
-    if(element.parentNode!=dropon) {
-      var oldParentNode = element.parentNode;
-      dropon.appendChild(element);
+  
+  onEmptyHover: function(element, dropon, overlap) {
+    var oldParentNode = element.parentNode;
+    var droponOptions = Sortable.options(dropon);
+        
+    if(!Element.isParent(dropon, element)) {
+      var index;
+      
+      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
+      var child = null;
+            
+      if(children) {
+        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
+        
+        for (index = 0; index &lt; children.length; index += 1) {
+          if (offset - Element.offsetSize (children[index], droponOptions.overlap) &gt;= 0) {
+            offset -= Element.offsetSize (children[index], droponOptions.overlap);
+          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) &gt;= 0) {
+            child = index + 1 &lt; children.length ? children[index + 1] : null;
+            break;
+          } else {
+            child = children[index];
+            break;
+          }
+        }
+      }
+      
+      dropon.insertBefore(element, child);
+      
       Sortable.options(oldParentNode).onChange(element);
-      Sortable.options(dropon).onChange(element);
+      droponOptions.onChange(element);
     }
   },
 
   unmark: function() {
-    if(Sortable._marker) Element.hide(Sortable._marker);
+    if(Sortable._marker) Sortable._marker.hide();
   },
 
   mark: function(dropon, position) {
@@ -548,37 +819,154 @@ var Sortable = {
     if(sortable &amp;&amp; !sortable.ghosting) return; 
 
     if(!Sortable._marker) {
-      Sortable._marker = $('dropmarker') || document.createElement('DIV');
-      Element.hide(Sortable._marker);
-      Element.addClassName(Sortable._marker, 'dropmarker');
-      Sortable._marker.style.position = 'absolute';
+      Sortable._marker = 
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
       document.getElementsByTagName(&quot;body&quot;).item(0).appendChild(Sortable._marker);
     }    
     var offsets = Position.cumulativeOffset(dropon);
-    Sortable._marker.style.left = offsets[0] + 'px';
-    Sortable._marker.style.top = offsets[1] + 'px';
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
     
     if(position=='after')
       if(sortable.overlap == 'horizontal') 
-        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
       else
-        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
     
-    Element.show(Sortable._marker);
+    Sortable._marker.show();
   },
+  
+  _tree: function(element, options, parent) {
+    var children = Sortable.findElements(element, options) || [];
+  
+    for (var i = 0; i &lt; children.length; ++i) {
+      var match = children[i].id.match(options.format);
 
-  serialize: function(element) {
+      if (!match) continue;
+      
+      var child = {
+        id: encodeURIComponent(match ? match[1] : null),
+        element: element,
+        parent: parent,
+        children: [],
+        position: parent.children.length,
+        container: $(children[i]).down(options.treeTag)
+      }
+      
+      /* Get the element containing the children and recurse over it */
+      if (child.container)
+        this._tree(child.container, options, child)
+      
+      parent.children.push (child);
+    }
+
+    return parent; 
+  },
+
+  tree: function(element) {
     element = $(element);
     var sortableOptions = this.options(element);
     var options = Object.extend({
-      tag:  sortableOptions.tag,
+      tag: sortableOptions.tag,
+      treeTag: sortableOptions.treeTag,
       only: sortableOptions.only,
       name: element.id,
-      format: sortableOptions.format || /^[^_]*_(.*)$/
-    }, arguments[1] || {});
+      format: sortableOptions.format
+    }, arguments[1] || { });
+    
+    var root = {
+      id: null,
+      parent: null,
+      children: [],
+      container: element,
+      position: 0
+    }
+    
+    return Sortable._tree(element, options, root);
+  },
+
+  /* Construct a [i] index for a particular node */
+  _constructIndex: function(node) {
+    var index = '';
+    do {
+      if (node.id) index = '[' + node.position + ']' + index;
+    } while ((node = node.parent) != null);
+    return index;
+  },
+
+  sequence: function(element) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[1] || { });
+    
     return $(this.findElements(element, options) || []).map( function(item) {
-      return (encodeURIComponent(options.name) + &quot;[]=&quot; + 
-              encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
-    }).join(&quot;&amp;&quot;);
+      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
+    });
+  },
+
+  setSequence: function(element, new_sequence) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[2] || { });
+    
+    var nodeMap = { };
+    this.findElements(element, options).each( function(n) {
+        if (n.id.match(options.format))
+            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+        n.parentNode.removeChild(n);
+    });
+   
+    new_sequence.each(function(ident) {
+      var n = nodeMap[ident];
+      if (n) {
+        n[1].appendChild(n[0]);
+        delete nodeMap[ident];
+      }
+    });
+  },
+  
+  serialize: function(element) {
+    element = $(element);
+    var options = Object.extend(Sortable.options(element), arguments[1] || { });
+    var name = encodeURIComponent(
+      (arguments[1] &amp;&amp; arguments[1].name) ? arguments[1].name : element.id);
+    
+    if (options.tree) {
+      return Sortable.tree(element, arguments[1]).children.map( function (item) {
+        return [name + Sortable._constructIndex(item) + &quot;[id]=&quot; + 
+                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
+      }).flatten().join('&amp;');
+    } else {
+      return Sortable.sequence(element, arguments[1]).map( function(item) {
+        return name + &quot;[]=&quot; + encodeURIComponent(item);
+      }).join('&amp;');
+    }
   }
-}
\ No newline at end of file
+}
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+  if (!child.parentNode || child == element) return false;
+  if (child.parentNode == element) return true;
+  return Element.isParent(child.parentNode, element);
+}
+
+Element.findChildren = function(element, only, recursive, tagName) {   
+  if(!element.hasChildNodes()) return null;
+  tagName = tagName.toUpperCase();
+  if(only) only = [only].flatten();
+  var elements = [];
+  $A(element.childNodes).each( function(e) {
+    if(e.tagName &amp;&amp; e.tagName.toUpperCase()==tagName &amp;&amp;
+      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
+        elements.push(e);
+    if(recursive) {
+      var grandchildren = Element.findChildren(e, only, recursive, tagName);
+      if(grandchildren) elements.push(grandchildren);
+    }
+  });
+
+  return (elements.length&gt;0 ? elements.flatten() : []);
+}
+
+Element.offsetSize = function (element, type) {
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+}</diff>
      <filename>public/javascripts/dragdrop.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,109 +1,124 @@
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
 // Contributors:
 //  Justin Palmer (http://encytemedia.com/)
 //  Mark Pilgrim (http://diveintomark.org/)
 //  Martin Bialasinki
 // 
-// See scriptaculous.js for full license.  
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
 
-/* ------------- element ext -------------- */  
- 
 // converts rgb() and #xxx to #xxxxxx format,  
 // returns self (or first argument) if not convertable  
 String.prototype.parseColor = function() {  
-  var color = '#';  
-  if(this.slice(0,4) == 'rgb(') {  
+  var color = '#';
+  if (this.slice(0,4) == 'rgb(') {  
     var cols = this.slice(4,this.length-1).split(',');  
     var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i&lt;3);  
   } else {  
-    if(this.slice(0,1) == '#') {  
-      if(this.length==4) for(var i=1;i&lt;4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
-      if(this.length==7) color = this.toLowerCase();  
+    if (this.slice(0,1) == '#') {  
+      if (this.length==4) for(var i=1;i&lt;4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if (this.length==7) color = this.toLowerCase();  
     }  
   }  
-  return(color.length==7 ? color : (arguments[0] || this));  
-}  
-
-Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {  
-  var children = $(element).childNodes;  
-  var text     = '';  
-  var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');  
- 
-  for (var i = 0; i &lt; children.length; i++) {  
-    if(children[i].nodeType==3) {  
-      text+=children[i].nodeValue;  
-    } else {  
-      if((!children[i].className.match(classtest)) &amp;&amp; children[i].hasChildNodes())  
-        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);  
-    }  
-  }  
- 
-  return text;
-}
+  return (color.length==7 ? color : (arguments[0] || this));  
+};
 
-Element.setStyle = function(element, style) {
-  element = $(element);
-  for(k in style) element.style[k.camelize()] = style[k];
-}
-
-Element.setContentZoom = function(element, percent) {  
-  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
-  if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0);  
-}
-
-Element.getOpacity = function(element){  
-  var opacity;
-  if (opacity = Element.getStyle(element, 'opacity'))  
-    return parseFloat(opacity);  
-  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
-    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
-  return 1.0;  
-}
-
-Element.setOpacity = function(element, value){  
-  element= $(element);  
-  if (value == 1){
-    Element.setStyle(element, { opacity: 
-      (/Gecko/.test(navigator.userAgent) &amp;&amp; !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
-      0.999999 : null });
-    if(/MSIE/.test(navigator.userAgent))  
-      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
-  } else {  
-    if(value &lt; 0.00001) value = 0;  
-    Element.setStyle(element, {opacity: value});
-    if(/MSIE/.test(navigator.userAgent))  
-     Element.setStyle(element, 
-       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
-                 'alpha(opacity='+value*100+')' });  
-  }   
-}  
- 
-Element.getInlineOpacity = function(element){  
-  return $(element).style.opacity || '';
-}  
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+};
 
-Element.childrenWithClassName = function(element, className) {  
-  return $A($(element).getElementsByTagName('*')).select(
-    function(c) { return Element.hasClassName(c, className) });
-}
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() &amp;&amp; !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+};
 
-Array.prototype.call = function() {
-  var args = arguments;
-  this.each(function(f){ f.apply(this, args) });
-}
+Element.setContentZoom = function(element, percent) {
+  element = $(element);  
+  element.setStyle({fontSize: (percent/100) + 'em'});   
+  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+};
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
 
 /*--------------------------------------------------------------------------*/
 
 var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  Transitions: {
+    linear: Prototype.K,
+    sinoidal: function(pos) {
+      return (-Math.cos(pos*Math.PI)/2) + 0.5;
+    },
+    reverse: function(pos) {
+      return 1-pos;
+    },
+    flicker: function(pos) {
+      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+      return pos &gt; 1 ? 1 : pos;
+    },
+    wobble: function(pos) {
+      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+    },
+    pulse: function(pos, pulses) { 
+      pulses = pulses || 5; 
+      return (
+        ((pos % (1/pulses)) * pulses).round() == 0 ? 
+              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
+          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+        );
+    },
+    spring: function(pos) { 
+      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
+    },
+    none: function(pos) {
+      return 0;
+    },
+    full: function(pos) {
+      return 1;
+    }
+  },
+  DefaultOptions: {
+    duration:   1.0,   // seconds
+    fps:        100,   // 100= assume 66fps max.
+    sync:       false, // true for combining
+    from:       0.0,
+    to:         1.0,
+    delay:      0.0,
+    queue:      'parallel'
+  },
   tagifyText: function(element) {
     var tagifyStyle = 'position:relative';
-    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
+    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+    
     element = $(element);
     $A(element.childNodes).each( function(child) {
-      if(child.nodeType==3) {
+      if (child.nodeType==3) {
         child.nodeValue.toArray().each( function(character) {
           element.insertBefore(
-            Builder.node('span',{style: tagifyStyle},
+            new Element('span', {style: tagifyStyle}).update(
               character == ' ' ? String.fromCharCode(160) : character), 
               child);
         });
@@ -113,8 +128,8 @@ var Effect = {
   },
   multiple: function(element, effect) {
     var elements;
-    if(((typeof element == 'object') || 
-        (typeof element == 'function')) &amp;&amp; 
+    if (((typeof element == 'object') || 
+        Object.isFunction(element)) &amp;&amp; 
        (element.length))
       elements = element;
     else
@@ -123,59 +138,48 @@ var Effect = {
     var options = Object.extend({
       speed: 0.1,
       delay: 0.0
-    }, arguments[2] || {});
+    }, arguments[2] || { });
     var masterDelay = options.delay;
 
     $A(elements).each( function(element, index) {
       new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
     });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, arguments[2] || { });
+    Effect[element.visible() ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
   }
 };
 
-var Effect2 = Effect; // deprecated
-
-/* ------------- transitions ------------- */
-
-Effect.Transitions = {}
-
-Effect.Transitions.linear = function(pos) {
-  return pos;
-}
-Effect.Transitions.sinoidal = function(pos) {
-  return (-Math.cos(pos*Math.PI)/2) + 0.5;
-}
-Effect.Transitions.reverse  = function(pos) {
-  return 1-pos;
-}
-Effect.Transitions.flicker = function(pos) {
-  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
-}
-Effect.Transitions.wobble = function(pos) {
-  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
-}
-Effect.Transitions.pulse = function(pos) {
-  return (Math.floor(pos*10) % 2 == 0 ? 
-    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
-}
-Effect.Transitions.none = function(pos) {
-  return 0;
-}
-Effect.Transitions.full = function(pos) {
-  return 1;
-}
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
 
 /* ------------- core effects ------------- */
 
-Effect.Queue = {
-  effects:  [],
+Effect.ScopedQueue = Class.create(Enumerable, {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;    
+  },
   _each: function(iterator) {
     this.effects._each(iterator);
   },
-  interval: null,
   add: function(effect) {
     var timestamp = new Date().getTime();
     
-    switch(effect.options.queue) {
+    var position = Object.isString(effect.options.queue) ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
       case 'front':
         // move unstarted effects after this effect  
         this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
@@ -183,6 +187,9 @@ Effect.Queue = {
             e.finishOn += effect.finishOn;
           });
         break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
       case 'end':
         // start effect after last queued effect has finished
         timestamp = this.effects.pluck('finishOn').max() || timestamp;
@@ -191,98 +198,112 @@ Effect.Queue = {
     
     effect.startOn  += timestamp;
     effect.finishOn += timestamp;
-    this.effects.push(effect);
-    if(!this.interval) 
-      this.interval = setInterval(this.loop.bind(this), 40);
+
+    if (!effect.options.queue.limit || (this.effects.length &lt; effect.options.queue.limit))
+      this.effects.push(effect);
+    
+    if (!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
   },
   remove: function(effect) {
     this.effects = this.effects.reject(function(e) { return e==effect });
-    if(this.effects.length == 0) {
+    if (this.effects.length == 0) {
       clearInterval(this.interval);
       this.interval = null;
     }
   },
   loop: function() {
     var timePos = new Date().getTime();
-    this.effects.invoke('loop', timePos);
+    for(var i=0, len=this.effects.length;i&lt;len;i++) 
+      this.effects[i] &amp;&amp; this.effects[i].loop(timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if (!Object.isString(queueName)) return queueName;
+    
+    return this.instances.get(queueName) ||
+      this.instances.set(queueName, new Effect.ScopedQueue());
   }
-}
-Object.extend(Effect.Queue, Enumerable);
+};
+Effect.Queue = Effect.Queues.get('global');
 
-Effect.Base = function() {};
-Effect.Base.prototype = {
+Effect.Base = Class.create({
   position: null,
-  setOptions: function(options) {
-    this.options = Object.extend({
-      transition: Effect.Transitions.sinoidal,
-      duration:   1.0,   // seconds
-      fps:        25.0,  // max. 25fps due to Effect.Queue implementation
-      sync:       false, // true for combining
-      from:       0.0,
-      to:         1.0,
-      delay:      0.0,
-      queue:      'parallel'
-    }, options || {});
-  },
   start: function(options) {
-    this.setOptions(options || {});
+    function codeForEvent(options,eventName){
+      return (
+        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+      );
+    }
+    if (options &amp;&amp; options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
     this.currentFrame = 0;
     this.state        = 'idle';
     this.startOn      = this.options.delay*1000;
-    this.finishOn     = this.startOn + (this.options.duration*1000);
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+    
+    eval('this.render = function(pos){ '+
+      'if (this.state==&quot;idle&quot;){this.state=&quot;running&quot;;'+
+      codeForEvent(this.options,'beforeSetup')+
+      (this.setup ? 'this.setup();':'')+ 
+      codeForEvent(this.options,'afterSetup')+
+      '};if (this.state==&quot;running&quot;){'+
+      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+      'this.position=pos;'+
+      codeForEvent(this.options,'beforeUpdate')+
+      (this.update ? 'this.update(pos);':'')+
+      codeForEvent(this.options,'afterUpdate')+
+      '}}');
+    
     this.event('beforeStart');
-    if(!this.options.sync) Effect.Queue.add(this);
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).add(this);
   },
   loop: function(timePos) {
-    if(timePos &gt;= this.startOn) {
-      if(timePos &gt;= this.finishOn) {
+    if (timePos &gt;= this.startOn) {
+      if (timePos &gt;= this.finishOn) {
         this.render(1.0);
         this.cancel();
         this.event('beforeFinish');
-        if(this.finish) this.finish(); 
+        if (this.finish) this.finish(); 
         this.event('afterFinish');
         return;  
       }
-      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
-      var frame = Math.round(pos * this.options.fps * this.options.duration);
-      if(frame &gt; this.currentFrame) {
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = (pos * this.totalFrames).round();
+      if (frame &gt; this.currentFrame) {
         this.render(pos);
         this.currentFrame = frame;
       }
     }
   },
-  render: function(pos) {
-    if(this.state == 'idle') {
-      this.state = 'running';
-      this.event('beforeSetup');
-      if(this.setup) this.setup();
-      this.event('afterSetup');
-    }
-    if(this.state == 'running') {
-      if(this.options.transition) pos = this.options.transition(pos);
-      pos *= (this.options.to-this.options.from);
-      pos += this.options.from;
-      this.position = pos;
-      this.event('beforeUpdate');
-      if(this.update) this.update(pos);
-      this.event('afterUpdate');
-    }
-  },
   cancel: function() {
-    if(!this.options.sync) Effect.Queue.remove(this);
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).remove(this);
     this.state = 'finished';
   },
   event: function(eventName) {
-    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
-    if(this.options[eventName]) this.options[eventName](this);
+    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if (this.options[eventName]) this.options[eventName](this);
   },
   inspect: function() {
-    return '#&lt;Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '&gt;';
+    var data = $H();
+    for(property in this)
+      if (!Object.isFunction(this[property])) data.set(property, this[property]);
+    return '#&lt;Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '&gt;';
   }
-}
+});
 
-Effect.Parallel = Class.create();
-Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+Effect.Parallel = Class.create(Effect.Base, {
   initialize: function(effects) {
     this.effects = effects || [];
     this.start(arguments[1]);
@@ -295,75 +316,106 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
       effect.render(1.0);
       effect.cancel();
       effect.event('beforeFinish');
-      if(effect.finish) effect.finish(position);
+      if (effect.finish) effect.finish(position);
       effect.event('afterFinish');
     });
   }
 });
 
-Effect.Opacity = Class.create();
-Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+Effect.Tween = Class.create(Effect.Base, {
+  initialize: function(object, from, to) {
+    object = Object.isString(object) ? $(object) : object;
+    var args = $A(arguments), method = args.last(), 
+      options = args.length == 5 ? args[3] : null;
+    this.method = Object.isFunction(method) ? method.bind(object) :
+      Object.isFunction(object[method]) ? object[method].bind(object) : 
+      function(value) { object[method] = value };
+    this.start(Object.extend({ from: from, to: to }, options || { }));
+  },
+  update: function(position) {
+    this.method(position);
+  }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+  initialize: function() {
+    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
   initialize: function(element) {
     this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
     // make this work on IE on elements without 'layout'
-    if(/MSIE/.test(navigator.userAgent) &amp;&amp; (!this.element.hasLayout))
-      Element.setStyle(this.element, {zoom: 1});
+    if (Prototype.Browser.IE &amp;&amp; (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
     var options = Object.extend({
-      from: Element.getOpacity(this.element) || 0.0,
+      from: this.element.getOpacity() || 0.0,
       to:   1.0
-    }, arguments[1] || {});
+    }, arguments[1] || { });
     this.start(options);
   },
   update: function(position) {
-    Element.setOpacity(this.element, position);
+    this.element.setOpacity(position);
   }
 });
 
-Effect.MoveBy = Class.create();
-Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
-  initialize: function(element, toTop, toLeft) {
-    this.element      = $(element);
-    this.toTop        = toTop;
-    this.toLeft       = toLeft;
-    this.start(arguments[3]);
+Effect.Move = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || { });
+    this.start(options);
   },
   setup: function() {
-    // Bug in Opera: Opera returns the &quot;real&quot; position of a static element or
-    // relative element that does not have top/left explicitly set.
-    // ==&gt; Always set top and left for position relative elements in your stylesheets 
-    // (to 0 if you do not need them) 
-    Element.makePositioned(this.element);
-    this.originalTop  = parseFloat(Element.getStyle(this.element,'top')  || '0');
-    this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if (this.options.mode == 'absolute') {
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
   },
   update: function(position) {
-    Element.setStyle(this.element, {
-      top:  this.toTop  * position + this.originalTop + 'px',
-      left: this.toLeft * position + this.originalLeft + 'px'
+    this.element.setStyle({
+      left: (this.options.x  * position + this.originalLeft).round() + 'px',
+      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
     });
   }
 });
 
-Effect.Scale = Class.create();
-Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
   initialize: function(element, percent) {
-    this.element = $(element)
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
     var options = Object.extend({
       scaleX: true,
       scaleY: true,
       scaleContent: true,
       scaleFromCenter: false,
-      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
+      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
       scaleFrom: 100.0,
       scaleTo:   percent
-    }, arguments[2] || {});
+    }, arguments[2] || { });
     this.start(options);
   },
   setup: function() {
     this.restoreAfterFinish = this.options.restoreAfterFinish || false;
-    this.elementPositioning = Element.getStyle(this.element,'position');
+    this.elementPositioning = this.element.getStyle('position');
     
-    this.originalStyle = {};
+    this.originalStyle = { };
     ['top','left','width','height','fontSize'].each( function(k) {
       this.originalStyle[k] = this.element.style[k];
     }.bind(this));
@@ -371,9 +423,9 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
     this.originalTop  = this.element.offsetTop;
     this.originalLeft = this.element.offsetLeft;
     
-    var fontSize = Element.getStyle(this.element,'font-size') || '100%';
-    ['em','px','%'].each( function(fontSizeType) {
-      if(fontSize.indexOf(fontSizeType)&gt;0) {
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if (fontSize.indexOf(fontSizeType)&gt;0) {
         this.fontSize     = parseFloat(fontSize);
         this.fontSizeType = fontSizeType;
       }
@@ -382,183 +434,184 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
     this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
     
     this.dims = null;
-    if(this.options.scaleMode=='box')
+    if (this.options.scaleMode=='box')
       this.dims = [this.element.offsetHeight, this.element.offsetWidth];
-    if(/^content/.test(this.options.scaleMode))
+    if (/^content/.test(this.options.scaleMode))
       this.dims = [this.element.scrollHeight, this.element.scrollWidth];
-    if(!this.dims)
+    if (!this.dims)
       this.dims = [this.options.scaleMode.originalHeight,
                    this.options.scaleMode.originalWidth];
   },
   update: function(position) {
     var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
-    if(this.options.scaleContent &amp;&amp; this.fontSize)
-      Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
+    if (this.options.scaleContent &amp;&amp; this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
     this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
   },
   finish: function(position) {
-    if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
+    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
   },
   setDimensions: function(height, width) {
-    var d = {};
-    if(this.options.scaleX) d.width = width + 'px';
-    if(this.options.scaleY) d.height = height + 'px';
-    if(this.options.scaleFromCenter) {
+    var d = { };
+    if (this.options.scaleX) d.width = width.round() + 'px';
+    if (this.options.scaleY) d.height = height.round() + 'px';
+    if (this.options.scaleFromCenter) {
       var topd  = (height - this.dims[0])/2;
       var leftd = (width  - this.dims[1])/2;
-      if(this.elementPositioning == 'absolute') {
-        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
-        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      if (this.elementPositioning == 'absolute') {
+        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
       } else {
-        if(this.options.scaleY) d.top = -topd + 'px';
-        if(this.options.scaleX) d.left = -leftd + 'px';
+        if (this.options.scaleY) d.top = -topd + 'px';
+        if (this.options.scaleX) d.left = -leftd + 'px';
       }
     }
-    Element.setStyle(this.element, d);
+    this.element.setStyle(d);
   }
 });
 
-Effect.Highlight = Class.create();
-Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
+Effect.Highlight = Class.create(Effect.Base, {
   initialize: function(element) {
     this.element = $(element);
-    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
     this.start(options);
   },
   setup: function() {
     // Prevent executing on elements not in the layout flow
-    if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
+    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
     // Disable background image during the effect
-    this.oldStyle = {
-      backgroundImage: Element.getStyle(this.element, 'background-image') };
-    Element.setStyle(this.element, {backgroundImage: 'none'});
-    if(!this.options.endcolor)
-      this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
-    if(!this.options.restorecolor)
-      this.options.restorecolor = Element.getStyle(this.element, 'background-color');
+    this.oldStyle = { };
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if (!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if (!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
     // init color calculations
     this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
     this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
   },
   update: function(position) {
-    Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
-      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
   },
   finish: function() {
-    Element.setStyle(this.element, Object.extend(this.oldStyle, {
+    this.element.setStyle(Object.extend(this.oldStyle, {
       backgroundColor: this.options.restorecolor
     }));
   }
 });
 
-Effect.ScrollTo = Class.create();
-Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
-  initialize: function(element) {
-    this.element = $(element);
-    this.start(arguments[1] || {});
-  },
-  setup: function() {
-    Position.prepare();
-    var offsets = Position.cumulativeOffset(this.element);
-    if(this.options.offset) offsets[1] += this.options.offset;
-    var max = window.innerHeight ? 
-      window.height - window.innerHeight :
-      document.body.scrollHeight - 
-        (document.documentElement.clientHeight ? 
-          document.documentElement.clientHeight : document.body.clientHeight);
-    this.scrollStart = Position.deltaY;
-    this.delta = (offsets[1] &gt; max ? max : offsets[1]) - this.scrollStart;
-  },
-  update: function(position) {
-    Position.prepare();
-    window.scrollTo(Position.deltaX, 
-      this.scrollStart + (position*this.delta));
-  }
-});
+Effect.ScrollTo = function(element) {
+  var options = arguments[1] || { },
+    scrollOffsets = document.viewport.getScrollOffsets(),
+    elementOffsets = $(element).cumulativeOffset(),
+    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
+
+  if (options.offset) elementOffsets[1] += options.offset;
+
+  return new Effect.Tween(null,
+    scrollOffsets.top,
+    elementOffsets[1] &gt; max ? max : elementOffsets[1],
+    options,
+    function(p){ scrollTo(scrollOffsets.left, p.round()) }
+  );
+};
 
 /* ------------- combination effects ------------- */
 
 Effect.Fade = function(element) {
-  var oldOpacity = Element.getInlineOpacity(element);
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
   var options = Object.extend({
-  from: Element.getOpacity(element) || 1.0,
-  to:   0.0,
-  afterFinishInternal: function(effect) { with(Element) { 
-    if(effect.options.to!=0) return;
-    hide(effect.element);
-    setStyle(effect.element, {opacity: oldOpacity}); }}
-  }, arguments[1] || {});
+    from: element.getOpacity() || 1.0,
+    to:   0.0,
+    afterFinishInternal: function(effect) { 
+      if (effect.options.to!=0) return;
+      effect.element.hide().setStyle({opacity: oldOpacity}); 
+    }
+  }, arguments[1] || { });
   return new Effect.Opacity(element,options);
-}
+};
 
 Effect.Appear = function(element) {
+  element = $(element);
   var options = Object.extend({
-  from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
   to:   1.0,
-  beforeSetup: function(effect) { with(Element) {
-    setOpacity(effect.element, effect.options.from);
-    show(effect.element); }}
-  }, arguments[1] || {});
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show(); 
+  }}, arguments[1] || { });
   return new Effect.Opacity(element,options);
-}
+};
 
 Effect.Puff = function(element) {
   element = $(element);
-  var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
+  var oldStyle = { 
+    opacity: element.getInlineOpacity(), 
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
   return new Effect.Parallel(
    [ new Effect.Scale(element, 200, 
       { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
      Object.extend({ duration: 1.0, 
-      beforeSetupInternal: function(effect) { with(Element) {
-        setStyle(effect.effects[0].element, {position: 'absolute'}); }},
-      afterFinishInternal: function(effect) { with(Element) {
-         hide(effect.effects[0].element);
-         setStyle(effect.effects[0].element, oldStyle); }}
-     }, arguments[1] || {})
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element)
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || { })
    );
-}
+};
 
 Effect.BlindUp = function(element) {
   element = $(element);
-  Element.makeClipping(element);
-  return new Effect.Scale(element, 0, 
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
     Object.extend({ scaleContent: false, 
       scaleX: false, 
       restoreAfterFinish: true,
-      afterFinishInternal: function(effect) { with(Element) {
-        [hide, undoClipping].call(effect.element); }} 
-    }, arguments[1] || {})
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      } 
+    }, arguments[1] || { })
   );
-}
+};
 
 Effect.BlindDown = function(element) {
   element = $(element);
-  var oldHeight = Element.getStyle(element, 'height');
-  var elementDimensions = Element.getDimensions(element);
-  return new Effect.Scale(element, 100, 
-    Object.extend({ scaleContent: false, 
-      scaleX: false,
-      scaleFrom: 0,
-      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
-      restoreAfterFinish: true,
-      afterSetup: function(effect) { with(Element) {
-        makeClipping(effect.element);
-        setStyle(effect.element, {height: '0px'});
-        show(effect.element); 
-      }},  
-      afterFinishInternal: function(effect) { with(Element) {
-        undoClipping(effect.element);
-        setStyle(effect.element, {height: oldHeight});
-      }}
-    }, arguments[1] || {})
-  );
-}
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || { }));
+};
 
 Effect.SwitchOff = function(element) {
   element = $(element);
-  var oldOpacity = Element.getInlineOpacity(element);
-  return new Effect.Appear(element, { 
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
     duration: 0.4,
     from: 0,
     transition: Effect.Transitions.flicker,
@@ -566,146 +619,150 @@ Effect.SwitchOff = function(element) {
       new Effect.Scale(effect.element, 1, { 
         duration: 0.3, scaleFromCenter: true,
         scaleX: false, scaleContent: false, restoreAfterFinish: true,
-        beforeSetup: function(effect) { with(Element) {
-          [makePositioned,makeClipping].call(effect.element);
-        }},
-        afterFinishInternal: function(effect) { with(Element) {
-          [hide,undoClipping,undoPositioned].call(effect.element);
-          setStyle(effect.element, {opacity: oldOpacity});
-        }}
+        beforeSetup: function(effect) { 
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
       })
     }
-  });
-}
+  }, arguments[1] || { }));
+};
 
 Effect.DropOut = function(element) {
   element = $(element);
   var oldStyle = {
-    top: Element.getStyle(element, 'top'),
-    left: Element.getStyle(element, 'left'),
-    opacity: Element.getInlineOpacity(element) };
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
   return new Effect.Parallel(
-    [ new Effect.MoveBy(element, 100, 0, { sync: true }), 
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
       new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
     Object.extend(
       { duration: 0.5,
-        beforeSetup: function(effect) { with(Element) {
-          makePositioned(effect.effects[0].element); }},
-        afterFinishInternal: function(effect) { with(Element) {
-          [hide, undoPositioned].call(effect.effects[0].element);
-          setStyle(effect.effects[0].element, oldStyle); }} 
-      }, arguments[1] || {}));
-}
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned(); 
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        } 
+      }, arguments[1] || { }));
+};
 
 Effect.Shake = function(element) {
   element = $(element);
+  var options = Object.extend({
+    distance: 20,
+    duration: 0.5
+  }, arguments[1] || {});
+  var distance = parseFloat(options.distance);
+  var split = parseFloat(options.duration) / 10.0;
   var oldStyle = {
-    top: Element.getStyle(element, 'top'),
-    left: Element.getStyle(element, 'left') };
-  return new Effect.MoveBy(element, 0, 20, 
-    { duration: 0.05, afterFinishInternal: function(effect) {
-  new Effect.MoveBy(effect.element, 0, -40, 
-    { duration: 0.1, afterFinishInternal: function(effect) {
-  new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinishInternal: function(effect) {
-  new Effect.MoveBy(effect.element, 0, -40, 
-    { duration: 0.1, afterFinishInternal: function(effect) {
-  new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinishInternal: function(effect) {
-  new Effect.MoveBy(effect.element, 0, -20, 
-    { duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
-        undoPositioned(effect.element);
-        setStyle(effect.element, oldStyle);
-  }}}) }}) }}) }}) }}) }});
-}
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element,
+      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
+  }}) }}) }}) }}) }}) }});
+};
 
 Effect.SlideDown = function(element) {
-  element = $(element);
-  Element.cleanWhitespace(element);
+  element = $(element).cleanWhitespace();
   // SlideDown need to have the content of the element wrapped in a container element with fixed height!
-  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
-  var elementDimensions = Element.getDimensions(element);
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
   return new Effect.Scale(element, 100, Object.extend({ 
     scaleContent: false, 
     scaleX: false, 
-    scaleFrom: 0,
+    scaleFrom: window.opera ? 0 : 1,
     scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
     restoreAfterFinish: true,
-    afterSetup: function(effect) { with(Element) {
-      makePositioned(effect.element);
-      makePositioned(effect.element.firstChild);
-      if(window.opera) setStyle(effect.element, {top: ''});
-      makeClipping(effect.element);
-      setStyle(effect.element, {height: '0px'});
-      show(element); }},
-    afterUpdateInternal: function(effect) { with(Element) {
-      setStyle(effect.element.firstChild, {bottom:
-        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
-    afterFinishInternal: function(effect) { with(Element) {
-      undoClipping(effect.element); 
-      undoPositioned(effect.element.firstChild);
-      undoPositioned(effect.element);
-      setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
-    }, arguments[1] || {})
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || { })
   );
-}
-  
+};
+
 Effect.SlideUp = function(element) {
-  element = $(element);
-  Element.cleanWhitespace(element);
-  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
-  return new Effect.Scale(element, 0, 
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, window.opera ? 0 : 1,
    Object.extend({ scaleContent: false, 
     scaleX: false, 
     scaleMode: 'box',
     scaleFrom: 100,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
     restoreAfterFinish: true,
-    beforeStartInternal: function(effect) { with(Element) {
-      makePositioned(effect.element);
-      makePositioned(effect.element.firstChild);
-      if(window.opera) setStyle(effect.element, {top: ''});
-      makeClipping(effect.element);
-      show(element); }},  
-    afterUpdateInternal: function(effect) { with(Element) {
-      setStyle(effect.element.firstChild, {bottom:
-        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
-    afterFinishInternal: function(effect) { with(Element) {
-        [hide, undoClipping].call(effect.element); 
-        undoPositioned(effect.element.firstChild);
-        undoPositioned(effect.element);
-        setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
-   }, arguments[1] || {})
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },  
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+    }
+   }, arguments[1] || { })
   );
-}
+};
 
 // Bug in opera makes the TD containing this element expand for a instance after finish 
 Effect.Squish = function(element) {
-  return new Effect.Scale(element, window.opera ? 1 : 0, 
-    { restoreAfterFinish: true,
-      beforeSetup: function(effect) { with(Element) {
-        makeClipping(effect.element); }},  
-      afterFinishInternal: function(effect) { with(Element) {
-        hide(effect.element); 
-        undoClipping(effect.element); }}
+  return new Effect.Scale(element, window.opera ? 1 : 0, { 
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping(); 
+    }
   });
-}
+};
 
 Effect.Grow = function(element) {
   element = $(element);
   var options = Object.extend({
     direction: 'center',
-    moveTransistion: Effect.Transitions.sinoidal,
+    moveTransition: Effect.Transitions.sinoidal,
     scaleTransition: Effect.Transitions.sinoidal,
     opacityTransition: Effect.Transitions.full
-  }, arguments[1] || {});
+  }, arguments[1] || { });
   var oldStyle = {
     top: element.style.top,
     left: element.style.left,
     height: element.style.height,
     width: element.style.width,
-    opacity: Element.getInlineOpacity(element) };
+    opacity: element.getInlineOpacity() };
 
-  var dims = Element.getDimensions(element);    
+  var dims = element.getDimensions();    
   var initialMoveX, initialMoveY;
   var moveX, moveY;
   
@@ -737,49 +794,49 @@ Effect.Grow = function(element) {
       break;
   }
   
-  return new Effect.MoveBy(element, initialMoveY, initialMoveX, { 
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
     duration: 0.01, 
-    beforeSetup: function(effect) { with(Element) {
-      hide(effect.element);
-      makeClipping(effect.element);
-      makePositioned(effect.element);
-    }},
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
     afterFinishInternal: function(effect) {
       new Effect.Parallel(
         [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
-          new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
           new Effect.Scale(effect.element, 100, {
             scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
             sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
         ], Object.extend({
-             beforeSetup: function(effect) { with(Element) {
-               setStyle(effect.effects[0].element, {height: '0px'});
-               show(effect.effects[0].element); }},
-             afterFinishInternal: function(effect) { with(Element) {
-               [undoClipping, undoPositioned].call(effect.effects[0].element); 
-               setStyle(effect.effects[0].element, oldStyle); }}
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show(); 
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
+             }
            }, options)
       )
     }
   });
-}
+};
 
 Effect.Shrink = function(element) {
   element = $(element);
   var options = Object.extend({
     direction: 'center',
-    moveTransistion: Effect.Transitions.sinoidal,
+    moveTransition: Effect.Transitions.sinoidal,
     scaleTransition: Effect.Transitions.sinoidal,
     opacityTransition: Effect.Transitions.none
-  }, arguments[1] || {});
+  }, arguments[1] || { });
   var oldStyle = {
     top: element.style.top,
     left: element.style.left,
     height: element.style.height,
     width: element.style.width,
-    opacity: Element.getInlineOpacity(element) };
+    opacity: element.getInlineOpacity() };
 
-  var dims = Element.getDimensions(element);
+  var dims = element.getDimensions();
   var moveX, moveY;
   
   switch (options.direction) {
@@ -807,29 +864,29 @@ Effect.Shrink = function(element) {
   return new Effect.Parallel(
     [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
       new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
-      new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition })
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
     ], Object.extend({            
-         beforeStartInternal: function(effect) { with(Element) {
-           [makePositioned, makeClipping].call(effect.effects[0].element) }},
-         afterFinishInternal: function(effect) { with(Element) {
-           [hide, undoClipping, undoPositioned].call(effect.effects[0].element);
-           setStyle(effect.effects[0].element, oldStyle); }}
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping(); 
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
        }, options)
   );
-}
+};
 
 Effect.Pulsate = function(element) {
   element = $(element);
-  var options    = arguments[1] || {};
-  var oldOpacity = Element.getInlineOpacity(element);
+  var options    = arguments[1] || { };
+  var oldOpacity = element.getInlineOpacity();
   var transition = options.transition || Effect.Transitions.sinoidal;
-  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
   reverser.bind(transition);
   return new Effect.Opacity(element, 
-    Object.extend(Object.extend({  duration: 3.0, from: 0,
-      afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
     }, options), {transition: reverser}));
-}
+};
 
 Effect.Fold = function(element) {
   element = $(element);
@@ -838,7 +895,7 @@ Effect.Fold = function(element) {
     left: element.style.left,
     width: element.style.width,
     height: element.style.height };
-  Element.makeClipping(element);
+  element.makeClipping();
   return new Effect.Scale(element, 5, Object.extend({   
     scaleContent: false,
     scaleX: false,
@@ -846,9 +903,218 @@ Effect.Fold = function(element) {
     new Effect.Scale(element, 1, { 
       scaleContent: false, 
       scaleY: false,
-      afterFinishInternal: function(effect) { with(Element) {
-        [hide, undoClipping].call(effect.element); 
-        setStyle(effect.element, oldStyle);
-      }} });
-  }}, arguments[1] || {}));
-}
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: { }
+    }, arguments[1] || { });
+    
+    if (!Object.isString(options.style)) this.style = $H(options.style);
+    else {
+      if (options.style.include(':'))
+        this.style = options.style.parseStyle();
+      else {
+        this.element.addClassName(options.style);
+        this.style = $H(this.element.getStyles());
+        this.element.removeClassName(options.style);
+        var css = this.element.getStyles();
+        this.style = this.style.reject(function(style) {
+          return style.value == css[style.key];
+        });
+        options.afterFinishInternal = function(effect) {
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            effect.element.style[transform.style] = '';
+          });
+        }
+      }
+    }
+    this.start(options);
+  },
+  
+  setup: function(){
+    function parseColor(color){
+      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if (property == 'opacity') {
+        value = parseFloat(value);
+        if (Prototype.Browser.IE &amp;&amp; (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if (Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return { 
+        style: property.camelize(), 
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &amp;&amp;
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      )
+    });
+  },
+  update: function(position) {
+    var style = { }, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] = 
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        (transform.originalValue +
+          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
+            (transform.unit === null ? '' : transform.unit);
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create({
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || { };
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      track = $H(track);
+      var data = track.values().first();
+      this.tracks.push($H({
+        ids:     track.keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+        var elements = [$(ids) || $$(ids)].flatten();
+        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+  
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+  var style, styleRules = $H();
+  if (Prototype.Browser.WebKit)
+    style = new Element('div',{style:this}).style;
+  else {
+    String.__parseStyleElement.innerHTML = '&lt;div style=&quot;' + this + '&quot;&gt;&lt;/div&gt;';
+    style = String.__parseStyleElement.childNodes[0].style;
+  }
+  
+  Element.CSS_PROPERTIES.each(function(property){
+    if (style[property]) styleRules.set(property, style[property]); 
+  });
+  
+  if (Prototype.Browser.IE &amp;&amp; this.include('opacity'))
+    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+  return styleRules;
+};
+
+if (document.defaultView &amp;&amp; document.defaultView.getComputedStyle) {
+  Element.getStyles = function(element) {
+    var css = document.defaultView.getComputedStyle($(element), null);
+    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+      styles[property] = css[property];
+      return styles;
+    });
+  };
+} else {
+  Element.getStyles = function(element) {
+    element = $(element);
+    var css = element.currentStyle, styles;
+    styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) {
+      hash.set(property, css[property]);
+      return hash;
+    });
+    if (!styles.opacity) styles.set('opacity', element.getOpacity());
+    return styles;
+  };
+};
+
+Effect.Methods = {
+  morph: function(element, style) {
+    element = $(element);
+    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+    return element;
+  },
+  visualEffect: function(element, effect, options) {
+    element = $(element)
+    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+    new Effect[klass](element, options);
+    return element;
+  },
+  highlight: function(element, options) {
+    element = $(element);
+    new Effect.Highlight(element, options);
+    return element;
+  }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+  'pulsate shake puff squish switchOff dropOut').each(
+  function(effect) { 
+    Effect.Methods[effect] = function(element, options){
+      element = $(element);
+      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+      return element;
+    }
+  }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
+  function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);</diff>
      <filename>public/javascripts/effects.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,102 +1,288 @@
-/*  Prototype JavaScript framework, version 1.4.0
- *  (c) 2005 Sam Stephenson &lt;sam@conio.net&gt;
- *
- *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
- *  against the source tree, available from the Prototype darcs repository.
+/*  Prototype JavaScript framework, version 1.6.0.1
+ *  (c) 2005-2007 Sam Stephenson
  *
  *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
  *
- *  For details, see the Prototype web site: http://prototype.conio.net/
- *
-/*--------------------------------------------------------------------------*/
+ *--------------------------------------------------------------------------*/
 
 var Prototype = {
-  Version: '1.4.0',
-  ScriptFragment: '(?:&lt;script.*?&gt;)((\n|\r|.)*?)(?:&lt;\/script&gt;)',
+  Version: '1.6.0.1',
 
-  emptyFunction: function() {},
-  K: function(x) {return x}
-}
+  Browser: {
+    IE:     !!(window.attachEvent &amp;&amp; !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') &gt; -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') &gt; -1 &amp;&amp; navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &amp;&amp;
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '&lt;script[^&gt;]*&gt;([\\S\\s]*?)&lt;\/script&gt;',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
 
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
 var Class = {
   create: function() {
-    return function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
       this.initialize.apply(this, arguments);
     }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i &lt; properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
   }
-}
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass &amp;&amp; this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push(&quot;toString&quot;, &quot;valueOf&quot;);
+
+    for (var i = 0, length = properties.length; i &lt; length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor &amp;&amp; Object.isFunction(value) &amp;&amp;
+          value.argumentNames().first() == &quot;$super&quot;) {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
 
-var Abstract = new Object();
+var Abstract = { };
 
 Object.extend = function(destination, source) {
-  for (property in source) {
+  for (var property in source)
     destination[property] = source[property];
-  }
   return destination;
-}
+};
 
-Object.inspect = function(object) {
-  try {
-    if (object == undefined) return 'undefined';
-    if (object == null) return 'null';
-    return object.inspect ? object.inspect() : object.toString();
-  } catch (e) {
-    if (e instanceof RangeError) return '...';
-    throw e;
-  }
-}
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : object.toString();
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
 
-Function.prototype.bind = function() {
-  var __method = this, args = $A(arguments), object = args.shift();
-  return function() {
-    return __method.apply(object, args.concat($A(arguments)));
-  }
-}
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object &amp;&amp; object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
 
-Function.prototype.bindAsEventListener = function(object) {
-  var __method = this;
-  return function(event) {
-    return __method.call(object, event || window.event);
+  isElement: function(object) {
+    return object &amp;&amp; object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object &amp;&amp; object.constructor === Array;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == &quot;function&quot;;
+  },
+
+  isString: function(object) {
+    return typeof object == &quot;string&quot;;
+  },
+
+  isNumber: function(object) {
+    return typeof object == &quot;number&quot;;
+  },
+
+  isUndefined: function(object) {
+    return typeof object == &quot;undefined&quot;;
   }
-}
+});
 
-Object.extend(Number.prototype, {
-  toColorPart: function() {
-    var digits = this.toString(16);
-    if (this &lt; 16) return '0' + digits;
-    return digits;
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(&quot;,&quot;).invoke(&quot;strip&quot;);
+    return names.length == 1 &amp;&amp; !names[0] ? [] : names;
   },
 
-  succ: function() {
-    return this + 1;
+  bind: function() {
+    if (arguments.length &lt; 2 &amp;&amp; Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
   },
 
-  times: function(iterator) {
-    $R(0, this, true).each(iterator);
-    return this;
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
   }
 });
 
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '&quot;' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z&quot;';
+};
+
 var Try = {
   these: function() {
     var returnValue;
 
-    for (var i = 0; i &lt; arguments.length; i++) {
+    for (var i = 0, length = arguments.length; i &lt; length; i++) {
       var lambda = arguments[i];
       try {
         returnValue = lambda();
         break;
-      } catch (e) {}
+      } catch (e) { }
     }
 
     return returnValue;
   }
-}
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
 
 /*--------------------------------------------------------------------------*/
 
-var PeriodicalExecuter = Class.create();
-PeriodicalExecuter.prototype = {
+var PeriodicalExecuter = Class.create({
   initialize: function(callback, frequency) {
     this.callback = callback;
     this.frequency = frequency;
@@ -106,40 +292,87 @@ PeriodicalExecuter.prototype = {
   },
 
   registerCallback: function() {
-    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
   },
 
   onTimerEvent: function() {
     if (!this.currentlyExecuting) {
       try {
         this.currentlyExecuting = true;
-        this.callback();
+        this.execute();
       } finally {
         this.currentlyExecuting = false;
       }
     }
   }
-}
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
 
-/*--------------------------------------------------------------------------*/
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length &gt; 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
 
-function $() {
-  var elements = new Array();
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
 
-  for (var i = 0; i &lt; arguments.length; i++) {
-    var element = arguments[i];
-    if (typeof element == 'string')
-      element = document.getElementById(element);
+    return this.gsub(pattern, function(match) {
+      if (--count &lt; 0) return match[0];
+      return replacement(match);
+    });
+  },
 
-    if (arguments.length == 1)
-      return element;
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
 
-    elements.push(element);
-  }
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length &gt; length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
 
-  return elements;
-}
-Object.extend(String.prototype, {
   stripTags: function() {
     return this.replace(/&lt;\/?[^&gt;]+&gt;/gi, '');
   },
@@ -157,28 +390,40 @@ Object.extend(String.prototype, {
   },
 
   evalScripts: function() {
-    return this.extractScripts().map(eval);
+    return this.extractScripts().map(function(script) { return eval(script) });
   },
 
   escapeHTML: function() {
-    var div = document.createElement('div');
-    var text = document.createTextNode(this);
-    div.appendChild(text);
-    return div.innerHTML;
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
   },
 
   unescapeHTML: function() {
-    var div = document.createElement('div');
+    var div = new Element('div');
     div.innerHTML = this.stripTags();
-    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
+    return div.childNodes[0] ? (div.childNodes.length &gt; 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
   },
 
-  toQueryParams: function() {
-    var pairs = this.match(/^\??(.*)$/)[1].split('&amp;');
-    return pairs.inject({}, function(params, pairString) {
-      var pair = pairString.split('=');
-      params[pair[0]] = pair[1];
-      return params;
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&amp;').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length &gt; 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
     });
   },
 
@@ -186,67 +431,204 @@ Object.extend(String.prototype, {
     return this.split('');
   },
 
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count &lt; 1 ? '' : new Array(count + 1).join(this);
+  },
+
   camelize: function() {
-    var oStringList = this.split('-');
-    if (oStringList.length == 1) return oStringList[0];
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
 
-    var camelizedString = this.indexOf('-') == 0
-      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
-      : oStringList[0];
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
 
-    for (var i = 1, len = oStringList.length; i &lt; len; i++) {
-      var s = oStringList[i];
-      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
-    }
+    for (var i = 1; i &lt; len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
 
-    return camelizedString;
+    return camelized;
   },
 
-  inspect: function() {
-    return &quot;'&quot; + this.replace('\\', '\\\\').replace(&quot;'&quot;, '\\\'') + &quot;'&quot;;
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '&quot;' + escapedString.replace(/&quot;/g, '\\&quot;') + '&quot;';
+    return &quot;'&quot; + escapedString.replace(/'/g, '\\\'') + &quot;'&quot;;
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/&quot;[^&quot;\\\n\r]*&quot;/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) &gt; -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d &gt;= 0 &amp;&amp; this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&amp;/g,'&amp;amp;').replace(/&lt;/g,'&amp;lt;').replace(/&gt;/g,'&amp;gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;amp;/g,'&amp;').replace(/&amp;lt;/g,'&lt;').replace(/&amp;gt;/g,'&gt;');
   }
 });
 
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
 String.prototype.parseQuery = String.prototype.toQueryParams;
 
-var $break    = new Object();
-var $continue = new Object();
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    }.bind(this));
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
 
 var Enumerable = {
-  each: function(iterator) {
+  each: function(iterator, context) {
     var index = 0;
+    iterator = iterator.bind(context);
     try {
       this._each(function(value) {
-        try {
-          iterator(value, index++);
-        } catch (e) {
-          if (e != $continue) throw e;
-        }
+        iterator(value, index++);
       });
     } catch (e) {
       if (e != $break) throw e;
     }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) &lt; array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
   },
 
-  all: function(iterator) {
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var result = true;
     this.each(function(value, index) {
-      result = result &amp;&amp; !!(iterator || Prototype.K)(value, index);
+      result = result &amp;&amp; !!iterator(value, index);
       if (!result) throw $break;
     });
     return result;
   },
 
-  any: function(iterator) {
-    var result = true;
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
     this.each(function(value, index) {
-      if (result = !!(iterator || Prototype.K)(value, index))
+      if (result = !!iterator(value, index))
         throw $break;
     });
     return result;
   },
 
-  collect: function(iterator) {
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var results = [];
     this.each(function(value, index) {
       results.push(iterator(value, index));
@@ -254,7 +636,8 @@ var Enumerable = {
     return results;
   },
 
-  detect: function (iterator) {
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
     var result;
     this.each(function(value, index) {
       if (iterator(value, index)) {
@@ -265,7 +648,8 @@ var Enumerable = {
     return result;
   },
 
-  findAll: function(iterator) {
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
     var results = [];
     this.each(function(value, index) {
       if (iterator(value, index))
@@ -274,17 +658,24 @@ var Enumerable = {
     return results;
   },
 
-  grep: function(pattern, iterator) {
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
     this.each(function(value, index) {
-      var stringValue = value.toString();
-      if (stringValue.match(pattern))
-        results.push((iterator || Prototype.K)(value, index));
-    })
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
     return results;
   },
 
   include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
     var found = false;
     this.each(function(value) {
       if (value == object) {
@@ -295,7 +686,16 @@ var Enumerable = {
     return found;
   },
 
-  inject: function(memo, iterator) {
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length &lt; number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
     this.each(function(value, index) {
       memo = iterator(memo, value, index);
     });
@@ -304,35 +704,38 @@ var Enumerable = {
 
   invoke: function(method) {
     var args = $A(arguments).slice(1);
-    return this.collect(function(value) {
+    return this.map(function(value) {
       return value[method].apply(value, args);
     });
   },
 
-  max: function(iterator) {
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var result;
     this.each(function(value, index) {
-      value = (iterator || Prototype.K)(value, index);
-      if (value &gt;= (result || value))
+      value = iterator(value, index);
+      if (result == null || value &gt;= result)
         result = value;
     });
     return result;
   },
 
-  min: function(iterator) {
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var result;
     this.each(function(value, index) {
-      value = (iterator || Prototype.K)(value, index);
-      if (value &lt;= (result || value))
+      value = iterator(value, index);
+      if (result == null || value &lt; result)
         result = value;
     });
     return result;
   },
 
-  partition: function(iterator) {
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var trues = [], falses = [];
     this.each(function(value, index) {
-      ((iterator || Prototype.K)(value, index) ?
+      (iterator(value, index) ?
         trues : falses).push(value);
     });
     return [trues, falses];
@@ -340,13 +743,14 @@ var Enumerable = {
 
   pluck: function(property) {
     var results = [];
-    this.each(function(value, index) {
+    this.each(function(value) {
       results.push(value[property]);
     });
     return results;
   },
 
-  reject: function(iterator) {
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
     var results = [];
     this.each(function(value, index) {
       if (!iterator(value, index))
@@ -355,8 +759,9 @@ var Enumerable = {
     return results;
   },
 
-  sortBy: function(iterator) {
-    return this.collect(function(value, index) {
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
       return {value: value, criteria: iterator(value, index)};
     }).sort(function(left, right) {
       var a = left.criteria, b = right.criteria;
@@ -365,52 +770,67 @@ var Enumerable = {
   },
 
   toArray: function() {
-    return this.collect(Prototype.K);
+    return this.map();
   },
 
   zip: function() {
     var iterator = Prototype.K, args = $A(arguments);
-    if (typeof args.last() == 'function')
+    if (Object.isFunction(args.last()))
       iterator = args.pop();
 
     var collections = [this].concat(args).map($A);
     return this.map(function(value, index) {
-      iterator(value = collections.pluck(index));
-      return value;
+      return iterator(collections.pluck(index));
     });
   },
 
+  size: function() {
+    return this.toArray().length;
+  },
+
   inspect: function() {
     return '#&lt;Enumerable:' + this.toArray().inspect() + '&gt;';
   }
-}
+};
 
 Object.extend(Enumerable, {
   map:     Enumerable.collect,
   find:    Enumerable.detect,
   select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
   member:  Enumerable.include,
-  entries: Enumerable.toArray
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
 });
-var $A = Array.from = function(iterable) {
+function $A(iterable) {
   if (!iterable) return [];
-  if (iterable.toArray) {
-    return iterable.toArray();
-  } else {
-    var results = [];
-    for (var i = 0; i &lt; iterable.length; i++)
-      results.push(iterable[i]);
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  function $A(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) &amp;&amp; iterable == '[object NodeList]') &amp;&amp;
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length, results = new Array(length);
+    while (length--) results[length] = iterable[length];
     return results;
   }
 }
 
+Array.from = $A;
+
 Object.extend(Array.prototype, Enumerable);
 
-Array.prototype._reverse = Array.prototype.reverse;
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
 
 Object.extend(Array.prototype, {
   _each: function(iterator) {
-    for (var i = 0; i &lt; this.length; i++)
+    for (var i = 0, length = this.length; i &lt; length; i++)
       iterator(this[i]);
   },
 
@@ -429,13 +849,13 @@ Object.extend(Array.prototype, {
 
   compact: function() {
     return this.select(function(value) {
-      return value != undefined || value != null;
+      return value != null;
     });
   },
 
   flatten: function() {
     return this.inject([], function(array, value) {
-      return array.concat(value.constructor == Array ?
+      return array.concat(Object.isArray(value) ?
         value.flatten() : [value]);
     });
   },
@@ -447,78 +867,219 @@ Object.extend(Array.prototype, {
     });
   },
 
-  indexOf: function(object) {
-    for (var i = 0; i &lt; this.length; i++)
-      if (this[i] == object) return i;
-    return -1;
-  },
-
   reverse: function(inline) {
     return (inline !== false ? this : this.toArray())._reverse();
   },
 
-  shift: function() {
-    var result = this[0];
-    for (var i = 0; i &lt; this.length - 1; i++)
-      this[i] = this[i + 1];
-    this.length--;
-    return result;
+  reduce: function() {
+    return this.length &gt; 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
   },
 
   inspect: function() {
     return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
   }
 });
-var Hash = {
-  _each: function(iterator) {
-    for (key in this) {
-      var value = this[key];
-      if (typeof value == 'function') continue;
 
-      var pair = [key, value];
-      pair.key = key;
-      pair.value = value;
-      iterator(pair);
-    }
-  },
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i &lt; 0) i = length + i;
+  for (; i &lt; length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i &lt; 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n &lt; 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
 
-  keys: function() {
-    return this.pluck('key');
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i &lt; length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i &lt; length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j &lt; arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
   },
 
-  values: function() {
-    return this.pluck('value');
+  succ: function() {
+    return this + 1;
   },
 
-  merge: function(hash) {
-    return $H(hash).inject($H(this), function(mergedHash, pair) {
-      mergedHash[pair.key] = pair.value;
-      return mergedHash;
-    });
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
   },
 
-  toQueryString: function() {
-    return this.map(function(pair) {
-      return pair.map(encodeURIComponent).join('=');
-    }).join('&amp;');
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
   },
 
-  inspect: function() {
-    return '#&lt;Hash:{' + this.map(function(pair) {
-      return pair.map(Object.inspect).join(': ');
-    }).join(', ') + '}&gt;';
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
   }
-}
+});
 
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
 function $H(object) {
-  var hash = Object.extend({}, object || {});
-  Object.extend(hash, Enumerable);
-  Object.extend(hash, Hash);
-  return hash;
-}
-ObjectRange = Class.create();
-Object.extend(ObjectRange.prototype, Enumerable);
-Object.extend(ObjectRange.prototype, {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match &amp;&amp; match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values &amp;&amp; typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&amp;');
+        }
+        return toQueryPair(key, values);
+      }).join('&amp;');
+    },
+
+    inspect: function() {
+      return '#&lt;Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}&gt;';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
   initialize: function(start, end, exclusive) {
     this.start = start;
     this.end = end;
@@ -527,10 +1088,10 @@ Object.extend(ObjectRange.prototype, {
 
   _each: function(iterator) {
     var value = this.start;
-    do {
+    while (this.include(value)) {
       iterator(value);
       value = value.succ();
-    } while (this.include(value));
+    }
   },
 
   include: function(value) {
@@ -544,19 +1105,19 @@ Object.extend(ObjectRange.prototype, {
 
 var $R = function(start, end, exclusive) {
   return new ObjectRange(start, end, exclusive);
-}
+};
 
 var Ajax = {
   getTransport: function() {
     return Try.these(
+      function() {return new XMLHttpRequest()},
       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
-      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
-      function() {return new XMLHttpRequest()}
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
     ) || false;
   },
 
   activeRequestCount: 0
-}
+};
 
 Ajax.Responders = {
   responders: [],
@@ -565,21 +1126,21 @@ Ajax.Responders = {
     this.responders._each(iterator);
   },
 
-  register: function(responderToAdd) {
-    if (!this.include(responderToAdd))
-      this.responders.push(responderToAdd);
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
   },
 
-  unregister: function(responderToRemove) {
-    this.responders = this.responders.without(responderToRemove);
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
   },
 
   dispatch: function(callback, request, transport, json) {
     this.each(function(responder) {
-      if (responder[callback] &amp;&amp; typeof responder[callback] == 'function') {
+      if (Object.isFunction(responder[callback])) {
         try {
           responder[callback].apply(responder, [request, transport, json]);
-        } catch (e) {}
+        } catch (e) { }
       }
     });
   }
@@ -588,154 +1149,185 @@ Ajax.Responders = {
 Object.extend(Ajax.Responders, Enumerable);
 
 Ajax.Responders.register({
-  onCreate: function() {
-    Ajax.activeRequestCount++;
-  },
-
-  onComplete: function() {
-    Ajax.activeRequestCount--;
-  }
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
 });
 
-Ajax.Base = function() {};
-Ajax.Base.prototype = {
-  setOptions: function(options) {
+Ajax.Base = Class.create({
+  initialize: function(options) {
     this.options = {
       method:       'post',
       asynchronous: true,
-      parameters:   ''
-    }
-    Object.extend(this.options, options || {});
-  },
-
-  responseIsSuccess: function() {
-    return this.transport.status == undefined
-        || this.transport.status == 0
-        || (this.transport.status &gt;= 200 &amp;&amp; this.transport.status &lt; 300);
-  },
-
-  responseIsFailure: function() {
-    return !this.responseIsSuccess();
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
   }
-}
+});
 
-Ajax.Request = Class.create();
-Ajax.Request.Events =
-  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
 
-Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
-  initialize: function(url, options) {
+  initialize: function($super, url, options) {
+    $super(options);
     this.transport = Ajax.getTransport();
-    this.setOptions(options);
     this.request(url);
   },
 
   request: function(url) {
-    var parameters = this.options.parameters || '';
-    if (parameters.length &gt; 0) parameters += '&amp;_=';
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
 
-    try {
-      this.url = url;
-      if (this.options.method == 'get' &amp;&amp; parameters.length &gt; 0)
-        this.url += (this.url.match(/\?/) ? '&amp;' : '?') + parameters;
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&amp;' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&amp;_=';
+    }
 
-      Ajax.Responders.dispatch('onCreate', this, this.transport);
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
 
-      this.transport.open(this.options.method, this.url,
+      this.transport.open(this.method.toUpperCase(), this.url,
         this.options.asynchronous);
 
-      if (this.options.asynchronous) {
-        this.transport.onreadystatechange = this.onStateChange.bind(this);
-        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
-      }
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
 
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
       this.setRequestHeaders();
 
-      var body = this.options.postBody ? this.options.postBody : parameters;
-      this.transport.send(this.options.method == 'post' ? body : null);
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
 
-    } catch (e) {
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous &amp;&amp; this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
       this.dispatchException(e);
     }
   },
 
-  setRequestHeaders: function() {
-    var requestHeaders =
-      ['X-Requested-With', 'XMLHttpRequest',
-       'X-Prototype-Version', Prototype.Version];
-
-    if (this.options.method == 'post') {
-      requestHeaders.push('Content-type',
-        'application/x-www-form-urlencoded');
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState &gt; 1 &amp;&amp; !((readyState == 4) &amp;&amp; this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
 
-      /* Force &quot;Connection: close&quot; for Mozilla browsers to work around
-       * a bug where XMLHttpReqeuest sends an incorrect Content-length
-       * header. See Mozilla Bugzilla #246651.
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force &quot;Connection: close&quot; for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
        */
-      if (this.transport.overrideMimeType)
-        requestHeaders.push('Connection', 'close');
+      if (this.transport.overrideMimeType &amp;&amp;
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] &lt; 2005)
+            headers['Connection'] = 'close';
     }
 
-    if (this.options.requestHeaders)
-      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
-
-    for (var i = 0; i &lt; requestHeaders.length; i += 2)
-      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
-  },
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
 
-  onStateChange: function() {
-    var readyState = this.transport.readyState;
-    if (readyState != 1)
-      this.respondToReadyState(this.transport.readyState);
-  },
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i &lt; length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
 
-  header: function(name) {
-    try {
-      return this.transport.getResponseHeader(name);
-    } catch (e) {}
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
   },
 
-  evalJSON: function() {
-    try {
-      return eval(this.header('X-JSON'));
-    } catch (e) {}
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status &gt;= 200 &amp;&amp; status &lt; 300);
   },
 
-  evalResponse: function() {
+  getStatus: function() {
     try {
-      return eval(this.transport.responseText);
-    } catch (e) {
-      this.dispatchException(e);
-    }
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
   },
 
   respondToReadyState: function(readyState) {
-    var event = Ajax.Request.Events[readyState];
-    var transport = this.transport, json = this.evalJSON();
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
 
-    if (event == 'Complete') {
+    if (state == 'Complete') {
       try {
-        (this.options['on' + this.transport.status]
-         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
-         || Prototype.emptyFunction)(transport, json);
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
       } catch (e) {
         this.dispatchException(e);
       }
 
-      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS &amp;&amp; contentType
+          &amp;&amp; contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
         this.evalResponse();
     }
 
     try {
-      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
-      Ajax.Responders.dispatch('on' + event, this, transport, json);
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
     } catch (e) {
       this.dispatchException(e);
     }
 
-    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
-    if (event == 'Complete')
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
       this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name);
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
   },
 
   dispatchException: function(exception) {
@@ -744,61 +1336,126 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
   }
 });
 
-Ajax.Updater = Class.create();
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
 
-Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
-  initialize: function(container, url, options) {
-    this.containers = {
-      success: container.success ? $(container.success) : $(container),
-      failure: container.failure ? $(container.failure) :
-        (container.success ? null : $(container))
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState &gt; 2 &amp;&amp; !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
     }
 
-    this.transport = Ajax.getTransport();
-    this.setOptions(options);
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
 
-    var onComplete = this.options.onComplete || Prototype.emptyFunction;
-    this.options.onComplete = (function(transport, object) {
-      this.updateContent();
-      onComplete(transport, object);
-    }).bind(this);
+  status:      0,
+  statusText: '',
 
-    this.request(url);
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
   },
 
-  updateContent: function() {
-    var receiver = this.responseIsSuccess() ?
-      this.containers.success : this.containers.failure;
-    var response = this.transport.responseText;
+  getHeader: Ajax.Request.prototype.getHeader,
 
-    if (!this.options.evalScripts)
-      response = response.stripScripts();
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
 
-    if (receiver) {
-      if (this.options.insertion) {
-        new this.options.insertion(receiver, response);
-      } else {
-        Element.update(receiver, response);
-      }
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
     }
+  },
 
-    if (this.responseIsSuccess()) {
-      if (this.onComplete)
-        setTimeout(this.onComplete.bind(this), 10);
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &amp;&amp;
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
     }
   }
 });
 
-Ajax.PeriodicalUpdater = Class.create();
-Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
-  initialize: function(container, url, options) {
-    this.setOptions(options);
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
     this.onComplete = this.options.onComplete;
 
     this.frequency = (this.options.frequency || 2);
     this.decay = (this.options.decay || 1);
 
-    this.updater = {};
+    this.updater = { };
     this.container = container;
     this.url = url;
 
@@ -811,80 +1468,334 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
   },
 
   stop: function() {
-    this.updater.onComplete = undefined;
+    this.updater.options.onComplete = undefined;
     clearTimeout(this.timer);
     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
   },
 
-  updateComplete: function(request) {
+  updateComplete: function(response) {
     if (this.options.decay) {
-      this.decay = (request.responseText == this.lastText ?
+      this.decay = (response.responseText == this.lastText ?
         this.decay * this.options.decay : 1);
 
-      this.lastText = request.responseText;
+      this.lastText = response.responseText;
     }
-    this.timer = setTimeout(this.onTimerEvent.bind(this),
-      this.decay * this.frequency * 1000);
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
   },
 
   onTimerEvent: function() {
     this.updater = new Ajax.Updater(this.container, this.url, this.options);
   }
 });
-document.getElementsByClassName = function(className, parentElement) {
-  var children = ($(parentElement) || document.body).getElementsByTagName('*');
-  return $A(children).inject([], function(elements, child) {
-    if (child.className.match(new RegExp(&quot;(^|\\s)&quot; + className + &quot;(\\s|$)&quot;)))
-      elements.push(child);
+function $(element) {
+  if (arguments.length &gt; 1) {
+    for (var i = 0, elements = [], length = arguments.length; i &lt; length; i++)
+      elements.push($(arguments[i]));
     return elements;
-  });
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i &lt; length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
 }
 
 /*--------------------------------------------------------------------------*/
 
-if (!window.Element) {
-  var Element = new Object();
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
 }
 
-Object.extend(Element, {
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE &amp;&amp; attributes.name) {
+      tagName = '&lt;' + tagName + ' name=&quot;' + attributes.name + '&quot;&gt;';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
   visible: function(element) {
     return $(element).style.display != 'none';
   },
 
-  toggle: function() {
-    for (var i = 0; i &lt; arguments.length; i++) {
-      var element = $(arguments[i]);
-      Element[Element.visible(element) ? 'hide' : 'show'](element);
-    }
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
   },
 
-  hide: function() {
-    for (var i = 0; i &lt; arguments.length; i++) {
-      var element = $(arguments[i]);
-      element.style.display = 'none';
-    }
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
   },
 
-  show: function() {
-    for (var i = 0; i &lt; arguments.length; i++) {
-      var element = $(arguments[i]);
-      element.style.display = '';
-    }
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
   },
 
   remove: function(element) {
     element = $(element);
     element.parentNode.removeChild(element);
+    return element;
   },
 
-  update: function(element, html) {
-    $(element).innerHTML = html.stripScripts();
-    setTimeout(function() {html.evalScripts()}, 10);
+  update: function(element, content) {
+    element = $(element);
+    if (content &amp;&amp; content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
   },
 
-  getHeight: function(element) {
+  replace: function(element, content) {
+    element = $(element);
+    if (content &amp;&amp; content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions &amp;&amp; (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, t, range;
+
+    for (position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      t = Element._insertionTranslations[position];
+
+      if (content &amp;&amp; content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        t.insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      range = element.ownerDocument.createRange();
+      t.initializeRange(element, range);
+      t.insert(element, range.createContextualFragment(content.stripScripts()));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '&lt;' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '&gt;';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).getElementsBySelector(&quot;*&quot;);
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element &amp;&amp; element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element &amp;&amp; element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return expression ? Selector.findElement(ancestors, expression, index) :
+      ancestors[index || 0];
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    var descendants = element.descendants();
+    return expression ? Selector.findElement(descendants, expression, index) :
+      descendants[index || 0];
+  },
+
+  previous: function(element, expression, index) {
     element = $(element);
-    return element.offsetHeight;
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return expression ? Selector.findElement(previousSiblings, expression, index) :
+      previousSiblings[index || 0];
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return expression ? Selector.findElement(nextSiblings, expression, index) :
+      nextSiblings[index || 0];
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
   },
 
   classNames: function(element) {
@@ -893,67 +1804,122 @@ Object.extend(Element, {
 
   hasClassName: function(element, className) {
     if (!(element = $(element))) return;
-    return Element.classNames(element).include(className);
+    var elementClassName = element.className;
+    return (elementClassName.length &gt; 0 &amp;&amp; (elementClassName == className ||
+      new RegExp(&quot;(^|\\s)&quot; + className + &quot;(\\s|$)&quot;).test(elementClassName)));
   },
 
   addClassName: function(element, className) {
     if (!(element = $(element))) return;
-    return Element.classNames(element).add(className);
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
   },
 
   removeClassName: function(element, className) {
     if (!(element = $(element))) return;
-    return Element.classNames(element).remove(className);
+    element.className = element.className.replace(
+      new RegExp(&quot;(^|\\s+)&quot; + className + &quot;(\\s+|$)&quot;), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
   },
 
   // removes whitespace-only text node children
   cleanWhitespace: function(element) {
     element = $(element);
-    for (var i = 0; i &lt; element.childNodes.length; i++) {
-      var node = element.childNodes[i];
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
       if (node.nodeType == 3 &amp;&amp; !/\S/.test(node.nodeValue))
-        Element.remove(node);
+        element.removeChild(node);
+      node = nextNode;
     }
+    return element;
   },
 
   empty: function(element) {
-    return $(element).innerHTML.match(/^\s*$/);
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) &amp; 8) === 8;
+
+    if (element.sourceIndex &amp;&amp; !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) &amp;&amp; ancestor.parentNode);
+      }
+      if (nextAncestor) return (e &gt; a &amp;&amp; e &lt; nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
   },
 
   scrollTo: function(element) {
     element = $(element);
-    var x = element.x ? element.x : element.offsetLeft,
-        y = element.y ? element.y : element.offsetTop;
-    window.scrollTo(x, y);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
   },
 
   getStyle: function(element, style) {
     element = $(element);
-    var value = element.style[style.camelize()];
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
     if (!value) {
-      if (document.defaultView &amp;&amp; document.defaultView.getComputedStyle) {
-        var css = document.defaultView.getComputedStyle(element, null);
-        value = css ? css.getPropertyValue(style) : null;
-      } else if (element.currentStyle) {
-        value = element.currentStyle[style.camelize()];
-      }
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
     }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
 
-    if (window.opera &amp;&amp; ['left', 'top', 'right', 'bottom'].include(style))
-      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
 
-    return value == 'auto' ? null : value;
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
   },
 
-  setStyle: function(element, style) {
+  setOpacity: function(element, value) {
     element = $(element);
-    for (name in style)
-      element.style[name.camelize()] = style[name];
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value &lt; 0.00001) ? 0 : value;
+    return element;
   },
 
   getDimensions: function(element) {
     element = $(element);
-    if (Element.getStyle(element, 'display') != 'none')
+    var display = $(element).getStyle('display');
+    if (display != 'none' &amp;&amp; display != null) // Safari bug
       return {width: element.offsetWidth, height: element.offsetHeight};
 
     // All *Width and *Height properties give 0 on elements with display none,
@@ -961,12 +1927,13 @@ Object.extend(Element, {
     var els = element.style;
     var originalVisibility = els.visibility;
     var originalPosition = els.position;
+    var originalDisplay = els.display;
     els.visibility = 'hidden';
     els.position = 'absolute';
-    els.display = '';
+    els.display = 'block';
     var originalWidth = element.clientWidth;
     var originalHeight = element.clientHeight;
-    els.display = 'none';
+    els.display = originalDisplay;
     els.position = originalPosition;
     els.visibility = originalVisibility;
     return {width: originalWidth, height: originalHeight};
@@ -985,6 +1952,7 @@ Object.extend(Element, {
         element.style.left = 0;
       }
     }
+    return element;
   },
 
   undoPositioned: function(element) {
@@ -997,388 +1965,1702 @@ Object.extend(Element, {
         element.style.bottom =
         element.style.right = '';
     }
+    return element;
   },
 
   makeClipping: function(element) {
     element = $(element);
-    if (element._overflow) return;
-    element._overflow = element.style.overflow;
-    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
       element.style.overflow = 'hidden';
+    return element;
   },
 
   undoClipping: function(element) {
     element = $(element);
-    if (element._overflow) return;
-    element.style.overflow = element._overflow;
-    element._overflow = undefined;
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) &amp;&amp; element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &amp;&amp;
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
   }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
 });
 
-var Toggle = new Object();
-Toggle.display = Element.toggle;
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
 
-/*--------------------------------------------------------------------------*/
 
-Abstract.Insertion = function(adjacency) {
-  this.adjacency = adjacency;
+if (!document.createRange || Prototype.Browser.Opera) {
+  Element.Methods.insert = function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions &amp;&amp; (insertions.toElement || insertions.toHTML)))
+          insertions = { bottom: insertions };
+
+    var t = Element._insertionTranslations, content, position, pos, tagName;
+
+    for (position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      pos      = t[position];
+
+      if (content &amp;&amp; content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        pos.insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      if (t.tags[tagName]) {
+        var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+        if (position == 'top' || position == 'after') fragments.reverse();
+        fragments.each(pos.insert.curry(element));
+      }
+      else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  };
 }
 
-Abstract.Insertion.prototype = {
-  initialize: function(element, content) {
-    this.element = $(element);
-    this.content = content.stripScripts();
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
 
-    if (this.adjacency &amp;&amp; this.element.insertAdjacentHTML) {
-      try {
-        this.element.insertAdjacentHTML(this.adjacency, this.content);
-      } catch (e) {
-        if (this.element.tagName.toLowerCase() == 'tbody') {
-          this.insertContent(this.contentFromAnonymousTable());
-        } else {
-          throw e;
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position != 'static') return proceed(element);
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value &amp;&amp; element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') &amp;&amp; (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle &amp;&amp; !currentStyle.hasLayout) ||
+      (!currentStyle &amp;&amp; element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value &lt; 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : &quot;&quot;;
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
         }
       }
-    } else {
-      this.range = this.element.ownerDocument.createRange();
-      if (this.initializeRange) this.initializeRange();
-      this.insertContent([this.range.createContextualFragment(this.content)]);
     }
+  };
 
-    setTimeout(function() {content.evalScripts()}, 10);
+  Element._attributeTranslations.write = {
+    names: Object.clone(Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko &amp;&amp; /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value &lt; 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value &lt; 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' &amp;&amp; element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content &amp;&amp; content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if (document.createElement('div').outerHTML) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content &amp;&amp; content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  div.innerHTML = t[0] + html + t[1];
+  t[2].times(function() { div = div.firstChild });
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: {
+    adjacency: 'beforeBegin',
+    insert: function(element, node) {
+      element.parentNode.insertBefore(node, element);
+    },
+    initializeRange: function(element, range) {
+      range.setStartBefore(element);
+    }
+  },
+  top: {
+    adjacency: 'afterBegin',
+    insert: function(element, node) {
+      element.insertBefore(node, element.firstChild);
+    },
+    initializeRange: function(element, range) {
+      range.selectNodeContents(element);
+      range.collapse(true);
+    }
+  },
+  bottom: {
+    adjacency: 'beforeEnd',
+    insert: function(element, node) {
+      element.appendChild(node);
+    }
+  },
+  after: {
+    adjacency: 'afterEnd',
+    insert: function(element, node) {
+      element.parentNode.insertBefore(node, element.nextSibling);
+    },
+    initializeRange: function(element, range) {
+      range.setStartAfter(element);
+    }
   },
+  tags: {
+    TABLE:  ['&lt;table&gt;',                '&lt;/table&gt;',                   1],
+    TBODY:  ['&lt;table&gt;&lt;tbody&gt;',         '&lt;/tbody&gt;&lt;/table&gt;',           2],
+    TR:     ['&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;',     '&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;',      3],
+    TD:     ['&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;', '&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;', 4],
+    SELECT: ['&lt;select&gt;',               '&lt;/select&gt;',                  1]
+  }
+};
 
-  contentFromAnonymousTable: function() {
-    var div = document.createElement('div');
-    div.innerHTML = '&lt;table&gt;&lt;tbody&gt;' + this.content + '&lt;/tbody&gt;&lt;/table&gt;';
-    return $A(div.childNodes[0].childNodes[0].childNodes);
+(function() {
+  this.bottom.initializeRange = this.top.initializeRange;
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node &amp;&amp; node.specified;
   }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &amp;&amp;
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
 }
 
-var Insertion = new Object();
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
 
-Insertion.Before = Class.create();
-Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
-  initializeRange: function() {
-    this.range.setStartBefore(this.element);
-  },
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) &amp;&amp; !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
 
-  insertContent: function(fragments) {
-    fragments.each((function(fragment) {
-      this.element.parentNode.insertBefore(fragment, this.element);
-    }).bind(this));
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      &quot;FORM&quot;:     Object.clone(Form.Methods),
+      &quot;INPUT&quot;:    Object.clone(Form.Element.Methods),
+      &quot;SELECT&quot;:   Object.clone(Form.Element.Methods),
+      &quot;TEXTAREA&quot;: Object.clone(Form.Element.Methods)
+    });
   }
-});
 
-Insertion.Top = Class.create();
-Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
-  initializeRange: function() {
-    this.range.selectNodeContents(this.element);
-    this.range.collapse(true);
-  },
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
 
-  insertContent: function(fragments) {
-    fragments.reverse(false).each((function(fragment) {
-      this.element.insertBefore(fragment, this.element.firstChild);
-    }).bind(this));
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
   }
-});
 
-Insertion.Bottom = Class.create();
-Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
-  initializeRange: function() {
-    this.range.selectNodeContents(this.element);
-    this.range.collapse(this.element);
-  },
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
 
-  insertContent: function(fragments) {
-    fragments.each((function(fragment) {
-      this.element.appendChild(fragment);
-    }).bind(this));
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
   }
-});
 
-Insertion.After = Class.create();
-Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
-  initializeRange: function() {
-    this.range.setStartAfter(this.element);
-  },
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      &quot;OPTGROUP&quot;: &quot;OptGroup&quot;, &quot;TEXTAREA&quot;: &quot;TextArea&quot;, &quot;P&quot;: &quot;Paragraph&quot;,
+      &quot;FIELDSET&quot;: &quot;FieldSet&quot;, &quot;UL&quot;: &quot;UList&quot;, &quot;OL&quot;: &quot;OList&quot;, &quot;DL&quot;: &quot;DList&quot;,
+      &quot;DIR&quot;: &quot;Directory&quot;, &quot;H1&quot;: &quot;Heading&quot;, &quot;H2&quot;: &quot;Heading&quot;, &quot;H3&quot;: &quot;Heading&quot;,
+      &quot;H4&quot;: &quot;Heading&quot;, &quot;H5&quot;: &quot;Heading&quot;, &quot;H6&quot;: &quot;Heading&quot;, &quot;Q&quot;: &quot;Quote&quot;,
+      &quot;INS&quot;: &quot;Mod&quot;, &quot;DEL&quot;: &quot;Mod&quot;, &quot;A&quot;: &quot;Anchor&quot;, &quot;IMG&quot;: &quot;Image&quot;, &quot;CAPTION&quot;:
+      &quot;TableCaption&quot;, &quot;COL&quot;: &quot;TableCol&quot;, &quot;COLGROUP&quot;: &quot;TableCol&quot;, &quot;THEAD&quot;:
+      &quot;TableSection&quot;, &quot;TFOOT&quot;: &quot;TableSection&quot;, &quot;TBODY&quot;: &quot;TableSection&quot;, &quot;TR&quot;:
+      &quot;TableRow&quot;, &quot;TH&quot;: &quot;TableCell&quot;, &quot;TD&quot;: &quot;TableCell&quot;, &quot;FRAMESET&quot;:
+      &quot;FrameSet&quot;, &quot;IFRAME&quot;: &quot;IFrame&quot;
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
 
-  insertContent: function(fragments) {
-    fragments.each((function(fragment) {
-      this.element.parentNode.insertBefore(fragment,
-        this.element.nextSibling);
-    }).bind(this));
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
   }
-});
 
-/*--------------------------------------------------------------------------*/
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
 
-Element.ClassNames = Class.create();
-Element.ClassNames.prototype = {
-  initialize: function(element) {
-    this.element = $(element);
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit &amp;&amp; !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
   },
 
-  _each: function(iterator) {
-    this.element.className.split(/\s+/).select(function(name) {
-      return name.length &gt; 0;
-    })._each(iterator);
+  getWidth: function() {
+    return this.getDimensions().width;
   },
 
-  set: function(className) {
-    this.element.className = className;
+  getHeight: function() {
+    return this.getDimensions().height;
   },
 
-  add: function(classNameToAdd) {
-    if (this.include(classNameToAdd)) return;
-    this.set(this.toArray().concat(classNameToAdd).join(' '));
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum&#8217;s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
   },
 
-  remove: function(classNameToRemove) {
-    if (!this.include(classNameToRemove)) return;
-    this.set(this.select(function(className) {
-      return className != classNameToRemove;
-    }).join(' '));
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &amp;&amp;
+     (e.include(&quot;-of-type&quot;) || e.include(&quot;:empty&quot;)))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the &quot;checked&quot; property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = [&quot;this.matcher = function(root) {&quot;,
+                    &quot;var r = root, h = Selector.handlers, c = false, n;&quot;];
+
+    while (e &amp;&amp; le != e &amp;&amp; (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push(&quot;return h.unique(n);\n}&quot;);
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e &amp;&amp; le != e &amp;&amp; (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e &amp;&amp; le !== e &amp;&amp; (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
   },
 
   toString: function() {
-    return this.toArray().join(' ');
+    return this.expression;
+  },
+
+  inspect: function() {
+    return &quot;#&lt;Selector:&quot; + this.expression.inspect() + &quot;&gt;&quot;;
   }
-}
+});
 
-Object.extend(Element.ClassNames.prototype, Enumerable);
-var Field = {
-  clear: function() {
-    for (var i = 0; i &lt; arguments.length; i++)
-      $(arguments[i]).value = '';
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   &quot;//*&quot;,
+    child:        &quot;/*&quot;,
+    adjacent:     &quot;/following-sibling::*[1]&quot;,
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return &quot;[local-name()='&quot; + m[1].toLowerCase() +
+             &quot;' or local-name()='&quot; + m[1].toUpperCase() + &quot;']&quot;;
+    },
+    className:    &quot;[contains(concat(' ', @class, ' '), ' #{1} ')]&quot;,
+    id:           &quot;[@id='#{1}']&quot;,
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template(&quot;[@#{1}]&quot;).evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  &quot;[@#{1}='#{3}']&quot;,
+      '!=': &quot;[@#{1}!='#{3}']&quot;,
+      '^=': &quot;[starts-with(@#{1}, '#{3}')]&quot;,
+      '$=': &quot;[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']&quot;,
+      '*=': &quot;[contains(@#{1}, '#{3}')]&quot;,
+      '~=': &quot;[contains(concat(' ', @#{1}, ' '), ' #{3} ')]&quot;,
+      '|=': &quot;[contains(concat('-', @#{1}, '-'), '-#{3}-')]&quot;
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       &quot;[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]&quot;,
+      'checked':     &quot;[@checked]&quot;,
+      'disabled':    &quot;[@disabled]&quot;,
+      'enabled':     &quot;[not(@disabled)]&quot;,
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e &amp;&amp; le != e &amp;&amp; (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push(&quot;(&quot; + v.substring(1, v.length - 1) + &quot;)&quot;);
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return &quot;[not(&quot; + exclusion.join(&quot; and &quot;) + &quot;)]&quot;;
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth(&quot;(count(./preceding-sibling::*) + 1) &quot;, m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth(&quot;(count(./following-sibling::*) + 1) &quot;, m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth(&quot;position() &quot;, m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth(&quot;(last() + 1 - position()) &quot;, m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = &quot;1&quot;; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = &quot;1&quot;; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + &quot;= &quot; + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == &quot;-&quot;) mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = &quot;[((#{fragment} - #{b}) mod #{a} = 0) and &quot; +
+          &quot;((#{fragment} - #{b}) div #{a} &gt;= 0)]&quot;;
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
   },
 
-  focus: function(element) {
-    $(element).focus();
+  criteria: {
+    tagName:      'n = h.tagName(n, r, &quot;#{1}&quot;, c);   c = false;',
+    className:    'n = h.className(n, r, &quot;#{1}&quot;, c); c = false;',
+    id:           'n = h.id(n, r, &quot;#{1}&quot;, c);        c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, &quot;#{1}&quot;); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, &quot;#{1}&quot;, &quot;#{3}&quot;, &quot;#{2}&quot;); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/&quot;/g, '\\&quot;');
+      return new Template('n = h.pseudo(n, &quot;#{1}&quot;, &quot;#{6}&quot;, r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = &quot;descendant&quot;;',
+    child:        'c = &quot;child&quot;;',
+    adjacent:     'c = &quot;adjacent&quot;;',
+    laterSibling: 'c = &quot;laterSibling&quot;;'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*&gt;\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['&quot;])([^\4]*?)\4|([^'&quot;][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return Selector.operators[matches[2]](nodeValue, matches[3]);
+    }
   },
 
-  present: function() {
-    for (var i = 0; i &lt; arguments.length; i++)
-      if ($(arguments[i]).value == '') return false;
-    return true;
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // &quot;ofType&quot; flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._counted = true;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i &gt;= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 &amp;&amp; (!ofType || node._counted)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 &amp;&amp; (!ofType || node._counted)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i &lt; l; i++)
+        if (!(n = nodes[i])._counted) {
+          n._counted = true;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 &amp;&amp; child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      tagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == &quot;descendant&quot;) {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == &quot;*&quot;) return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() == tagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes &amp;&amp; root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode &amp;&amp; Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes &amp;&amp; combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr) {
+      if (!nodes) nodes = root.getElementsByTagName(&quot;*&quot;);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator) {
+      if (!nodes) nodes = root.getElementsByTagName(&quot;*&quot;);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes &amp;&amp; combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName(&quot;*&quot;);
+      return Selector.pseudos[name](nodes, value, root);
+    }
   },
 
-  select: function(element) {
-    $(element).select();
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) &amp;&amp; !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, &quot;1&quot;, root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, &quot;1&quot;, root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b &gt; 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a &amp;&amp; (i - b) / a &gt;= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._counted) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == &quot;-&quot;) m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j &lt; l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild &amp;&amp; !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._counted) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
   },
 
-  activate: function(element) {
-    element = $(element);
-    element.focus();
-    if (element.select)
-      element.select();
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = new Selector(expression).findElements(), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._counted) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    var exprs = expressions.join(',');
+    expressions = [];
+    exprs.scan(/(([\w#:.~&gt;+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i &lt; l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l &gt; 1) ? h.unique(results) : results;
   }
-}
+});
 
-/*--------------------------------------------------------------------------*/
+if (Prototype.Browser.IE) {
+  // IE returns comment nodes on getElementsByTagName(&quot;*&quot;).
+  // Filter them out.
+  Selector.handlers.concat = function(a, b) {
+    for (var i = 0, node; node = b[i]; i++)
+      if (node.tagName !== &quot;!&quot;) a.push(node);
+    return a;
+  };
+}
 
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
 var Form = {
-  serialize: function(form) {
-    var elements = Form.getElements($(form));
-    var queryComponents = new Array();
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled &amp;&amp; element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null &amp;&amp; (element.type != 'submit' || (!submitted &amp;&amp;
+            submit !== false &amp;&amp; (!submit || key == submit) &amp;&amp; (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
 
-    for (var i = 0; i &lt; elements.length; i++) {
-      var queryComponent = Form.Element.serialize(elements[i]);
-      if (queryComponent)
-        queryComponents.push(queryComponent);
-    }
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
 
-    return queryComponents.join('&amp;');
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
   },
 
   getElements: function(form) {
-    form = $(form);
-    var elements = new Array();
-
-    for (tagName in Form.Element.Serializers) {
-      var tagElements = form.getElementsByTagName(tagName);
-      for (var j = 0; j &lt; tagElements.length; j++)
-        elements.push(tagElements[j]);
-    }
-    return elements;
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
   },
 
   getInputs: function(form, typeName, name) {
     form = $(form);
     var inputs = form.getElementsByTagName('input');
 
-    if (!typeName &amp;&amp; !name)
-      return inputs;
+    if (!typeName &amp;&amp; !name) return $A(inputs).map(Element.extend);
 
-    var matchingInputs = new Array();
-    for (var i = 0; i &lt; inputs.length; i++) {
+    for (var i = 0, matchingInputs = [], length = inputs.length; i &lt; length; i++) {
       var input = inputs[i];
-      if ((typeName &amp;&amp; input.type != typeName) ||
-          (name &amp;&amp; input.name != name))
+      if ((typeName &amp;&amp; input.type != typeName) || (name &amp;&amp; input.name != name))
         continue;
-      matchingInputs.push(input);
+      matchingInputs.push(Element.extend(input));
     }
 
     return matchingInputs;
   },
 
   disable: function(form) {
-    var elements = Form.getElements(form);
-    for (var i = 0; i &lt; elements.length; i++) {
-      var element = elements[i];
-      element.blur();
-      element.disabled = 'true';
-    }
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
   },
 
   enable: function(form) {
-    var elements = Form.getElements(form);
-    for (var i = 0; i &lt; elements.length; i++) {
-      var element = elements[i];
-      element.disabled = '';
-    }
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
   },
 
   findFirstElement: function(form) {
-    return Form.getElements(form).find(function(element) {
-      return element.type != 'hidden' &amp;&amp; !element.disabled &amp;&amp;
-        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type &amp;&amp; !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') &amp;&amp; element.tabIndex &gt;= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
     });
   },
 
   focusFirstElement: function(form) {
-    Field.activate(Form.findFirstElement(form));
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
   },
 
-  reset: function(form) {
-    $(form).reset();
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') &amp;&amp; !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
   }
-}
+};
+
+/*--------------------------------------------------------------------------*/
 
 Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
   serialize: function(element) {
     element = $(element);
+    if (!element.disabled &amp;&amp; element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
     var method = element.tagName.toLowerCase();
-    var parameter = Form.Element.Serializers[method](element);
+    return Form.Element.Serializers[method](element);
+  },
 
-    if (parameter) {
-      var key = encodeURIComponent(parameter[0]);
-      if (key.length == 0) return;
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
 
-      if (parameter[1].constructor != Array)
-        parameter[1] = [parameter[1]];
+  present: function(element) {
+    return $(element).value != '';
+  },
 
-      return parameter[1].map(function(value) {
-        return key + '=' + encodeURIComponent(value);
-      }).join('&amp;');
-    }
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select &amp;&amp; (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
   },
 
-  getValue: function(element) {
+  disable: function(element) {
     element = $(element);
-    var method = element.tagName.toLowerCase();
-    var parameter = Form.Element.Serializers[method](element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
 
-    if (parameter)
-      return parameter[1];
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
   }
-}
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
 
 Form.Element.Serializers = {
-  input: function(element) {
+  input: function(element, value) {
     switch (element.type.toLowerCase()) {
-      case 'submit':
-      case 'hidden':
-      case 'password':
-      case 'text':
-        return Form.Element.Serializers.textarea(element);
       case 'checkbox':
       case 'radio':
-        return Form.Element.Serializers.inputSelector(element);
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
     }
-    return false;
   },
 
-  inputSelector: function(element) {
-    if (element.checked)
-      return [element.name, element.value];
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
   },
 
-  textarea: function(element) {
-    return [element.name, element.value];
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
   },
 
-  select: function(element) {
-    return Form.Element.Serializers[element.type == 'select-one' ?
-      'selectOne' : 'selectMany'](element);
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i &lt; length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
   },
 
   selectOne: function(element) {
-    var value = '', opt, index = element.selectedIndex;
-    if (index &gt;= 0) {
-      opt = element.options[index];
-      value = opt.value;
-      if (!value &amp;&amp; !('value' in opt))
-        value = opt.text;
-    }
-    return [element.name, value];
+    var index = element.selectedIndex;
+    return index &gt;= 0 ? this.optionValue(element.options[index]) : null;
   },
 
   selectMany: function(element) {
-    var value = new Array();
-    for (var i = 0; i &lt; element.length; i++) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i &lt; length; i++) {
       var opt = element.options[i];
-      if (opt.selected) {
-        var optValue = opt.value;
-        if (!optValue &amp;&amp; !('value' in opt))
-          optValue = opt.text;
-        value.push(optValue);
-      }
+      if (opt.selected) values.push(this.optionValue(opt));
     }
-    return [element.name, value];
-  }
-}
-
-/*--------------------------------------------------------------------------*/
+    return values;
+  },
 
-var $F = Form.Element.getValue;
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
 
 /*--------------------------------------------------------------------------*/
 
-Abstract.TimedObserver = function() {}
-Abstract.TimedObserver.prototype = {
-  initialize: function(element, frequency, callback) {
-    this.frequency = frequency;
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
     this.element   = $(element);
-    this.callback  = callback;
-
     this.lastValue = this.getValue();
-    this.registerCallback();
   },
 
-  registerCallback: function() {
-    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
-  },
-
-  onTimerEvent: function() {
+  execute: function() {
     var value = this.getValue();
-    if (this.lastValue != value) {
+    if (Object.isString(this.lastValue) &amp;&amp; Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
       this.callback(this.element, value);
       this.lastValue = value;
     }
   }
-}
+});
 
-Form.Element.Observer = Class.create();
-Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
   getValue: function() {
     return Form.Element.getValue(this.element);
   }
 });
 
-Form.Observer = Class.create();
-Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+Form.Observer = Class.create(Abstract.TimedObserver, {
   getValue: function() {
     return Form.serialize(this.element);
   }
@@ -1386,8 +3668,7 @@ Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
 
 /*--------------------------------------------------------------------------*/
 
-Abstract.EventObserver = function() {}
-Abstract.EventObserver.prototype = {
+Abstract.EventObserver = Class.create({
   initialize: function(element, callback) {
     this.element  = $(element);
     this.callback = callback;
@@ -1408,9 +3689,7 @@ Abstract.EventObserver.prototype = {
   },
 
   registerFormCallbacks: function() {
-    var elements = Form.getElements(this.element);
-    for (var i = 0; i &lt; elements.length; i++)
-      this.registerCallback(elements[i]);
+    Form.getElements(this.element).each(this.registerCallback, this);
   },
 
   registerCallback: function(element) {
@@ -1420,34 +3699,26 @@ Abstract.EventObserver.prototype = {
         case 'radio':
           Event.observe(element, 'click', this.onElementEvent.bind(this));
           break;
-        case 'password':
-        case 'text':
-        case 'textarea':
-        case 'select-one':
-        case 'select-multiple':
+        default:
           Event.observe(element, 'change', this.onElementEvent.bind(this));
           break;
       }
     }
   }
-}
+});
 
-Form.Element.EventObserver = Class.create();
-Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
   getValue: function() {
     return Form.Element.getValue(this.element);
   }
 });
 
-Form.EventObserver = Class.create();
-Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+Form.EventObserver = Class.create(Abstract.EventObserver, {
   getValue: function() {
     return Form.serialize(this.element);
   }
 });
-if (!window.Event) {
-  var Event = new Object();
-}
+if (!window.Event) var Event = { };
 
 Object.extend(Event, {
   KEY_BACKSPACE: 8,
@@ -1459,99 +3730,341 @@ Object.extend(Event, {
   KEY_RIGHT:    39,
   KEY_DOWN:     40,
   KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
 
-  element: function(event) {
-    return event.target || event.srcElement;
-  },
-
-  isLeftClick: function(event) {
-    return (((event.which) &amp;&amp; (event.which == 1)) ||
-            ((event.button) &amp;&amp; (event.button == 1)));
-  },
-
-  pointerX: function(event) {
-    return event.pageX || (event.clientX +
-      (document.documentElement.scrollLeft || document.body.scrollLeft));
-  },
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 &amp;&amp; !event.metaKey;
+        case 1: return event.which == 1 &amp;&amp; event.metaKey;
+        default: return false;
+      }
+    };
 
-  pointerY: function(event) {
-    return event.pageY || (event.clientY +
-      (document.documentElement.scrollTop || document.body.scrollTop));
-  },
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
 
-  stop: function(event) {
-    if (event.preventDefault) {
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
       event.preventDefault();
       event.stopPropagation();
-    } else {
-      event.returnValue = false;
-      event.cancelBubble = true;
+      event.stopped = true;
     }
-  },
+  };
+})();
 
-  // find the first node with the given tagName, starting from the
-  // node the event was triggered on; traverses the DOM upwards
-  findElement: function(event, tagName) {
-    var element = Event.element(event);
-    while (element.parentNode &amp;&amp; (!element.tagName ||
-        (element.tagName.toUpperCase() != tagName.toUpperCase())))
-      element = element.parentNode;
-    return element;
-  },
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return &quot;[object Event]&quot; }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent(&quot;HTMLEvents&quot;).__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._eventID) return element._eventID;
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._eventID = ++arguments.callee.id;
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName &amp;&amp; eventName.include(':')) return &quot;dataavailable&quot;;
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck(&quot;handler&quot;).include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName &amp;&amp; event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event)
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent(&quot;onunload&quot;, destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent(&quot;on&quot; + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler &amp;&amp; eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent(&quot;on&quot; + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
 
-  observers: false,
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document &amp;&amp; document.createEvent &amp;&amp; !element.dispatchEvent)
+        element = document.documentElement;
+
+      if (document.createEvent) {
+        var event = document.createEvent(&quot;HTMLEvents&quot;);
+        event.initEvent(&quot;dataavailable&quot;, true, true);
+      } else {
+        var event = document.createEventObject();
+        event.eventType = &quot;ondataavailable&quot;;
+      }
 
-  _observeAndCache: function(element, name, observer, useCapture) {
-    if (!this.observers) this.observers = [];
-    if (element.addEventListener) {
-      this.observers.push([element, name, observer, useCapture]);
-      element.addEventListener(name, observer, useCapture);
-    } else if (element.attachEvent) {
-      this.observers.push([element, name, observer, useCapture]);
-      element.attachEvent('on' + name, observer);
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
     }
-  },
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize()
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer, fired = false;
+
+  function fireContentLoadedEvent() {
+    if (fired) return;
+    if (timer) window.clearInterval(timer);
+    document.fire(&quot;dom:loaded&quot;);
+    fired = true;
+  }
 
-  unloadCache: function() {
-    if (!Event.observers) return;
-    for (var i = 0; i &lt; Event.observers.length; i++) {
-      Event.stopObserving.apply(this, Event.observers[i]);
-      Event.observers[i][0] = null;
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, &quot;load&quot;, fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener(&quot;DOMContentLoaded&quot;,
+        fireContentLoadedEvent, false);
     }
-    Event.observers = false;
-  },
 
-  observe: function(element, name, observer, useCapture) {
-    var element = $(element);
-    useCapture = useCapture || false;
+  } else {
+    document.write(&quot;&lt;script id=__onDOMContentLoaded defer src=//:&gt;&lt;\/script&gt;&quot;);
+    $(&quot;__onDOMContentLoaded&quot;).onreadystatechange = function() {
+      if (this.readyState == &quot;complete&quot;) {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
 
-    if (name == 'keypress' &amp;&amp;
-        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
-        || element.attachEvent))
-      name = 'keydown';
+var Toggle = { display: Element.toggle };
 
-    this._observeAndCache(element, name, observer, useCapture);
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
   },
 
-  stopObserving: function(element, name, observer, useCapture) {
-    var element = $(element);
-    useCapture = useCapture || false;
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
 
-    if (name == 'keypress' &amp;&amp;
-        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
-        || element.detachEvent))
-      name = 'keydown';
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
 
-    if (element.removeEventListener) {
-      element.removeEventListener(name, observer, useCapture);
-    } else if (element.detachEvent) {
-      element.detachEvent('on' + name, observer);
-    }
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
   }
-});
+};
+
+var $continue = new Error('&quot;throw $continue&quot; is deprecated, use &quot;return&quot; instead');
 
-/* prevent memory leaks in IE */
-Event.observe(window, 'unload', Event.unloadCache, false);
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
 var Position = {
   // set to true if needed, warning: firefox performance problems
   // NOT neeeded for page scrolling, only if draggable contained in
@@ -1571,58 +4084,13 @@ var Position = {
                 || 0;
   },
 
-  realOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.scrollTop  || 0;
-      valueL += element.scrollLeft || 0;
-      element = element.parentNode;
-    } while (element);
-    return [valueL, valueT];
-  },
-
-  cumulativeOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-      element = element.offsetParent;
-    } while (element);
-    return [valueL, valueT];
-  },
-
-  positionedOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-      element = element.offsetParent;
-      if (element) {
-        p = Element.getStyle(element, 'position');
-        if (p == 'relative' || p == 'absolute') break;
-      }
-    } while (element);
-    return [valueL, valueT];
-  },
-
-  offsetParent: function(element) {
-    if (element.offsetParent) return element.offsetParent;
-    if (element == document.body) return element;
-
-    while ((element = element.parentNode) &amp;&amp; element != document.body)
-      if (Element.getStyle(element, 'position') != 'static')
-        return element;
-
-    return document.body;
-  },
-
   // caches x/y coordinate pair to use with overlap
   within: function(element, x, y) {
     if (this.includeScrollOffsets)
       return this.withinIncludingScrolloffsets(element, x, y);
     this.xcomp = x;
     this.ycomp = y;
-    this.offset = this.cumulativeOffset(element);
+    this.offset = Element.cumulativeOffset(element);
 
     return (y &gt;= this.offset[1] &amp;&amp;
             y &lt;  this.offset[1] + element.offsetHeight &amp;&amp;
@@ -1631,11 +4099,11 @@ var Position = {
   },
 
   withinIncludingScrolloffsets: function(element, x, y) {
-    var offsetcache = this.realOffset(element);
+    var offsetcache = Element.cumulativeScrollOffset(element);
 
     this.xcomp = x + offsetcache[0] - this.deltaX;
     this.ycomp = y + offsetcache[1] - this.deltaY;
-    this.offset = this.cumulativeOffset(element);
+    this.offset = Element.cumulativeOffset(element);
 
     return (this.ycomp &gt;= this.offset[1] &amp;&amp;
             this.ycomp &lt;  this.offset[1] + element.offsetHeight &amp;&amp;
@@ -1654,132 +4122,104 @@ var Position = {
         element.offsetWidth;
   },
 
-  clone: function(source, target) {
-    source = $(source);
-    target = $(target);
-    target.style.position = 'absolute';
-    var offsets = this.cumulativeOffset(source);
-    target.style.top    = offsets[1] + 'px';
-    target.style.left   = offsets[0] + 'px';
-    target.style.width  = source.offsetWidth + 'px';
-    target.style.height = source.offsetHeight + 'px';
-  },
+  // Deprecation layer -- use newer Element methods now (1.5.2).
 
-  page: function(forElement) {
-    var valueT = 0, valueL = 0;
+  cumulativeOffset: Element.Methods.cumulativeOffset,
 
-    var element = forElement;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
+  positionedOffset: Element.Methods.positionedOffset,
 
-      // Safari fix
-      if (element.offsetParent==document.body)
-        if (Element.getStyle(element,'position')=='absolute') break;
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
 
-    } while (element = element.offsetParent);
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
 
-    element = forElement;
-    do {
-      valueT -= element.scrollTop  || 0;
-      valueL -= element.scrollLeft || 0;
-    } while (element = element.parentNode);
+  realOffset: Element.Methods.cumulativeScrollOffset,
 
-    return [valueL, valueT];
-  },
+  offsetParent: Element.Methods.getOffsetParent,
 
-  clone: function(source, target) {
-    var options = Object.extend({
-      setLeft:    true,
-      setTop:     true,
-      setWidth:   true,
-      setHeight:  true,
-      offsetTop:  0,
-      offsetLeft: 0
-    }, arguments[2] || {})
+  page: Element.Methods.viewportOffset,
 
-    // find page position of source
-    source = $(source);
-    var p = Position.page(source);
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
 
-    // find coordinate system to use
-    target = $(target);
-    var delta = [0, 0];
-    var parent = null;
-    // delta [0,0] will do fine with position: fixed elements,
-    // position:absolute needs offsetParent deltas
-    if (Element.getStyle(target,'position') == 'absolute') {
-      parent = Position.offsetParent(target);
-      delta = Position.page(parent);
-    }
+/*--------------------------------------------------------------------------*/
 
-    // correct by body offsets (fixes Safari)
-    if (parent == document.body) {
-      delta[0] -= document.body.offsetLeft;
-      delta[1] -= document.body.offsetTop;
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : &quot;[contains(concat(' ', @class, ' '), ' &quot; + name + &quot; ')]&quot;;
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames &amp;&amp; !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className &amp;&amp; (cn = ' ' + child.className + ' ') &amp;&amp; (cn.include(className) ||
+          (classNames &amp;&amp; classNames.all(function(name) {
+            return !name.toString().blank() &amp;&amp; cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
     }
+    return elements;
+  };
 
-    // set position
-    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
-    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
-    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
-    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
-  },
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
 
-  absolutize: function(element) {
-    element = $(element);
-    if (element.style.position == 'absolute') return;
-    Position.prepare();
+/*--------------------------------------------------------------------------*/
 
-    var offsets = Position.positionedOffset(element);
-    var top     = offsets[1];
-    var left    = offsets[0];
-    var width   = element.clientWidth;
-    var height  = element.clientHeight;
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
 
-    element._originalLeft   = left - parseFloat(element.style.left  || 0);
-    element._originalTop    = top  - parseFloat(element.style.top || 0);
-    element._originalWidth  = element.style.width;
-    element._originalHeight = element.style.height;
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length &gt; 0;
+    })._each(iterator);
+  },
 
-    element.style.position = 'absolute';
-    element.style.top    = top + 'px';;
-    element.style.left   = left + 'px';;
-    element.style.width  = width + 'px';;
-    element.style.height = height + 'px';;
+  set: function(className) {
+    this.element.className = className;
   },
 
-  relativize: function(element) {
-    element = $(element);
-    if (element.style.position == 'relative') return;
-    Position.prepare();
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
 
-    element.style.position = 'relative';
-    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
-    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
 
-    element.style.top    = top + 'px';
-    element.style.left   = left + 'px';
-    element.style.height = element._originalHeight;
-    element.style.width  = element._originalWidth;
+  toString: function() {
+    return $A(this).join(' ');
   }
-}
+};
 
-// Safari returns margins on body which is incorrect if the child is absolutely
-// positioned.  For performance reasons, redefine Position.cumulativeOffset for
-// KHTML/WebKit only.
-if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
-  Position.cumulativeOffset = function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-      if (element.offsetParent == document.body)
-        if (Element.getStyle(element, 'position') == 'absolute') break;
+Object.extend(Element.ClassNames.prototype, Enumerable);
 
-      element = element.offsetParent;
-    } while (element);
+/*--------------------------------------------------------------------------*/
 
-    return [valueL, valueT];
-  }
-}
\ No newline at end of file
+Element.addMethods();
\ No newline at end of file</diff>
      <filename>public/javascripts/prototype.js</filename>
    </modified>
    <modified>
      <diff>@@ -1 +1,5 @@
-# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
\ No newline at end of file
+# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
+#
+# To ban all spiders from the entire site uncomment the next two lines:
+# User-Agent: *
+# Disallow: /</diff>
      <filename>public/robots.txt</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/about'
\ No newline at end of file
+require 'commands/about'</diff>
      <filename>script/about</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/console'
\ No newline at end of file
+require 'commands/console'</diff>
      <filename>script/console</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/destroy'
\ No newline at end of file
+require 'commands/destroy'</diff>
      <filename>script/destroy</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/generate'
\ No newline at end of file
+require 'commands/generate'</diff>
      <filename>script/generate</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/plugin'
\ No newline at end of file
+require 'commands/plugin'</diff>
      <filename>script/plugin</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/runner'
\ No newline at end of file
+require 'commands/runner'</diff>
      <filename>script/runner</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/server'
\ No newline at end of file
+require 'commands/server'</diff>
      <filename>script/server</filename>
    </modified>
    <modified>
      <diff>@@ -69,7 +69,7 @@ class AssetsControllerTest &lt; Test::Unit::TestCase
     old_count = project.assets.length
     post :destroy, :id =&gt; 1
     assert_response :success
-    assert_equal 'text/javascript', @response.headers['Content-Type']
+    assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
     
     assert_equal old_count - 1, project.assets.count
   end</diff>
      <filename>test/functional/assets_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,12 +4,36 @@ require 'test_help'
 
 class Test::Unit::TestCase
   include AuthenticatedTestHelper
-  
+  # Transactional fixtures accelerate your tests by wrapping each test method
+  # in a transaction that's rolled back on completion.  This ensures that the
+  # test database remains unchanged so your fixtures don't have to be reloaded
+  # between every test method.  Fewer database queries means faster tests.
+  #
+  # Read Mike Clark's excellent walkthrough at
+  #   http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
+  #
+  # Every Active Record database supports transactions except MyISAM tables
+  # in MySQL.  Turn off transactional fixtures in this case; however, if you
+  # don't care one way or the other, switching from MyISAM to InnoDB tables
+  # is recommended.
+  #
+  # The only drawback to using transactional fixtures is when you actually 
+  # need to test transactions.  Since your test is bracketed by a transaction,
+  # any transactions started in your code will be automatically rolled back.
   self.use_transactional_fixtures = true
+
+  # Instantiated fixtures are slow, but give you @david where otherwise you
+  # would need people(:david).  If you don't want to migrate your existing
+  # test cases which use the @david style and don't mind the speed hit (each
+  # instantiated fixtures translates to a database query per test method),
+  # then set this back to true.
   self.use_instantiated_fixtures  = false
 
-  fixtures :users, :projects, :assets
+  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
+  #
+  # Note: You'll currently still have to declare fixtures explicitly in integration tests
+  # -- they do not yet inherit this setting
+  fixtures :all
 
   # Add more helper methods to be used by all tests here...
-    
 end</diff>
      <filename>test/test_helper.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/base.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/helpers.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/mail_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/part.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/part_container.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/quoting.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/utils.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/text/format.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/address.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/attachments.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/base64.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/config.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/encode.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/facade.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/header.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/info.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/loader.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mail.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mailbox.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mbox.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/net.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/obsolete.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/parser.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/port.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/quoting.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner_r.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/stringio.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/tmail.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/utils.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionmailer/lib/action_mailer/version.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/assertions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/base.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/benchmarking.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/caching.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/cgi_ext/cgi_ext.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/cgi_ext/cookie_performance_fix.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/cgi_ext/raw_post_data_fix.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/cgi_process.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/code_generation.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/components.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/cookies.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/dependencies.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/deprecated_assertions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/deprecated_redirects.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/filters.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/flash.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/helpers.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/layout.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/macros/auto_complete.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/macros/in_place_editing.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/pagination.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/request.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/rescue.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/response.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/routing.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/scaffolding.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/session/drb_server.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/session/drb_store.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/session_management.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/streaming.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/_trace.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/layout.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/missing_template.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/template_error.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/rescues/unknown_action.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/scaffolds/edit.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/scaffolds/layout.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/scaffolds/list.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/scaffolds/new.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/templates/scaffolds/show.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/test_process.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/url_rewriter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/vendor/xml_simple.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_controller/verification.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_pack.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_pack/version.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/base.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/compiled_templates.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/java_script_macros_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/javascript_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/javascripts/prototype.js</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/pagination_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/scriptaculous_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/tag_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/partials.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/template_error.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/vendor/builder.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/vendor/builder/blankslate.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/vendor/builder/xmlbase.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/vendor/builder/xmlevents.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionpack/lib/action_view/vendor/builder/xmlmarkup.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/api.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/base.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/casting.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/client.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/client/base.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/client/soap_client.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/container.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/container/action_controller_container.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/container/delegated_container.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/container/direct_container.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/dispatcher.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/dispatcher/abstract.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/invocation.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/protocol.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/protocol/abstract.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/protocol/discovery.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/scaffolding.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/struct.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/support/signature_types.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/test_invoke.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/actionwebservice/lib/action_web_service/version.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/acts/list.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/acts/nested_set.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/acts/tree.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/aggregations.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/association_collection.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/base.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/calculations.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/callbacks.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/db2_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/oci_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/deprecated_associations.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/deprecated_finders.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/fixtures.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/locking.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/migration.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/observer.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/query_cache.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/reflection.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/schema.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/schema_dumper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/timestamp.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/transactions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/validations.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/vendor/db2.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/vendor/mysql.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/vendor/simple.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/version.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/wrappers/yaml_wrapper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activerecord/lib/active_record/wrappings.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/binding_of_caller.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/breakpoint.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/clean_logger.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/array.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/blank.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/cgi.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/class.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/class/removal.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/date.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/date/conversions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/exception.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/hash.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/hash/keys.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/integer.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/integer/even_odd.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/integer/inflections.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/kernel.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/load_error.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/logger.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/module.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/module/inclusion.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/numeric.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/numeric/bytes.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/numeric/time.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/object.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/object/extending.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/object/misc.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/proc.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/range.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/range/conversions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/string.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/string/access.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/string/conversions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/string/iterators.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/symbol.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/time.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/core_ext/time/conversions.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/dependencies.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/inflections.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/inflector.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/json.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/json/encoders.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/json/encoders/core.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/option_merger.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/ordered_options.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/reloadable.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/values/time_zone.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/version.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/activesupport/lib/active_support/whiny_nil.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/binding_of_caller.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/breakpoint.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/breakpoint_client.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/code_statistics.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/about.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/breakpointer.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/console.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/destroy.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/generate.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/ncgi/listener</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/ncgi/tracker</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/performance/benchmarker.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/performance/profiler.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/plugin.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/process/reaper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/process/spawner.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/process/spinner.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/runner.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/server.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/servers/lighttpd.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/servers/webrick.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/commands/update.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/console_sandbox.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/dispatcher.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/fcgi_handler.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/initializer.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/base.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/commands.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/applications/app/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/controller/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/controller/controller_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/controller.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/view.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/mailer/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/migration/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/migration/migration_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/migration/templates/migration.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/model/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/model/templates/fixtures.yml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/model/templates/migration.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/model/templates/model.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/README</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/init.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/install.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/plugin.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/tasks.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form_scaffolding.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/helper.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/style.css</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/session_migration/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/session_migration/session_migration_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/session_migration/templates/migration.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/web_service/USAGE</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/api_definition.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/controller.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/functional_test.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/generators/components/web_service/web_service_generator.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/lookup.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/manifest.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/options.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/scripts.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/scripts/destroy.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/scripts/generate.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/scripts/update.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/simple_logger.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_generator/spec.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_info.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rails_version.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/railties_path.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/rubyprof_ext.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/databases.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/documentation.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/framework.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/javascripts.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/misc.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/rails.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/statistics.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/tasks/testing.rake</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/test_help.rb</filename>
    </removed>
    <removed>
      <filename>vendor/rails/railties/lib/webrick_server.rb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>1e4a7b7423fe9fe66d56e2bf4b70587d69d12370</id>
    </parent>
  </parents>
  <author>
    <name>Jacques Crocker</name>
    <email>jcnetdev@gmail.com</email>
  </author>
  <url>http://github.com/jcnetdev/gullery/commit/9f1e334d1774275f7020f426ccf58fb25ca9d5ac</url>
  <id>9f1e334d1774275f7020f426ccf58fb25ca9d5ac</id>
  <committed-date>2008-04-30T04:31:36-07:00</committed-date>
  <authored-date>2008-04-30T04:31:36-07:00</authored-date>
  <message>Updating to Rails 2.0</message>
  <tree>b9695f232e95724ca567f744b3547031ac4430cc</tree>
  <committer>
    <name>Jacques Crocker</name>
    <email>jcnetdev@gmail.com</email>
  </committer>
</commit>
