diff --git includes/path.inc includes/path.inc
index da8c575..5473977 100644
--- includes/path.inc
+++ includes/path.inc
@@ -465,3 +465,33 @@ function path_delete($criteria) {
   drupal_clear_path_cache();
 }
 
+/**
+ * Determine whether a path is in the administrative section of the site.
+ *
+ * @param $path
+ *   A Drupal path.
+ */
+function path_is_admin($path) {
+  $path_map = &drupal_static(__FUNCTION__);
+  if (!isset($path_map[$path])) {
+    $patterns = path_get_admin_paths();
+    $path_map[$path] = drupal_match_path($path, $patterns);
+  }
+  return $path_map[$path];
+}
+
+/**
+ * Get a list of administrative paths.
+ *
+ * @return array
+ *   The array of admin paths.
+ */
+function path_get_admin_paths() {
+  $paths = &drupal_static(__FUNCTION__);
+  if (!isset($paths)) {
+    $paths = module_invoke_all('admin_paths');
+    drupal_alter('admin_paths', $paths);
+    $paths = implode("\n", $paths);
+  }
+  return $paths;
+}
diff --git misc/jquery.ba-bbq.js misc/jquery.ba-bbq.js
new file mode 100644
index 0000000..f1e63d4
--- /dev/null
+++ misc/jquery.ba-bbq.js
@@ -0,0 +1,877 @@
+/*!
+ * jQuery BBQ: Back Button & Query Library - v1.0.2 - 10/10/2009
+ * http://benalman.com/projects/jquery-bbq-plugin/
+ * 
+ * Copyright (c) 2009 "Cowboy" Ben Alman
+ * Licensed under the MIT license
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery BBQ: Back Button & Query Library
+//
+// *Version: 1.0.2, Last updated: 10/10/2009*
+// 
+// Project Home - http://benalman.com/projects/jquery-bbq-plugin/
+// GitHub       - http://github.com/cowboy/jquery-bbq/
+// Source       - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js
+// (Minified)   - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (3.1kb)
+// 
+// About: License
+// 
+// Copyright (c) 2009 "Cowboy" Ben Alman,
+// Licensed under the MIT license.
+// http://benalman.com/about/license/
+// 
+// About: Examples
+// 
+// These working examples, complete with fully commented code, illustrate a few
+// ways in which this plugin can be used.
+// 
+// Basic AJAX     - http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/
+// Advanced AJAX  - http://benalman.com/code/projects/jquery-bbq/examples/fragment-advanced/
+// jQuery UI Tabs - http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/
+// Deparam        - http://benalman.com/code/projects/jquery-bbq/examples/deparam/
+// 
+// About: Support and Testing
+// 
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+// 
+// jQuery Versions - 1.3.2, 1.4pre
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.
+// Unit Tests      - http://benalman.com/code/projects/jquery-bbq/unit/
+// 
+// About: Release History
+// 
+// 1.0.2 - (10/10/2009) Fixed an issue in IE 6/7 where the hidden IFRAME caused
+//         a "This page contains both secure and nonsecure items." warning when
+//         used on an https:// page.
+// 1.0.1 - (10/7/2009) Fixed an issue in IE 8. Since both "IE7" and "IE8
+//         Compatibility View" modes erroneously report that the browser
+//         supports the native window.onhashchange event, a slightly more
+//         robust test needed to be added.
+// 1.0   - (10/2/2009) Initial release
+
+(function($,window){
+  '$:nomunge'; // Used by YUI compressor.
+  
+  // Some convenient shortcuts.
+  var undefined,
+    loc = document.location,
+    aps = Array.prototype.slice,
+    decode = decodeURIComponent,
+    
+    // Method references.
+    jq_param = $.param,
+    jq_param_fragment,
+    jq_deparam,
+    jq_deparam_fragment,
+    jq_bbq = $.bbq = $.bbq || {},
+    jq_bbq_pushState,
+    jq_elemUrlAttr,
+    fake_onhashchange,
+    
+    // Reused strings.
+    str_hashchange = 'hashchange',
+    str_querystring = 'querystring',
+    str_fragment = 'fragment',
+    str_hash = 'hash',
+    str_elemUrlAttr = 'elemUrlAttr',
+    str_href = 'href',
+    str_src = 'src',
+    
+    browser = $.browser,
+    is_old_ie = browser.msie && browser.version < 8,
+    
+    // Does the browser support window.onhashchange? Test for IE version, since
+    // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
+    supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie,
+    
+    // Reused RegExp.
+    re_trim_querystring = /^.*\?|#.*$/g,
+    re_trim_fragment = /^.*\#/,
+    
+    // Used by jQuery.elemUrlAttr.
+    elemUrlAttr_cache = {};
+  
+  // A few commonly used bits, broken out to help reduce minified file size.
+  
+  function is_string( arg ) {
+    return typeof arg === 'string';
+  };
+  
+  // Why write the same function twice? Let's curry! Mmmm, curry..
+  
+  function curry( func ) {
+    var args = aps.call( arguments, 1 );
+    
+    return function() {
+      return func.apply( this, args.concat( aps.call( arguments ) ) );
+    };
+  };
+  
+  // Section: Param (to string)
+  // 
+  // Method: jQuery.param.querystring
+  // 
+  // Retrieve the query string from a URL or if no arguments are passed, the
+  // current document.location.
+  // 
+  // Usage:
+  // 
+  // > jQuery.param.querystring( [ url ] );
+  // 
+  // Arguments:
+  // 
+  //  url - (String) A URL containing query string params to be parsed. If url
+  //    is not passed, the current document.location is used.
+  // 
+  // Returns:
+  // 
+  //  (String) The parsed query string, with any leading "?" removed.
+  //
+  
+  // Method: jQuery.param.querystring (build url)
+  // 
+  // Merge a URL, with or without pre-existing query string params, plus any
+  // object, params string or URL containing query string params into a new URL.
+  // 
+  // Usage:
+  // 
+  // > jQuery.param.querystring( url, params [, merge_mode ] );
+  // 
+  // Arguments:
+  // 
+  //  url - (String) A valid URL for params to be merged into. This URL may
+  //    contain a query string and/or fragment (hash).
+  //  params - (String) A params string or URL containing query string params to
+  //    be merged into url.
+  //  params - (Object) A params object to be merged into url.
+  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
+  //    specified, and is as-follows:
+  // 
+  //    * 0: params in the params argument will override any query string
+  //         params in url.
+  //    * 1: any query string params in url will override params in the params
+  //         argument.
+  //    * 2: params argument will completely replace any query string in url.
+  // 
+  // Returns:
+  // 
+  //  (String) Either a params string with urlencoded data or a URL with a
+  //    urlencoded query string in the format 'a=b&c=d&e=f'.
+  
+  // Method: jQuery.param.fragment
+  // 
+  // Retrieve the fragment (hash) from a URL or if no arguments are passed, the
+  // current document.location.
+  // 
+  // Usage:
+  // 
+  // > jQuery.param.fragment( [ url ] );
+  // 
+  // Arguments:
+  // 
+  //  url - (String) A URL containing fragment (hash) params to be parsed. If
+  //    url is not passed, the current document.location is used.
+  // 
+  // Returns:
+  // 
+  //  (String) The parsed fragment (hash) string, with any leading "#" removed.
+  
+  // Method: jQuery.param.fragment (build url)
+  // 
+  // Merge a URL, with or without pre-existing fragment (hash) params, plus any
+  // object, params string or URL containing fragment (hash) params into a new
+  // URL.
+  // 
+  // Usage:
+  // 
+  // > jQuery.param.fragment( url, params [, merge_mode ] );
+  // 
+  // Arguments:
+  // 
+  //  url - (String) A valid URL for params to be merged into. This URL may
+  //    contain a query string and/or fragment (hash).
+  //  params - (String) A params string or URL containing fragment (hash) params
+  //    to be merged into url.
+  //  params - (Object) A params object to be merged into url.
+  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
+  //    specified, and is as-follows:
+  // 
+  //    * 0: params in the params argument will override any fragment (hash)
+  //         params in url.
+  //    * 1: any fragment (hash) params in url will override params in the
+  //         params argument.
+  //    * 2: params argument will completely replace any query string in url.
+  // 
+  // Returns:
+  // 
+  //  (String) Either a params string with urlencoded data or a URL with a
+  //    urlencoded fragment (hash) in the format 'a=b&c=d&e=f'.
+  
+  function jq_param_sub( is_fragment, re, url, params, merge_mode ) {
+    var result,
+      qs,
+      matches,
+      url_params,
+      hash;
+    
+    if ( params !== undefined ) {
+      // Build URL by merging params into url string.
+      
+      // matches[1] = url part that precedes params, not including trailing ?/#
+      // matches[2] = params, not including leading ?/#
+      // matches[3] = if in 'querystring' mode, hash including leading #, otherwise ''
+      matches = url.match( is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/ );
+      
+      // Get the hash if in 'querystring' mode, and it exists.
+      hash = matches[3] || '';
+      
+      if ( merge_mode === 2 && is_string( params ) ) {
+        // If merge_mode is 2 and params is a string, merge the fragment / query
+        // string into the URL wholesale, without converting it into an object.
+        qs = params.replace( re, '' );
+        
+      } else {
+        // Convert relevant params in url to object.
+        url_params = jq_deparam( matches[2] );
+        
+        params = is_string( params )
+          
+          // Convert passed params string into object.
+          ? jq_deparam[ is_fragment ? str_fragment : str_querystring ]( params )
+          
+          // Passed params object.
+          : params;
+        
+        qs = merge_mode === 2 ? params                              // passed params replace url params
+          : merge_mode === 1  ? $.extend( {}, params, url_params )  // url params override passed params
+          : $.extend( {}, url_params, params );                     // passed params override url params
+        
+        // Convert params object to a string.
+        qs = jq_param( qs );
+      }
+      
+      // Build URL from the base url, querystring and hash. In 'querystring'
+      // mode, ? is only added if a query string exists. In 'fragment' mode, #
+      // is always added.
+      result = matches[1] + ( is_fragment ? '#' : qs || !matches[1] ? '?' : '' ) + qs + hash;
+      
+    } else if ( url ) {
+      // Parse params from URL string.
+      result = url.replace( re, '' );
+      
+    } else {
+      result = is_fragment
+        
+        // Parse hash from location.href, removing leading #. Firefox urldecodes
+        // location.hash by default, which breaks everything.
+        ? loc[ str_hash ] ? loc[ str_href ].replace( re, '' ) : ''
+        
+        // Get location.search and removing any leading ?
+        : loc.search.replace( /^\??/, '' );
+    }
+    
+    return result;
+  };
+  
+  jq_param[ str_querystring ]                  = curry( jq_param_sub, 0, re_trim_querystring );
+  jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, re_trim_fragment );
+  
+  // Section: Deparam (from string)
+  // 
+  // Method: jQuery.deparam
+  // 
+  // Deserialize a params string into an object, optionally coercing numbers,
+  // booleans, null and undefined values; this method is the counterpart to the
+  // internal jQuery.param method.
+  // 
+  // Usage:
+  // 
+  // > jQuery.deparam( params [, coerce ] );
+  // 
+  // Arguments:
+  // 
+  //  params - (String) A params string to be parsed.
+  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
+  //    undefined to their actual value. Defaults to false if omitted.
+  // 
+  // Returns:
+  // 
+  //  (Object) An object representing the deserialized params string.
+  
+  $.deparam = jq_deparam = function( params, coerce ) {
+    var obj = {},
+      coerce_types = { 'true': !0, 'false': !1, 'null': null };
+    
+    // Iterate over all name=value pairs.
+    $.each( params.replace( /\+/g, ' ' ).split( '&' ), function(j,v){
+      var param = v.split( '=' ),
+        key = decode( param[0] ),
+        val,
+        cur = obj,
+        i = 0,
+        
+        // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
+        // into its component parts.
+        keys = key.split( '][' ),
+        keys_last = keys.length - 1;
+      
+      // If the first keys part contains [ and the last ends with ], then []
+      // are correctly balanced.
+      if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
+        // Remove the trailing ] from the last keys part.
+        keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );
+        
+        // Split first keys part into two parts on the [ and add them back onto
+        // the beginning of the keys array.
+        keys = keys.shift().split('[').concat( keys );
+        
+        keys_last = keys.length - 1;
+      } else {
+        // Basic 'foo' style key.
+        keys_last = 0;
+      }
+      
+      // Are we dealing with a name=value pair, or just a name?
+      if ( param.length === 2 ) {
+        val = decode( param[1] );
+        
+        // Coerce values.
+        if ( coerce ) {
+          val = val && !isNaN(val)            ? +val              // number
+            : val === 'undefined'             ? undefined         // undefined
+            : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
+            : val;                                                // string
+        }
+        
+        if ( keys_last ) {
+          // Complex key, build deep object structure based on a few rules:
+          // * The 'cur' pointer starts at the object top-level.
+          // * [] = array push (n is set to array length), [n] = array if n is 
+          //   numeric, otherwise object.
+          // * If at the last keys part, set the value.
+          // * For each keys part, if the current level is undefined create an
+          //   object or array based on the type of the next keys part.
+          // * Move the 'cur' pointer to the next level.
+          // * Rinse & repeat.
+          for ( ; i <= keys_last; i++ ) {
+            key = keys[i] === '' ? cur.length : keys[i];
+            cur = cur[key] = i < keys_last
+              ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
+              : val;
+          }
+          
+        } else {
+          // Simple key, even simpler rules, since only scalars and shallow
+          // arrays are allowed.
+          
+          if ( $.isArray( obj[key] ) ) {
+            // val is already an array, so push on the next value.
+            obj[key].push( val );
+            
+          } else if ( obj[key] !== undefined ) {
+            // val isn't an array, but since a second value has been specified,
+            // convert val into an array.
+            obj[key] = [ obj[key], val ];
+            
+          } else {
+            // val is a scalar.
+            obj[key] = val;
+          }
+        }
+        
+      } else if ( key ) {
+        // No value was defined, so set something meaningful.
+        obj[key] = coerce
+          ? undefined
+          : '';
+      }
+    });
+    
+    return obj;
+  };
+  
+  // Method: jQuery.deparam.querystring
+  // 
+  // Parse the query string from a URL or the current document.location,
+  // deserializing it into an object, optionally coercing numbers, booleans,
+  // null and undefined values.
+  // 
+  // Usage:
+  // 
+  // > jQuery.deparam.querystring( [ url ] [, coerce ] );
+  // 
+  // Arguments:
+  // 
+  //  url - (String) An optional params string or URL containing query string
+  //    params to be parsed. If url is omitted, the current document.location
+  //    is used.
+  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
+  //    undefined to their actual value. Defaults to false if omitted.
+  // 
+  // Returns:
+  // 
+  //  (Object) An object representing the deserialized params string.
+  
+  // Method: jQuery.deparam.fragment
+  // 
+  // Parse the fragment (hash) from a URL or the current document.location,
+  // deserializing it into an object, optionally coercing numbers, booleans,
+  // null and undefined values.
+  // 
+  // Usage:
+  // 
+  // > jQuery.deparam.fragment( [ url ] [, coerce ] );
+  // 
+  // Arguments:
+  // 
+  //  url - (String) An optional params string or URL containing fragment (hash)
+  //    params to be parsed. If url is omitted, the current document.location
+  //    is used.
+  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
+  //    undefined to their actual value. Defaults to false if omitted.
+  // 
+  // Returns:
+  // 
+  //  (Object) An object representing the deserialized params string.
+  
+  function jq_deparam_sub( mode, re, url_or_params, coerce ) {
+    if ( url_or_params === undefined || typeof url_or_params === 'boolean' ) {
+      // url_or_params not specified.
+      coerce = url_or_params;
+      url_or_params = jq_param[ mode ]();
+    } else {
+      url_or_params = is_string( url_or_params )
+        ? url_or_params.replace( re, '' )
+        : url_or_params;
+    }
+    
+    return jq_deparam( url_or_params, coerce );
+  };
+  
+  jq_deparam[ str_querystring ]                    = curry( jq_deparam_sub, str_querystring, re_trim_querystring );
+  jq_deparam[ str_fragment ] = jq_deparam_fragment = curry( jq_deparam_sub, str_fragment, re_trim_fragment );
+  
+  // Section: Element manipulation
+  // 
+  // Method: jQuery.elemUrlAttr
+  // 
+  // Get the internal "Default URL attribute per tag" list, or augment the list
+  // with additional tag-attribute pairs, in case the defaults are insufficient.
+  // 
+  // In the <jQuery.fn.querystring> and <jQuery.fn.fragment> methods, this list
+  // is used to determine which attribute contains the URL to be modified, if
+  // an "attr" param is not specified.
+  // 
+  // Default Tag-Attribute List:
+  // 
+  //  a      - href
+  //  base   - href
+  //  iframe - src
+  //  img    - src
+  //  input  - src
+  //  form   - action
+  //  link   - href
+  //  script - src
+  // 
+  // Usage:
+  // 
+  // > jQuery.elemUrlAttr( [ tag_attr ] );
+  // 
+  // Arguments:
+  // 
+  //  tag_attr - (Object) An object containing a list of tag names and their
+  //    associated default attribute names in the format { tag: 'attr', ... } to
+  //    be merged into the internal tag-attribute list.
+  // 
+  // Returns:
+  // 
+  //  (Object) An object containing all stored tag-attribute values.
+  
+  // Only define function and set defaults if function doesn't already exist, as
+  // the urlInternal plugin will provide this method as well.
+  $[ str_elemUrlAttr ] || ($[ str_elemUrlAttr ] = function( obj ) {
+    return $.extend( elemUrlAttr_cache, obj );
+  })({
+    a: str_href,
+    base: str_href,
+    iframe: str_src,
+    img: str_src,
+    input: str_src,
+    form: 'action',
+    link: str_href,
+    script: str_src
+  });
+  
+  jq_elemUrlAttr = $[ str_elemUrlAttr ];
+  
+  // Method: jQuery.fn.querystring
+  // 
+  // Update URL attribute in one or more elements, merging the current URL (with
+  // or without pre-existing query string params) plus any params object or
+  // string into a new URL, which is then set into that attribute. Like
+  // <jQuery.param.querystring (build url)>, but for all elements in a jQuery
+  // collection.
+  // 
+  // Usage:
+  // 
+  // > jQuery('selector').querystring( [ attr, ] params [, merge_mode ] );
+  // 
+  // Arguments:
+  // 
+  //  attr - (String) Optional name of an attribute that will contain a URL to
+  //    merge params or url into. See <jQuery.elemUrlAttr> for a list of default
+  //    attributes.
+  //  params - (Object) A params object to be merged into the URL attribute.
+  //  params - (String) A URL containing query string params, or params string
+  //    to be merged into the URL attribute.
+  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
+  //    specified, and is as-follows:
+  //    
+  //    * 0: params in the params argument will override any params in attr URL.
+  //    * 1: any params in attr URL will override params in the params argument.
+  //    * 2: params argument will completely replace any query string in attr
+  //         URL.
+  // 
+  // Returns:
+  // 
+  //  (jQuery) The initial jQuery collection of elements, but with modified URL
+  //  attribute values.
+  
+  // Method: jQuery.fn.fragment
+  // 
+  // Update URL attribute in one or more elements, merging the current URL (with
+  // or without pre-existing fragment/hash params) plus any params object or
+  // string into a new URL, which is then set into that attribute. Like
+  // <jQuery.param.fragment (build url)>, but for all elements in a jQuery
+  // collection.
+  // 
+  // Usage:
+  // 
+  // > jQuery('selector').fragment( [ attr, ] params [, merge_mode ] );
+  // 
+  // Arguments:
+  // 
+  //  attr - (String) Optional name of an attribute that will contain a URL to
+  //    merge params into. See <jQuery.elemUrlAttr> for a list of default
+  //    attributes.
+  //  params - (Object) A params object to be merged into the URL attribute.
+  //  params - (String) A URL containing fragment (hash) params, or params
+  //    string to be merged into the URL attribute.
+  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
+  //    specified, and is as-follows:
+  //    
+  //    * 0: params in the params argument will override any params in attr URL.
+  //    * 1: any params in attr URL will override params in the params argument.
+  //    * 2: params argument will completely replace any fragment (hash) in attr
+  //         URL.
+  // 
+  // Returns:
+  // 
+  //  (jQuery) The initial jQuery collection of elements, but with modified URL
+  //  attribute values.
+  
+  function jq_fn_sub( mode, force_attr, params, merge_mode ) {
+    if ( !is_string( params ) && typeof params !== 'object' ) {
+      // force_attr not specified.
+      merge_mode = params;
+      params = force_attr;
+      force_attr = undefined;
+    }
+    
+    return this.each(function(){
+      var that = $(this),
+        
+        // Get attribute specified, or default specified via $.elemUrlAttr.
+        attr = force_attr || jq_elemUrlAttr()[ ( this.nodeName || '' ).toLowerCase() ] || '',
+        
+        // Get URL value.
+        url = attr && that.attr( attr ) || '';
+      
+      // Update attribute with new URL.
+      that.attr( attr, jq_param[ mode ]( url, params, merge_mode ) );
+    });
+    
+  };
+  
+  $.fn[ str_querystring ] = curry( jq_fn_sub, str_querystring );
+  $.fn[ str_fragment ]    = curry( jq_fn_sub, str_fragment );
+  
+  // Section: History, hashchange event
+  // 
+  // Method: jQuery.bbq.pushState
+  // 
+  // Adds a 'state' into the browser history at the current position, setting
+  // location.hash and triggering any bound <window.onhashchange> event
+  // callbacks (provided the new state is different than the previous state).
+  // 
+  // If no arguments are passed, an empty state is created, which is just a
+  // shortcut for jQuery.bbq.pushState( {}, 2 ).
+  // 
+  // Usage:
+  // 
+  // > jQuery.bbq.pushState( [ params [, merge_mode ] ] );
+  // 
+  // Arguments:
+  // 
+  //  params - (String) A serialized params string or a hash string beginning
+  //    with # to merge into location.hash.
+  //  params - (Object) A params object to merge into location.hash.
+  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
+  //    specified (unless a hash string beginning with # is specified, in which
+  //    case merge behavior defaults to 2), and is as-follows:
+  // 
+  //    * 0: params in the params argument will override any params in the
+  //         current state.
+  //    * 1: any params in the current state will override params in the params
+  //         argument.
+  //    * 2: params argument will completely replace current state.
+  // 
+  // Returns:
+  // 
+  //  Nothing.
+  // 
+  // Additional Notes:
+  // 
+  //  * Setting an empty state may cause the browser to scroll.
+  //  * Unlike the fragment and querystring methods, if a hash string beginning
+  //    with # is specified as the params agrument, merge_mode defaults to 2.
+  
+  jq_bbq.pushState = jq_bbq_pushState = function( params, merge_mode ) {
+    if ( is_string( params ) && /^#/.test( params ) && merge_mode === undefined ) {
+      // Params string begins with # and merge_mode not specified, so completely
+      // overwrite document.location.hash.
+      merge_mode = 2;
+    }
+    
+    var has_args = params !== undefined,
+      // Merge params into document.location using $.param.fragment.
+      url = jq_param_fragment( loc[ str_href ], has_args ? params : {}, has_args ? merge_mode : 2 );
+    
+    // Set new document.location.href. If hash is empty, use just # to prevent
+    // browser from reloading the page. Note that Safari 3 & Chrome barf on
+    // location.hash = '#'.
+    loc[ str_href ] = url + ( /#/.test( url ) ? '' : '#' );
+  };
+  
+  // Method: jQuery.bbq.getState
+  // 
+  // Retrieves the current 'state' from the browser history, parsing
+  // location.hash for a specific key or returning an object containing the
+  // entire state, optionally coercing numbers, booleans, null and undefined
+  // values.
+  // 
+  // Usage:
+  // 
+  // > jQuery.bbq.getState( [ key ] [, coerce ] );
+  // 
+  // Arguments:
+  // 
+  //  key - (String) An optional state key for which to return a value.
+  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
+  //    undefined to their actual value. Defaults to false.
+  // 
+  // Returns:
+  // 
+  //  (Anything) If key is passed, returns the value corresponding with that key
+  //    in the location.hash 'state', or undefined. If not, an object
+  //    representing the entire 'state' is returned.
+  
+  jq_bbq.getState = function( key, coerce ) {
+    return key === undefined || typeof key === 'boolean'
+      ? jq_deparam_fragment( key ) // 'key' really means 'coerce' here
+      : jq_deparam_fragment( coerce )[ key ];
+  };
+  
+  // Property: jQuery.bbq.pollDelay
+  // 
+  // The numeric interval (in milliseconds) at which the <window.onhashchange>
+  // polling loop executes. Defaults to 100.
+  
+  jq_bbq.pollDelay = 100;
+  
+  // Event: window.onhashchange
+  // 
+  // Fired when document.location.hash changes. In browsers that support it, the
+  // native window.onhashchange event is used (IE8, FF3.6), otherwise a polling
+  // loop is initialized, running every <jQuery.bbq.pollDelay> milliseconds to
+  // see if the hash has changed. In IE 6 and 7, a hidden IFRAME is created
+  // to allow hash-based history to work.
+  // 
+  // Usage in 1.4pre and newer:
+  // 
+  // In 1.4pre and newer, the event object that is passed into the callback is
+  // augmented with an additional e.fragment property that contains the current
+  // document location.hash state as a string, as well as an e.getState method.
+  // 
+  // e.fragment is equivalent to the output of <jQuery.param.fragment>, and
+  // e.getState() is equivalent to <jQuery.bbq.getState>, except that they refer
+  // to the event-specific state value stored in the event object, instead of
+  // the current document.location, allowing the event object to be referenced
+  // later, even if document.location has changed.
+  // 
+  // > $(window).bind( 'hashchange', function(e) {
+  // >   var hash_str = e.fragment,
+  // >     param_obj = e.getState(),
+  // >     param_val = e.getState( 'param_name' ),
+  // >     param_val_coerced = e.getState( 'param_name', true );
+  // >   ...
+  // > });
+  // 
+  // Usage in 1.3.2:
+  // 
+  // In 1.3.2, the event object is unable to be augmented as in 1.4pre+, so the
+  // fragment state isn't bound to the event object and must instead be parsed
+  // using the <jQuery.param.fragment> and <jQuery.bbq.getState> methods.
+  // 
+  // > $(window).bind( 'hashchange', function(e) {
+  // >   var hash_str = $.param.fragment(),
+  // >     param_obj = $.bbq.getState(),
+  // >     param_val = $.bbq.getState( 'param_name' ),
+  // >     param_val_coerced = $.bbq.getState( 'param_name', true );
+  // >   ...
+  // > });
+  // 
+  // Additional Notes:
+  // 
+  // * The polling loop and iframe are not created until at least one callback
+  //   is actually bound to 'hashchange'.
+  // * If you need the bound callback(s) to execute immediately, in cases where
+  //   the page 'state' exists on page load (via bookmark or page refresh, for
+  //   example) use $(window).trigger( 'hashchange' );
+  
+  $.event.special[ str_hashchange ] = {
+    
+    // Called only when the first 'hashchange' event is bound to window.
+    setup: function() {
+      // If window.onhashchange is supported natively, there's nothing to do..
+      if ( supports_onhashchange ) { return false; }
+      
+      // Otherwise, we need to create our own. And we don't want to call this
+      // until the user binds to the event, just in case they never do, since it
+      // will create a polling loop and possibly even a hidden IFRAME.
+      fake_onhashchange.start();
+    },
+    
+    // Called only when the last 'hashchange' event is unbound from window.
+    teardown: function() {
+      // If window.onhashchange is supported natively, there's nothing to do..
+      if ( supports_onhashchange ) { return false; }
+      
+      // Otherwise, we need to stop ours (if possible).
+      fake_onhashchange.stop();
+    },
+    
+    // Augmenting the event object with the .fragment property and .getState
+    // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will
+    // work, but the event won't be augmented)
+    add: function( handler, data, namespaces ) {
+      return function(e) {
+        // e.fragment is set to the value of location.hash (with any leading #
+        // removed) at the time the event is triggered.
+        var hash = e[ str_fragment ] = jq_param_fragment();
+        
+        // e.getState() works just like $.bbq.getState(), but uses the
+        // e.fragment property stored on the event object.
+        e.getState = function( key, coerce ) {
+          return key === undefined || typeof key === 'boolean'
+            ? jq_deparam( hash, key ) // 'key' really means 'coerce' here
+            : jq_deparam( hash, coerce )[ key ];
+        };
+        
+        handler.apply( this, arguments );
+      };
+    }
+  };
+  
+  // fake_onhashchange does all the work of triggering the window.onhashchange
+  // event for browsers that don't natively support it, including creating a
+  // polling loop to watch for hash changes and in IE 6/7 creating a hidden
+  // IFRAME to enable back and forward.
+  fake_onhashchange = (function(){
+    var self = {},
+      timeout_id,
+      iframe,
+      set_history,
+      get_history;
+    
+    // Initialize. In IE 6/7, creates a hidden IFRAME for history handling.
+    function init(){
+      // Most browsers don't need special methods here..
+      set_history = get_history = function(val){ return val; };
+      
+      // But IE6/7 do!
+      if ( is_old_ie ) {
+        
+        // Create hidden IFRAME at the end of the body.
+        iframe = $('<iframe src="javascript:0"/>').hide().appendTo( 'body' )[0].contentWindow;
+        
+        // Get history by looking at the hidden IFRAME's location.hash.
+        get_history = function() {
+          return iframe.document.location[ str_hash ].replace( /^#/, '' );
+        };
+        
+        // Set a new history item by opening and then closing the IFRAME
+        // document, *then* setting its location.hash.
+        set_history = function( hash, history_hash ) {
+          if ( hash !== history_hash ) {
+            var doc = iframe.document;
+            doc.open();
+            doc.close();
+            doc.location[ str_hash ] = '#' + hash;
+          }
+        };
+        
+        // Set initial history.
+        set_history( jq_param_fragment() );
+      }
+    };
+    
+    // Start the polling loop.
+    self.start = function() {
+      // Polling loop is already running!
+      if ( timeout_id ) { return; }
+      
+      // Remember the initial hash so it doesn't get triggered immediately.
+      var last_hash = jq_param_fragment();
+      
+      // Initialize if not yet initialized.
+      set_history || init();
+      
+      // This polling loop checks every $.bbq.pollDelay milliseconds to see if
+      // location.hash has changed, and triggers the 'hashchange' event on
+      // window when necessary.
+      (function loopy(){
+        var hash = jq_param_fragment(),
+          history_hash = get_history( last_hash );
+        
+        if ( hash !== last_hash ) {
+          set_history( last_hash = hash, history_hash );
+          
+          $(window).trigger( str_hashchange );
+          
+        } else if ( history_hash !== last_hash ) {
+          jq_bbq_pushState( '#' + history_hash );
+        }
+        
+        timeout_id = setTimeout( loopy, jq_bbq.pollDelay );
+      })();
+    };
+    
+    // Stop the polling loop, but only if an IE6/7 IFRAME wasn't created. In
+    // that case, even if there are no longer any bound event handlers, the
+    // polling loop is still necessary for back/next to work at all!
+    self.stop = function() {
+      if ( !iframe ) {
+        timeout_id && clearTimeout( timeout_id );
+        timeout_id = 0;
+      }
+    };
+    
+    return self;
+  })();
+  
+})(jQuery,this);
diff --git modules/dashboard/dashboard.css modules/dashboard/dashboard.css
index 05980a8..69d38f8 100644
--- modules/dashboard/dashboard.css
+++ modules/dashboard/dashboard.css
@@ -64,9 +64,11 @@
   border: 0;
 }
 
-#dashboard .canvas-content input {
+#dashboard .canvas-content a.button {
   float: right;
   margin: 0 0 0 10px;
+  color: #5a5a5a;
+  text-decoration: none;
 }
 
 #dashboard .region {
diff --git modules/dashboard/dashboard.js modules/dashboard/dashboard.js
index fedb542..2af23ad 100644
--- modules/dashboard/dashboard.js
+++ modules/dashboard/dashboard.js
@@ -65,7 +65,7 @@ Drupal.behaviors.dashboard = {
    * Helper for enterCustomizeMode; sets up drag-and-drop and close button.
    */
   setupDrawer: function () {
-    $('div.customize .canvas-content').prepend('<input type="button" class="form-submit" value="' + Drupal.t('Done') + '"></input>');
+    $('div.customize .canvas-content').prepend('<a class="button" href="">' + Drupal.t('Done') + '</a>');
     $('div.customize .canvas-content input').click(Drupal.behaviors.dashboard.exitCustomizeMode);
 
     // Initialize drag-and-drop.
diff --git modules/node/node.module modules/node/node.module
index 5399308..c939189 100644
--- modules/node/node.module
+++ modules/node/node.module
@@ -234,6 +234,20 @@ function node_field_build_modes($obj_type) {
 }
 
 /**
+ * Implement hook_admin_paths().
+ */
+function node_admin_paths() {
+  $paths = array(
+    'node/*/add',
+    'node/*/edit',
+    'node/*/delete',
+    'node/add',
+    'node/add/*',
+  );
+  return $paths;
+}
+
+/**
  * Gather a listing of links to nodes.
  *
  * @param $result
diff --git modules/overlay/overlay-child.js modules/overlay/overlay-child.js
new file mode 100644
index 0000000..dd173e4
--- /dev/null
+++ modules/overlay/overlay-child.js
@@ -0,0 +1,151 @@
+// $Id: child.js,v 1.1.4.3 2009/06/17 15:16:26 markuspetrux Exp $
+
+(function ($) {
+
+/**
+ * Overlay object for child windows.
+ */
+Drupal.overlayChild = Drupal.overlayChild || { processed: false, behaviors: {} };
+
+/**
+ * Attach the child dialog behavior to new content.
+ */
+Drupal.behaviors.overlayChild = {
+  attach: function (context, settings) {
+    var self = Drupal.overlayChild;
+    var settings = settings.overlayChild || {};
+
+    // Make sure this behavior is not processed more than once.
+    if (self.processed) {
+      return;
+    }
+    self.processed = true;
+
+    // If we cannot reach the parent window, then we have nothing else to do
+    // here.
+    if (!$.isObject(parent.Drupal) || !$.isObject(parent.Drupal.overlay)) {
+      return;
+    }
+
+    // If a form has been submitted successfully, then the server side script
+    // may have decided to tell us the parent window to close the popup dialog.
+    if (settings.closeOverlay) {
+      parent.Drupal.overlay.bindChild(window, true);
+      // Close the child window from a separate thread because the current
+      // one is busy processing Drupal behaviors.
+      setTimeout(function () {
+        // We need to store the parent variable locally because it will
+        // disappear as soon as we close the iframe.
+        var p = parent;
+        p.Drupal.overlay.close(settings.args, settings.statusMessages);
+        if (typeof settings.redirect == 'string') {
+          p.Drupal.overlay.redirect(settings.redirect);
+        }
+      }, 1);
+      return;
+    }
+
+    // If one of the toolbars displaying outside the overlay needs to be
+    // reloaded, let the parent window know.
+    if (settings.refreshToolbars) {
+      parent.Drupal.overlay.refreshToolbars(settings.refreshToolbars);
+    }
+
+    // Ok, now we can tell the parent window we're ready.
+    parent.Drupal.overlay.bindChild(window);
+
+    // If a form is being displayed, it has a hidden field for the parent
+    // window's location. Pass it that information.
+    var re = new RegExp('^' + parent.Drupal.settings.basePath);
+    var path = parent.window.location.pathname.replace(re, '');
+    $('#edit-overlay-parent-url').val(path);
+
+    // Install onBeforeUnload callback, if module is present.
+    if ($.isObject(Drupal.onBeforeUnload) && !Drupal.onBeforeUnload.callbackExists('overlayChild')) {
+      Drupal.onBeforeUnload.addCallback('overlayChild', function () {
+        // Tell the parent window we're unloading.
+        parent.Drupal.overlay.unbindChild(window);
+      });
+    }
+
+    // Attach child related behaviors to the iframe document.
+    self.attachBehaviors(context, settings);
+  }
+};
+
+/**
+ * Attach child related behaviors to the iframe document.
+ */
+Drupal.overlayChild.attachBehaviors = function (context, settings) {
+  $.each(this.behaviors, function () {
+    this(context, settings);
+  });
+};
+
+/**
+ * Scroll to the top of the page.
+ *
+ * This makes the overlay visible to users even if it is not as tall as the
+ * previously shown overlay was.
+ */
+Drupal.overlayChild.behaviors.scrollToTop = function (context, settings) {
+  window.scrollTo(0, 0);
+};
+
+/**
+ * Modify links and forms depending on their relation to the overlay.
+ *
+ * By default, forms and links are assumed to keep the flow in the overlay.
+ * Thus their action and href attributes respectively get a ?render=overlay
+ * suffix. Non-administrative links should however close the overlay and
+ * redirect the parent page to the given link. This would include links in a
+ * content listing, where administration options are mixed with links to the
+ * actual content to be shown on the site out of the overlay.
+ *
+ * @see Drupal.overlay.isAdminLink()
+ */
+Drupal.overlayChild.behaviors.parseLinks = function (context, settings) {
+  $('a:not(.overlay-exclude)', context).once('overlay').each(function () {
+    // Non-admin links should close the overlay and open in the main window.
+    if (!parent.Drupal.overlay.isAdminLink(this.href)) {
+      $(this).click(function () {
+        // We need to store the parent variable locally because it will
+        // disappear as soon as we close the iframe.
+        var parentWindow = parent;
+        if (parentWindow.Drupal.overlay.close(false)) {
+          parentWindow.Drupal.overlay.redirect($(this).attr('href'));
+        }
+        return false;
+      });
+      return;
+    }
+    else {
+      var href = $(this).attr('href');
+      if (href.indexOf('http') > 0 || href.indexOf('https') > 0) {
+        $(this).attr('target', '_new');
+      }
+      else {
+        $(this).each(function(){
+          this.href = parent.Drupal.overlay.fragmentizeLink(this);
+        }).click(function () {
+          parent.window.location.href = this.href;
+          return false;
+        });
+      }
+    }
+  });
+  $('form:not(.overlay-processed)', context).addClass('overlay-processed').each(function () {
+    // Obtain the action attribute of the form.
+    var action = $(this).attr('action');
+    if (action.indexOf('http') != 0 && action.indexOf('https') != 0) {
+      // Keep internal forms in the overlay.
+      action += (action.indexOf('?') > -1 ? '&' : '?') + 'render=overlay';
+      $(this).attr('action', action);
+    }
+    else {
+      $(this).attr('target', '_new');
+    }
+  });
+};
+
+})(jQuery);
diff --git modules/overlay/overlay-parent.css modules/overlay/overlay-parent.css
new file mode 100644
index 0000000..d9b6764
--- /dev/null
+++ modules/overlay/overlay-parent.css
@@ -0,0 +1,130 @@
+/* $Id$ */
+
+/**
+ * ui-dialog overlay.
+ */
+.ui-widget-overlay {
+  background-color: #000;
+  opacity: 0.7;
+  filter: alpha(opacity=80);
+  background-image: none;
+}
+
+/**
+ * jQuery UI Dialog classes.
+ */
+.overlay {
+  padding-right: 26px;
+}
+
+.overlay.ui-widget-content, .overlay .ui-widget-header {
+  background: none;
+  border: none;
+}
+
+.overlay .ui-dialog-titlebar {
+  white-space: nowrap;
+  padding: 0 20px;
+}
+
+.overlay .ui-dialog-title {
+  font-family: Verdana,sans-serif;
+  margin: 0;
+  padding: 0.3em 0;
+  color: #fff;
+  font-size: 20px;
+}
+.overlay .ui-dialog-title:active,
+.overlay .ui-dialog-title:focus {
+  outline: 0;
+}
+.overlay .ui-dialog-titlebar-close,
+.overlay .ui-dialog-titlebar-close:hover {
+  display: block;
+  right: -25px;
+  top: 100%;
+  margin: 0;
+  border: none;
+  padding: 0;
+  width: 26px;
+  height: 36px;
+  background: transparent url(images/close.png) no-repeat;
+  -moz-border-radius-topleft: 0;
+  -webkit-border-top-left-radius: 0;
+}
+.overlay .ui-dialog-titlebar-close span {
+  display: none;
+}
+.overlay .ui-dialog-content {
+  color: #292929;
+  background-color: #f8f8f8;
+}
+
+/**
+ * Overlay content and shadows.
+ */
+.overlay #overlay-container {
+  margin: 0;
+  padding: 0;
+  overflow: visible;
+  background: #fff url(images/loading.gif) no-repeat 50% 50%;
+  -webkit-box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+  -moz-box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+  box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+}
+.overlay #overlay-element {
+  overflow: hidden;
+}
+
+/**
+ * Tabs on the overlay.
+ */
+.overlay .ui-dialog-titlebar ul {
+  position: absolute;
+  right: 20px;
+  bottom: 0;
+  margin: 0;
+  line-height: 27px;
+  text-transform: uppercase;
+}
+.overlay .ui-dialog-titlebar ul li {
+  display: inline-block;
+  list-style: none;
+  margin: 0 0 0 -3px;
+  padding: 0;
+}
+.overlay .ui-dialog-titlebar ul li a,
+.overlay .ui-dialog-titlebar ul li a:active,
+.overlay .ui-dialog-titlebar ul li a:visited,
+.overlay .ui-dialog-titlebar ul li a:hover {
+  background-color: #a6a7a2;
+  -moz-border-radius: 8px 8px 0 0;
+  -webkit-border-top-left-radius: 8px;
+  -webkit-border-top-right-radius: 8px;
+  border-radius: 8px 8px 0 0;
+  color: #000;
+  font-weight: bold;
+  padding: 5px 14px;
+  text-decoration: none;
+  font-size: 11px;
+}
+.overlay .ui-dialog-titlebar ul li.active a,
+.overlay .ui-dialog-titlebar ul li.active a.active,
+.overlay .ui-dialog-titlebar ul li.active a:active,
+.overlay .ui-dialog-titlebar ul li.active a:visited {
+  background-color: #fff;
+  padding-bottom: 7px;
+}
+.overlay .ui-dialog-titlebar ul li a:hover {
+  color: #fff;
+}
+.overlay .ui-dialog-titlebar ul li.active a:hover {
+  color: #000;
+}
+
+/**
+ * Add to shortcuts link
+ */
+.overlay div.add-to-shortcuts {
+  padding-top: 0.9em;
+}
diff --git modules/overlay/overlay-parent.js modules/overlay/overlay-parent.js
new file mode 100644
index 0000000..b25009b
--- /dev/null
+++ modules/overlay/overlay-parent.js
@@ -0,0 +1,878 @@
+// $Id: parent.js,v 1.1.4.4 2009/06/19 15:32:57 markuspetrux Exp $
+
+(function ($) {
+
+/**
+ * Open the overlay, or load content into it, when an admin link is clicked.
+ */
+Drupal.behaviors.overlayParent = {
+  attach: function (context, settings) {
+    // Alter all admin links so that they will open in the overlay.
+    $('a', context).filter(function () {
+      return Drupal.overlay.isAdminLink(this.href);
+    })
+    .once('overlay')
+    .each(function () {
+      // Move the link destination to a URL fragment.
+      this.href = Drupal.overlay.fragmentizeLink(this);
+    });
+
+    // Simulate the native click event for all links that appear outside the
+    // overlay. jQuery UI Dialog prevents all clicks outside a modal dialog.
+    $('.overlay-displace-top a', context)
+    .add('.overlay-displace-bottom a', context)
+    .click(function () {
+      window.location.href = this.href;
+    });
+
+
+    // Resize the overlay when the toolbar drawer is toggled.
+    $('#toolbar span.toggle', context).once('overlay').click(function () {
+      setTimeout(function () {
+        Drupal.overlay.resize(Drupal.overlay.iframe.documentSize);
+      }, 150);
+
+    });
+
+    // Make sure the onhashchange handling below is only processed once.
+    if (this.processed) {
+      return;
+    }
+    this.processed = true;
+
+    // When the hash (URL fragment) changes, open the overlay if needed.
+    $(window).bind('hashchange', function (e) {
+      // If we changed the hash to reflect an internal redirect in the overlay,
+      // its location has already been changed, so don't do anything.
+      if ($.data(window.location, window.location.href) === 'redirect') {
+        $.data(window.location, window.location.href, null);
+      }
+      // Otherwise, change the contents of the overlay to reflect the new hash.
+      else {
+        Drupal.overlay.trigger();
+      }
+    });
+
+    // Trigger the hashchange event once, after the page is loaded, so that
+    // permalinks open the overlay.
+    $(window).trigger('hashchange');
+  }
+};
+
+/**
+ * Overlay object for parent windows.
+ */
+Drupal.overlay = Drupal.overlay || {
+  options: {},
+  iframe: { $container: null, $element: null },
+  isOpen: false
+};
+
+/**
+ * Open an overlay.
+ *
+ * Ensure that only one overlay is opened ever. Use Drupal.overlay.load() if
+ * the overlay is already open but a new page needs to be opened.
+ *
+ * @param options
+ *   Properties of the overlay to open:
+ *   - url: the URL of the page to open in the overlay.
+ *   - width: width of the overlay in pixels.
+ *   - height: height of the overlay in pixels.
+ *   - autoFit: boolean indicating whether the overlay should be resized to
+ *     fit the contents of the document loaded.
+ *   - onOverlayOpen: callback to invoke when the overlay is opened.
+ *   - onOverlayCanClose: callback to allow external scripts decide if the
+ *     overlay can be closed.
+ *   - onOverlayClose: callback to invoke when the overlay is closed.
+ *   - customDialogOptions: an object with custom jQuery UI Dialog options.
+ *
+ * @return
+ *   If the overlay was opened true, otherwise false.
+ */
+Drupal.overlay.open = function (options) {
+  var self = this;
+
+  // Just one overlay is allowed.
+  if (self.isOpen || $('#overlay-container').size()) {
+    return false;
+  }
+
+  var defaultOptions = {
+    url: options.url,
+    width: options.width,
+    height: options.height,
+    autoFit: (options.autoFit == undefined || options.autoFit),
+    onOverlayOpen: options.onOverlayOpen,
+    onOverlayCanClose: options.onOverlayCanClose,
+    onOverlayClose: options.onOverlayClose,
+    customDialogOptions: options.customDialogOptions || {}
+  }
+
+  self.options = $.extend(defaultOptions, options);
+
+  // Create the dialog and related DOM elements.
+  self.create();
+
+  // Open the dialog offscreen where we can set its size, etc.
+  var temp = self.iframe.$container.dialog('option', { position: ['-999em', '-999em'] }).dialog('open');;
+
+  return true;
+};
+
+/**
+ * Create the underlying markup and behaviors for the overlay.
+ *
+ * Reuses jQuery UI's dialog component to construct the overlay markup and
+ * behaviors, sanitizing the options previously set in self.options.
+ */
+Drupal.overlay.create = function () {
+  var self = this;
+
+  self.iframe.$element = $(Drupal.theme('overlayElement'));
+  self.iframe.$container = $(Drupal.theme('overlayContainer')).append(self.iframe.$element);
+
+  $('body').append(self.iframe.$container);
+
+  // Open callback for jQuery UI Dialog.
+  var dialogOpen = function () {
+    // Unbind the keypress handler installed by ui.dialog itself.
+    // IE does not fire keypress events for some non-alphanumeric keys
+    // such as the tab character. http://www.quirksmode.org/js/keys.html
+    // Also, this is not necessary here because we need to deal with an
+    // iframe element that contains a separate window.
+    // We'll try to provide our own behavior from bindChild() method.
+    $('.overlay').unbind('keypress.ui-dialog');
+
+    // Adjust close button features.
+    $('.overlay .ui-dialog-titlebar-close:not(.overlay-processed)').addClass('overlay-processed')
+      .attr('href', '#')
+      .attr('title', Drupal.t('Close'))
+      .unbind('click')
+      .bind('click', function () {
+        try { self.close(); } catch(e) {}
+        // Allow the click event to propagate, to clear the hash state.
+        return true;
+      });
+
+    // Replace the title span element with an h1 element for accessibility.
+    $('.overlay .ui-dialog-title').replaceWith(Drupal.theme('overlayTitleHeader', $('.overlay .ui-dialog-title').html()));
+
+    // Compute initial dialog size.
+    var dialogSize = self.sanitizeSize({width: self.options.width, height: self.options.height});
+
+    // Compute frame size and dialog position based on dialog size.
+    var frameSize = $.extend({}, dialogSize);
+    frameSize.height -= $('.overlay .ui-dialog-titlebar').outerHeight(true);
+    var dialogPosition = self.computePosition($('.overlay'), dialogSize);
+
+    // Adjust size of the iframe element and container.
+    $('.overlay').width(dialogSize.width).height(dialogSize.height);
+    self.iframe.$container.width(frameSize.width).height(frameSize.height);
+    self.iframe.$element.width(frameSize.width).height(frameSize.height);
+
+    // Update the dialog size so that UI internals are aware of the change.
+    self.iframe.$container.dialog('option', { width: dialogSize.width, height: dialogSize.height });
+
+    // Hide the dialog, position it on the viewport and then fade it in with
+    // the frame hidden until the child document is loaded.
+    self.iframe.$element.hide();
+    $('.overlay').hide().css({top: dialogPosition.top, left: dialogPosition.left});
+    $('.overlay').fadeIn('fast', function () {
+      // Load the document on hidden iframe (see bindChild method).
+      self.load(self.options.url);
+    });
+
+    if ($.isFunction(self.options.onOverlayOpen)) {
+      self.options.onOverlayOpen(self);
+    }
+
+    self.isOpen = true;
+  };
+
+  // Before close callback for jQuery UI Dialog.
+  var dialogBeforeClose = function () {
+    if (self.beforeCloseEnabled) {
+      return true;
+    }
+    if (!self.beforeCloseIsBusy) {
+      self.beforeCloseIsBusy = true;
+      setTimeout(function () { self.close(); }, 1);
+    }
+    return false;
+  };
+
+  // Close callback for jQuery UI Dialog.
+  var dialogClose = function () {
+    $(document).unbind('keydown.overlay-event');
+    $('.overlay .ui-dialog-titlebar-close').unbind('keydown.overlay-event');
+    try {
+      self.iframe.$element.remove();
+      self.iframe.$container.dialog('destroy').remove();
+    } catch(e) {};
+    delete self.iframe.documentSize;
+    delete self.iframe.Drupal;
+    delete self.iframe.$element;
+    delete self.iframe.$container;
+    if (self.beforeCloseEnabled) {
+      delete self.beforeCloseEnabled;
+    }
+    if (self.beforeCloseIsBusy) {
+      delete self.beforeCloseIsBusy;
+    }
+    self.isOpen = false;
+  };
+
+  // Default jQuery UI Dialog options.
+  var dialogOptions = {
+    modal: true,
+    autoOpen: false,
+    closeOnEscape: true,
+    resizable: false,
+    title: Drupal.t('Loading...'),
+    dialogClass: 'overlay',
+    zIndex: 500,
+    open: dialogOpen,
+    beforeclose: dialogBeforeClose,
+    close: dialogClose
+  };
+
+  // Allow external script override default jQuery UI Dialog options.
+  $.extend(dialogOptions, self.options.customDialogOptions);
+
+  // Create the jQuery UI Dialog.
+  self.iframe.$container.dialog(dialogOptions);
+};
+
+/**
+ * Load the given URL into the overlay iframe.
+ *
+ * Use this method to change the URL being loaded in the overlay if it is
+ * already open.
+ */
+Drupal.overlay.load = function (url) {
+  var self = this;
+  var iframe = self.iframe.$element.get(0);
+  // Get the document object of the iframe window.
+  // @see http://xkr.us/articles/dom/iframe-document/
+  var doc = (iframe.contentWindow || iframe.contentDocument);
+  if (doc.document) {
+    doc = doc.document;
+  }
+  // location.replace doesn't create a history entry. location.href does.
+  // In this case, we want location.replace, as we're creating the history
+  // entry using URL fragments.
+  doc.location.replace(url);
+};
+
+/**
+ * Check if the dialog can be closed.
+ */
+Drupal.overlay.canClose = function () {
+  var self = this;
+  if (!self.isOpen) {
+    return false;
+  }
+  // Allow external scripts decide if the overlay can be closed.
+  if ($.isFunction(self.options.onOverlayCanClose)) {
+    if (!self.options.onOverlayCanClose(self)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+/**
+ * Close the overlay and remove markup related to it from the document.
+ */
+Drupal.overlay.close = function (args, statusMessages) {
+  var self = this;
+
+  // Offer the user a chance to change their mind if there is a form on the
+  // page, which may have unsaved work on it.
+  var iframeElement = self.iframe.$element.get(0);
+  var iframeDocument = (iframeElement.contentWindow || iframeElement.contentDocument);
+  if (iframeDocument.document) {
+    iframeDocument = iframeDocument.document;
+  }
+
+  // Check if the dialog can be closed.
+  if (!self.canClose()) {
+    delete self.beforeCloseIsBusy;
+    return false;
+  }
+
+  // Hide and destroy the dialog.
+  function closeDialog() {
+    // Prevent double execution when close is requested more than once.
+    if (!$.isObject(self.iframe.$container)) {
+      return;
+    }
+    self.beforeCloseEnabled = true;
+    self.iframe.$container.dialog('close');
+    if ($.isFunction(self.options.onOverlayClose)) {
+      self.options.onOverlayClose(args, statusMessages);
+    }
+  }
+  if (!$.isObject(self.iframe.$element) || !self.iframe.$element.size() || !self.iframe.$element.is(':visible')) {
+    closeDialog();
+  }
+  else {
+    self.iframe.$container.animate({height: 'hide'}, { duration: 'fast', 'queue': false });
+    $('.overlay').animate({opacity: 'hide'}, closeDialog);
+  }
+  return true;
+};
+
+/**
+ * Redirect the overlay parent window to the given URL.
+ *
+ * @param link
+ *   Can be an absolute URL or a relative link to the domain root.
+ */
+Drupal.overlay.redirect = function (link) {
+  if (link.indexOf('http') != 0 && link.indexOf('https') != 0) {
+    var absolute = location.href.match(/https?:\/\/[^\/]*/)[0];
+    link = absolute + link;
+  }
+  location.href = link;
+  return true;
+}
+
+/**
+ * Bind the child window.
+ *
+ * Add tabs on the overlay, keyboard actions and display animation.
+ */
+Drupal.overlay.bindChild = function (iFrameWindow, isClosing) {
+  var self = this;
+  var $iFrameWindow = iFrameWindow.jQuery;
+  var $iFrameDocument = $iFrameWindow(iFrameWindow.document);
+  var autoResizing = false;
+  self.iframe.Drupal = iFrameWindow.Drupal;
+
+  // We are done if the child window is closing.
+  if (isClosing) {
+    return;
+  }
+
+  // Make sure the parent window URL matches the child window URL.
+  self.syncChildLocation($iFrameDocument[0].location);
+  // Update the dialog title with the child window title.
+  $('.overlay .ui-dialog-title').html($iFrameDocument.attr('title')).focus();
+  // Add a title attribute to the iframe for accessibility.
+  self.iframe.$element.attr('title', Drupal.t('@title dialog', { '@title': $iFrameDocument.attr('title') }));
+
+  // Move over shortcuts addition button if exists. 
+  var addToShortcuts = $('.add-to-shortcuts', $iFrameDocument);
+  if (addToShortcuts.length) {
+    $('.ui-dialog-titlebar .add-to-shortcuts').remove();
+    $('a', addToShortcuts).attr('target', 'overlay-element');
+    $('.overlay .ui-dialog-title').after(Drupal.theme('overlayShortcutsButton', addToShortcuts.html()));
+    $('.add-to-shortcuts', $iFrameDocument).remove();
+  }
+
+  // Remove any existing tabs.
+  $('.overlay .ui-dialog-titlebar ul').remove();
+
+  // Setting tabIndex makes the div focusable.
+  $iFrameDocument.attr('tabindex', -1);
+
+  $('.ui-dialog-titlebar-close-bg').animate({opacity: 0.9999}, 'fast');
+
+  // Perform animation to show the iframe element.
+  self.iframe.$element.fadeIn('fast', function () {
+    // @todo: Watch for experience in the way we compute the size of the
+    // iframed document. There are many ways to do it, and none of them
+    // seem to be perfect. Note though, that the size of the iframe itself
+    // may affect the size of the child document, especially on fluid layouts.
+    self.iframe.documentSize = { width: $iFrameDocument.width(), height: $iFrameWindow('body').height() + 25 };
+
+    // Adjust overlay to fit the iframe content?
+    if (self.options.autoFit) {
+      self.resize(self.iframe.documentSize);
+    }
+
+    // Try to enhance keyboard based navigation of the overlay.
+    // Logic inspired by the open() method in ui.dialog.js, and
+    // http://wiki.codetalks.org/wiki/index.php/Docs/Keyboard_navigable_JS_widgets
+
+    // Get a reference to the close button.
+    var $closeButton = $('.overlay .ui-dialog-titlebar-close');
+
+    // Search tabbable elements on the iframed document to speed up related
+    // keyboard events.
+    // @todo: Do we need to provide a method to update these references when
+    // AJAX requests update the DOM on the child document?
+    var $iFrameTabbables = $iFrameWindow(':tabbable:not(form)');
+    var $firstTabbable = $iFrameTabbables.filter(':first');
+    var $lastTabbable = $iFrameTabbables.filter(':last');
+
+    // Unbind keyboard event handlers that may have been enabled previously.
+    $(document).unbind('keydown.overlay-event');
+    $closeButton.unbind('keydown.overlay-event');
+
+    // When the focus leaves the close button, then we want to jump to the
+    // first/last inner tabbable element of the child window.
+    $closeButton.bind('keydown.overlay-event', function (event) {
+      if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) {
+        var $target = (event.shiftKey ? $lastTabbable : $firstTabbable);
+        if (!$target.size()) {
+          $target = $iFrameDocument;
+        }
+        setTimeout(function () { $target.focus(); }, 10);
+        return false;
+      }
+    });
+
+    // When the focus leaves the child window, then drive the focus to the
+    // close button of the dialog.
+    $iFrameDocument.bind('keydown.overlay-event', function (event) {
+      if (event.keyCode) {
+        if (event.keyCode == $.ui.keyCode.TAB) {
+          if (event.shiftKey && event.target == $firstTabbable.get(0)) {
+            setTimeout(function () { $closeButton.focus(); }, 10);
+            return false;
+          }
+          else if (!event.shiftKey && event.target == $lastTabbable.get(0)) {
+            setTimeout(function () { $closeButton.focus(); }, 10);
+            return false;
+          }
+        }
+        else if (event.keyCode == $.ui.keyCode.ESCAPE) {
+          setTimeout(function () { self.close(); }, 10);
+          return false;
+        }
+      }
+    });
+
+    var autoResize = function () {
+      if (typeof self.iframe.$element == 'undefined') {
+        autoResizing = false;
+        $(window).unbind('resize', windowResize);
+        return;
+      }
+      var iframeElement = self.iframe.$element.get(0);
+      var iframeDocument = (iframeElement.contentWindow || iframeElement.contentDocument);
+      if (iframeDocument.document) {
+        iframeDocument = iframeDocument.document;
+      }
+      // Use outerHeight() because otherwise the calculation will be off
+      // because of padding and/or border added by the theme.
+      var height = $(iframeDocument).find('body').outerHeight() + 25;
+      self.iframe.$element.css('height', height);
+      self.iframe.$container.css('height', height);
+      self.iframe.$container.parent().css('height', height + 45);
+      // Don't allow the shadow background to shrink so it's not enough to hide
+      // the whole page. Take the existing document height (with overlay) and
+      // the body height itself for our base calculation.
+      var docHeight = Math.min($(document).find('body').outerHeight(), $(document).height());
+      $('.ui-widget-overlay').height(Math.max(docHeight, $(window).height(), height + 145));
+      setTimeout(autoResize, 150);
+    };
+
+    var windowResize = function () {
+      var width = $(window).width()
+      var change = lastWidth - width;
+      var currentWidth = self.iframe.$element.width();
+      var newWidth = lastFrameWidth - change;
+      lastWidth = width;
+      lastFrameWidth = newWidth;
+
+      if (newWidth >= 300) {
+        self.iframe.$element.css('width', newWidth);
+        self.iframe.$container.css('width', newWidth);
+        self.iframe.$container.parent().css('width', newWidth);
+        widthBelowMin = false;
+      }
+      else {
+        widthBelowMin = true;
+      }
+    }
+
+    if (!autoResizing) {
+      autoResizing = true;
+      autoResize();
+      var lastFrameWidth = self.iframe.$element.width();
+      var lastWidth = $(window).width();
+      $(window).resize(windowResize);
+    }
+
+    // When the focus is captured by the parent document, then try
+    // to drive the focus back to the first tabbable element, or the
+    // close button of the dialog (default).
+    $(document).bind('keydown.overlay-event', function (event) {
+      if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) {
+        setTimeout(function () {
+          if (!$iFrameWindow(':tabbable:not(form):first').focus().size()) {
+            $closeButton.focus();
+          }
+        }, 10);
+        return false;
+      }
+    });
+
+    // If there are tabs in the page, move them to the titlebar.
+    var tabs = $iFrameDocument.find('ul.primary').get(0);
+
+    // This breaks in anything less than IE 7. Prevent it from running.
+    if (typeof tabs != 'undefined' && (!$.browser.msie || parseInt($.browser.version) >= 7)) {
+      $('.ui-dialog-titlebar').append($(tabs).remove().get(0));
+      if ($(tabs).is('.primary')) {
+        $(tabs).find('a').removeClass('overlay-processed');
+        Drupal.attachBehaviors($(tabs));
+      }
+      // Remove any classes from the list element to avoid theme styles
+      // clashing with our styling.
+      $(tabs).removeAttr('class');
+    }
+  });
+};
+
+/**
+ * Unbind the child window.
+ *
+ * Remove keyboard event handlers, reset title and hide the iframe.
+ */
+Drupal.overlay.unbindChild = function (iFrameWindow) {
+  var self = this;
+
+  // Prevent memory leaks by explicitly unbinding keyboard event handler
+  // on the child document.
+  iFrameWindow.jQuery(iFrameWindow.document).unbind('keydown.overlay-event');
+
+  // Change the overlay title.
+  $('.overlay .ui-dialog-title').html(Drupal.t('Please wait...'));
+
+  // Hide the iframe element.
+  self.iframe.$element.fadeOut('fast');
+};
+
+/**
+ * Check if the given link is in the administrative section of the site.
+ *
+ * @param url
+ *   The url to be tested.
+ * @return boolean
+ *   TRUE if the URL represents an administrative link, FALSE otherwise.
+ */
+Drupal.overlay.isAdminLink = function (url) {
+  var self = this;
+  // Create a native Link object, so we can use its object methods.
+  var link = $(url.link(url)).get(0);
+  var path = link.pathname.replace(new RegExp(Drupal.settings.basePath), '');
+  if (path == '') {
+    // If the path appears empty, it might mean the path is represented in the
+    // query string (clean URLs are not used).
+    var match = new RegExp("(\\?|&)q=(.+)(&|$)").exec(link.search);
+    if (match && match.length == 4) {
+      path = match[2];
+    }
+  }
+
+  // Turn the list of administrative paths into a regular expression.
+  if (!self.adminPathRegExp) {
+    var adminPaths = '$(' + Drupal.settings.overlay.adminPaths.replace(/\s+/g, ')|(') + ')^';
+    adminPaths = adminPaths.replace(/\*/g, '.*');
+    self.adminPathRegExp = new RegExp(adminPaths);
+  }
+
+  return self.adminPathRegExp.exec(path);
+}
+
+/**
+ * Sanitize dialog size.
+ *
+ * Do not let the overlay go over the 0.78x of the width of the screen and set
+ * minimal height. The height is not limited due to how we rely on the parent
+ * window to provide scrolling instead of scrolling in scrolling with the
+ * overlay.
+ *
+ * @param size
+ *   Contains 'width' and 'height' items as numbers.
+ * @return
+ *   The same structure with sanitized number values.
+ */
+Drupal.overlay.sanitizeSize = function (size) {
+  var width, height;
+  var $window = $(window);
+
+  // Use 300px as the minimum width but at most expand to 78% of the window.
+  // Ensures that users see that there is an actual website in the background.
+  var minWidth = 300, maxWidth = parseInt($window.width() * .78);
+  if (typeof size.width != 'number') {
+    width = maxWidth;
+  }
+  // Set to at least minWidth but at most maxWidth. 
+  else if (size.width < minWidth || size.width > maxWidth) {
+    width = Math.min(maxWidth, Math.max(minWidth, size.width));
+  }
+  else {
+    width = size.width;
+  }
+  
+  // Use 100px as the minimum height. Expand to 92% of the window if height
+  // was invalid, to ensure that we have a reasonable chance to show content.
+  var minHeight = 100, maxHeight = parseInt($window.height() * .92);
+  if (typeof size.height != 'number') {
+    height = maxHeight;
+  }
+  else if (size.height < minHeight) {
+    // Do not consider maxHeight as the actual maximum height, since we rely on
+    // the parent window scroll bar to scroll the window. Only set up to be at
+    // least the minimal height.
+    height = Math.max(minHeight, size.height);
+  }
+  else {
+    height = size.height;
+  }
+  return { width: width, height: height };
+};
+
+/**
+ * Compute position to center horizontally and on viewport top vertically.
+ */
+Drupal.overlay.computePosition = function ($element, elementSize) {
+  var $window = $(window);
+  // Consider any region that should be visible above the overlay (such as
+  // an admin toolbar).
+  var $toolbar = $('.overlay-displace-top');
+  var toolbarHeight = 0;
+  $toolbar.each(function () {
+    toolbarHeight += $toolbar.height();
+  });
+  var position = {
+    left: Math.max(0, parseInt(($window.width() - elementSize.width) / 2)),
+    top: toolbarHeight + 20
+  };
+
+  // Reset the scroll to the top of the window so that the overlay is visible again.
+  window.scrollTo(0, 0);
+  return position;
+};
+
+/**
+ * Resize overlay to the given size.
+ * 
+ * @param size
+ *   Contains 'width' and 'height' items as numbers.
+ */
+Drupal.overlay.resize = function (size) {
+  var self = this;
+
+  // Compute frame and dialog size based on requested document size.
+  var titleBarHeight = $('.overlay .ui-dialog-titlebar').outerHeight(true);
+  var frameSize = self.sanitizeSize(size); 
+  var dialogSize = $.extend({}, frameSize);
+  dialogSize.height += titleBarHeight + 15;
+
+  // Compute position on viewport.
+  var dialogPosition = self.computePosition($('.overlay'), dialogSize);
+
+  var animationOptions = $.extend(dialogSize, dialogPosition);
+
+  // Perform the resize animation.
+  $('.overlay').animate(animationOptions, 'fast', function () {
+    // Proceed only if the dialog still exists.
+    if ($.isObject(self.iframe.$element) && $.isObject(self.iframe.$container)) {
+      // Resize the iframe element and container.
+      $('.overlay').width(dialogSize.width).height(dialogSize.height);
+      self.iframe.$container.width(frameSize.width).height(frameSize.height);
+      self.iframe.$element.width(frameSize.width).height(frameSize.height);
+
+      // Update the dialog size so that UI internals are aware of the change.
+      self.iframe.$container.dialog('option', { width: dialogSize.width, height: dialogSize.height });
+
+      // Keep the dim background grow or shrink with the dialog.
+      $('.ui-widget-overlay').height($(document).height());
+      
+      // Animate body opacity, so we fade in the page as it loads in. 
+      $(self.iframe.$element.get(0)).contents().find('body.overlay').animate({opacity: 0.9999}, 'slow');
+    }
+  });
+};
+
+/**
+ * Add overlay rendering GET parameter to the given href.
+ */
+Drupal.overlay.addOverlayParam = function (href) {
+  return $.param.querystring(href, {'render': 'overlay'});
+  // Do not process links with an empty href, or that only have the fragment or
+  // which are external links.
+  if (href.length > 0 && href.charAt(0) != '#' && href.indexOf('http') != 0 && href.indexOf('https') != 0) {
+    var fragmentIndex = href.indexOf('#');
+    var fragment = '';
+    if (fragmentIndex != -1) {
+      fragment = href.substr(fragmentIndex);
+      href = href.substr(0, fragmentIndex);
+    }
+    href += (href.indexOf('?') > -1 ? '&' : '?') + 'render=overlay' + fragment;
+  }
+  return href;
+};
+
+/**
+ * Open, reload, or close the overlay, based on the current URL fragment.
+ */
+Drupal.overlay.trigger = function () {
+  // Get the overlay URL from the current URL fragment.
+  var state = $.bbq.getState('overlay');
+  if (state) {
+    // Append render variable, so the server side can choose the right
+    // rendering and add child modal frame code to the page if needed.
+    var linkURL = Drupal.overlay.addOverlayParam(Drupal.settings.basePath + state);
+
+    // If the modal frame is already open, replace the loaded document with
+    // this new one.
+    if (Drupal.overlay.isOpen) {
+      Drupal.overlay.load(linkURL);
+    }
+    else {
+      // There is not an overlay opened yet; we should open a new one.
+      var overlayOptions = {
+        url: linkURL,
+        onOverlayClose: function () {
+          // Clear the overlay URL fragment.
+          $.bbq.pushState();
+          // Remove active class from all header buttons.
+          $('a.overlay-processed').each(function () {
+            $(this).removeClass('active');
+          });
+        },
+        draggable: false
+      };
+      Drupal.overlay.open(overlayOptions);
+    }
+  }
+  else {
+    // If there is no overlay URL in the fragment, close the overlay.
+    try {
+      Drupal.overlay.close();
+    }
+    catch(e) {
+      // The close attempt may have failed because the overlay isn't open.
+      // If so, no special handling is needed here.
+    }
+  }
+};
+
+/**
+ * Make a regular admin link into a URL that will trigger the overlay to open.
+ *
+ * @param link
+ *   A Javascript Link object (i.e. an <a> element).
+ * @return
+ *   A URL that will trigger the overlay (in the form
+ *   /node/1#overlay=admin/config).
+ */
+Drupal.overlay.fragmentizeLink = function (link) {
+  // Don't operate on links that are already overlay-ready.
+  var params = $.deparam.fragment(link.href);
+  if (params.overlay) {
+    return link.href;
+  }
+
+  // Determine the link's original destination, and make it relative to the
+  // Drupal site.
+  var fullpath = link.pathname;
+  var re = new RegExp('^' + Drupal.settings.basePath);
+  var path = fullpath.replace(re, '');
+  // Preserve existing query and fragment parameters in the URL.
+  var fragment = link.hash;
+  var querystring = link.search;
+  // If the query includes ?render=overlay, leave it out.
+  if (querystring.indexOf('render=overlay') !== -1) {
+    querystring = querystring.replace(/render=overlay/, '');
+    if (querystring === '?') {
+      querystring = '';
+    }
+  }
+
+  var destination = path + querystring + fragment;
+
+  // Assemble the overlay-ready link.
+  var base = window.location.href;
+  return $.param.fragment(base, {'overlay':destination});
+}
+
+/**
+ * Make sure the internal overlay URL is reflected in the parent URL fragment.
+ *
+ * Normally the parent URL fragment determines the overlay location. However, if
+ * the overlay redirects internally, the parent doesn't get informed, and the
+ * parent URL fragment will be out of date. This is a sanity check to make
+ * sure we're in the right place.
+ *
+ * @param childLocation
+ *   The child window's location object.
+ */
+Drupal.overlay.syncChildLocation = function (childLocation) {
+  var expected = $.bbq.getState('overlay');
+  // This is just a sanity check, so we're comparing paths, not query strings.
+  expected = Drupal.settings.basePath + expected.replace(/\?.+/, '');
+  var actual = childLocation.pathname;
+  if (expected !== actual) {
+    // There may have been a redirect inside the child overlay window that the
+    // parent wasn't aware of. Update the parent URL fragment appropriately.
+    var newLocation = Drupal.overlay.fragmentizeLink(childLocation);
+    // Set a 'redirect' flag on the new location so the hashchange event handler
+    // knows not to change the overlay's content.
+    $.data(window.location, newLocation, 'redirect');
+    window.location.href = newLocation;
+  }
+};
+
+/**
+ * Refresh any sections of the page that are displayed outside the overlay.
+ *
+ * @param data
+ *   An array of objects with information on the page sections to be refreshed.
+ *   For each object, the key is the id of the DOM element to be refreshed, and
+ *   the value represents the sections of the Drupal $page object to be
+ *   refreshed, in URL argument format.
+ */
+Drupal.overlay.refreshToolbars = function (data) {
+  $.each(data, function () {
+    var toolbar_info = this;
+    $.each(toolbar_info, function (id) {
+      var args = toolbar_info[id];
+      $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + args, function (newElement) {
+        $(id).replaceWith($(newElement));
+        Drupal.attachBehaviors($(id), Drupal.settings);
+      });
+    });
+  });
+};
+
+/**
+ * Theme function to create the overlay iframe element.
+ */
+Drupal.theme.prototype.overlayElement = function () {
+  // Note: We use scrolling="yes" for IE as a workaround to yet another IE bug
+  // where the horizontal scrollbar is always rendered no matter how wide the
+  // iframe element is defined.
+  return '<iframe id="overlay-element" frameborder="0" name="overlay-element"'+ ($.browser.msie ? ' scrolling="yes"' : '') +'/>';
+};
+
+/**
+ * Theme function to create a container for the overlay iframe element.
+ */
+Drupal.theme.prototype.overlayContainer = function () {
+  return '<div id="overlay-container"/>';
+}
+
+/**
+ * Theme function for the overlay title markup.
+ */
+Drupal.theme.prototype.overlayTitleHeader = function (text) {
+  return '<h1 id="ui-dialog-title-overlay-container" class="ui-dialog-title" tabindex="-1" unselectable="on">' + text + '</h1>';
+};
+
+/**
+ * Theme function for the shortcuts button next to the overlay title.
+ */
+Drupal.theme.prototype.overlayShortcutsButton = function (text) {
+  return '<div class="add-to-shortcuts">' + text + '</div>';
+}
+
+})(jQuery);
diff --git modules/overlay/overlay.api.php modules/overlay/overlay.api.php
new file mode 100644
index 0000000..499c157
--- /dev/null
+++ modules/overlay/overlay.api.php
@@ -0,0 +1,46 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Hooks provided by Overlay module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Allow modules to act when an overlay parent window is initialized.
+ *
+ * The parent window is initialized when a page is displayed in which the
+ * overlay might be required to be displayed, so modules can act here if they
+ * need to take action to accomodate the possibility of the overlay appearing
+ * within a Drupal page.
+ */
+function hook_overlay_parent_initialize() {
+  // Add our custom JavaScript.
+  drupal_add_js(drupal_get_path('module', 'hook') . '/hook-overlay.js');
+}
+
+/**
+ * Allow modules to act when an overlay child window is initialized.
+ *
+ * The child window is initialized when a page is displayed from within the
+ * overlay, so modules can act here if they need to take action to work from
+ * within the confines of the overlay.
+ */
+function hook_overlay_child_initialize() {
+  // Use a different theme for content administration pages.
+  if (arg(0) == 'admin' && arg(1) == 'content') {
+    if ($theme = variable_get('content_administration_pages_theme', FALSE)) {
+      global $custom_theme;
+      $custom_theme = $theme;
+    }
+  }
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git modules/overlay/overlay.info modules/overlay/overlay.info
index e69de29..7afc059 100644
--- modules/overlay/overlay.info
+++ modules/overlay/overlay.info
@@ -0,0 +1,8 @@
+; $Id$
+name = Overlay
+description = Displays the Drupal administration interface in an overlay.
+package = Core
+version = VERSION
+core = 7.x
+files[] = overlay.module
+files[] = overlay.install
diff --git modules/overlay/overlay.install modules/overlay/overlay.install
new file mode 100644
index 0000000..e5d4f76
--- /dev/null
+++ modules/overlay/overlay.install
@@ -0,0 +1,14 @@
+<?php
+// $Id$
+
+/**
+ * Implement hook_enable().
+ *
+ * If the module is being enabled through the admin UI, and not from an
+ * install profile, reopen the modules page in an overlay.
+ */
+function overlay_enable() {
+  if (strpos($_GET['q'], 'admin/config/modules') === 0) {
+    drupal_goto('<front>', array('fragment' => 'overlay=admin/config/modules'));
+  }
+}
diff --git modules/overlay/overlay.module modules/overlay/overlay.module
new file mode 100644
index 0000000..189819a
--- /dev/null
+++ modules/overlay/overlay.module
@@ -0,0 +1,562 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Displays the Drupal administration interface in an overlay.
+ */
+
+/**
+ * Implement hook_menu().
+ */
+function overlay_menu() {
+  $items['overlay-ajax'] = array(
+    'title' => '',
+    'page callback' => 'overlay_ajax',
+    'page arguments' => array(1),
+    'access arguments' => array('access overlay'),
+    'type' => MENU_CALLBACK,
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function overlay_permission() {
+  return array(
+    'access overlay' => array(
+      'title' => t('Access the administrative overlay'),
+      'description' => t('View administrative pages in the overlay.'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_page_alter().
+ *
+ * Remove the toolbar when displaying a page within the overlay.
+ */
+function overlay_page_alter(&$page) {
+  if (overlay_get_mode() == 'child') {
+    $page['page_top']['toolbar']['#access'] = FALSE;
+  }
+}
+
+/**
+ * Implement hook_init().
+ *
+ * Determine whether the current page request is destined to appear in the
+ * parent window or in the overlay window, and format the page accordingly.
+ *
+ * @see overlay_set_mode()
+ */
+function overlay_init() {
+  // @todo: custom_theme does not exist anymore.
+  global $custom_theme;
+  // Only act if the user has access to administration pages. Other modules can
+  // also enable the overlay directly for other uses of the JavaScript.
+  if (user_access('access overlay')) {
+    if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
+      // If this page shouldn't be rendered here, redirect to the parent.
+      if (!path_is_admin($_GET['q'])) {
+        overlay_close_dialog();
+      }
+      // If the parent window needs to refresh its toolbars, pass that request
+      // to the client side.
+      if (!empty($_SESSION['overlay_refresh_toolbars'])) {
+        overlay_refresh_toolbars($_SESSION['overlay_refresh_toolbars']);
+      }
+      $admin_theme = variable_get('admin_theme', 0);
+      if ($custom_theme != $admin_theme) {
+        // If system module did not switch the theme yet (i.e. this is not an
+        // admin page, per se), we should switch the theme here.
+        $custom_theme = $admin_theme;
+        drupal_add_css(drupal_get_path('module', 'system') . '/admin.css');
+      }
+      overlay_set_mode('child');
+    }
+    else {
+      // Otherwise add overlay parent code and our behavior.
+      overlay_set_mode('parent');
+      // Let the client side know which paths are administrative.
+      $admin_paths = path_get_admin_paths();
+      $admin_paths = str_replace('<front>', variable_get('site_frontpage', 'node'), $admin_paths);
+      drupal_add_js(array('overlay' => array('adminPaths' => $admin_paths)), 'setting');
+      // Pass along the AJAX callback for rerendering sections of the parent window.
+      drupal_add_js(array('overlay' => array('ajaxCallback' => 'overlay-ajax')), 'setting');
+    }
+  }
+}
+
+/**
+ * Implement hook_element_info_alter().
+ */
+function overlay_element_info_alter(&$types) {
+  foreach (array('submit', 'button', 'image_button', 'form') as $type) {
+    $types[$type]['#after_build'][] = 'overlay_form_after_build';
+  }
+}
+
+/**
+ * Implement hook_library().
+ */
+function overlay_library() {
+  $module_path = drupal_get_path('module', 'overlay');
+
+  // jQuery BBQ plugin.
+  $libraries['jquery-bbq'] = array(
+    'title' => 'jQuery BBQ',
+    'website' => 'http://benalman.com/projects/jquery-bbq-plugin/',
+    'version' => '1.0.2',
+    'js' => array(
+      'misc/jquery.ba-bbq.js',
+    ),
+  );
+  // Overlay parent.
+  $libraries['parent'] = array(
+    'title' => 'Overlay: Parent',
+    'website' => 'http://drupal.org/node/517688',
+    'version' => '1.0',
+    'js' => array(
+      $module_path . '/overlay-parent.js' => array(),
+    ),
+    'css' => array(
+      $module_path . '/overlay-parent.css' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'ui.dialog'),
+    ),
+  );
+  // Overlay child.
+  $libraries['child'] = array(
+    'title' => 'Overlay: Child',
+    'website' => 'http://drupal.org/node/517688',
+    'version' => '1.0',
+    'js' => array(
+      $module_path . '/overlay-child.js' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'ui'),
+    ),
+  );
+
+  return $libraries;
+}
+
+/**
+ * Implement hook_form_alter().
+ * 
+ * For forms displayed in the overlay, add a hidden form field that lets us pass
+ * the parent window's URL into the form.
+ */
+function overlay_form_alter(&$form, &$form_state, $form_id) {
+  if (overlay_get_mode() == 'child') {
+    $form['overlay_parent_url'] = array(
+      '#type' => 'hidden',
+    );
+  }
+}
+
+/**
+ * Implement hook_drupal_goto_alter().
+ *
+ * If the current page request is inside the overlay, add ?render=overlay to
+ * the new path, so that it appears correctly inside the overlay.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) {
+  if (overlay_get_mode() == 'child') {
+    if (isset($options['query'])) {
+      $options['query'] += array('render' => 'overlay');
+    }
+    else {
+      $options['query'] = array('render' => 'overlay');
+    }
+  }
+}
+
+/**
+ * Implement hook_batch_alter().
+ *
+ * If the current page request is inside the overlay, add ?render=overlay to
+ * the success callback URL, so that it appears correctly within the overlay.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_batch_alter(&$batch) {
+  if (overlay_get_mode() == 'child') {
+    if (isset($batch['url_options']['query'])) {
+      $batch['url_options']['query']['render'] = 'overlay';
+    }
+    else {
+      $batch['url_options']['query'] = array('render' => 'overlay');
+    }
+  }
+}
+
+/**
+ * Implement hook_block_info_alter().
+ *
+ * If the current page request is inside the overlay, don't display extraneous
+ * blocks.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_block_info_alter(&$blocks) {
+  // @todo: custom_theme does not exist anymore.
+  global $custom_theme;
+
+  if (!empty($custom_theme) && overlay_get_mode() == 'child') {
+    $themes = list_themes();
+    $theme = $themes[$custom_theme];
+    if (!empty($theme->info['overlay_regions'])) {
+      // Don't show any blocks except the main page content and the help text if
+      // we're in the overlay.
+      foreach ($blocks as $bid => $block) {
+        if (!in_array($block->region, $theme->info['overlay_regions'])) {
+          unset($blocks[$bid]);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implement hook_system_info_alter().
+ *
+ * Add default regions for overlay.
+ */
+function overlay_system_info_alter(&$info, $file) {
+  $info['overlay_regions'][] = 'content';
+  $info['overlay_regions'][] = 'help';
+}
+
+/**
+ * Preprocess template variables for html.tpl.php.
+ *
+ * If the current page request is inside the overlay, add appropriate classes
+ * to the <body> element, and simplify the page title.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_preprocess_html(&$variables) {
+  if (overlay_get_mode() == 'child') {
+    // Add overlay class, so themes can react to being displayed in the overlay.
+    $variables['classes_array'][] = 'overlay';
+    // Do not include site name or slogan in the overlay title.
+    $variables['head_title'] = drupal_get_title();
+  }
+}
+
+/**
+ * Preprocess template variables for page.tpl.php.
+ *
+ * Display breadcrumbs correctly inside the overlay.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_preprocess_page(&$variables) {
+  if (overlay_get_mode() == 'child') {
+    // Remove 'Home' from the breadcrumbs.
+    $overlay_breadcrumb = drupal_get_breadcrumb();
+    array_shift($overlay_breadcrumb);
+    $variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => $overlay_breadcrumb));
+  }
+}
+
+/**
+ * Preprocess template variables for toolbar.tpl.php.
+ *
+ * Adding the 'overlay-displace-top' class to the toolbar pushes the overlay
+ * down, so it appears below the toolbar.
+ */
+function overlay_preprocess_toolbar(&$variables) {
+  $variables['classes_array'][] = "overlay-displace-top";
+}
+
+/**
+ * Form after_build callback.
+ *
+ * After all hook_form_alter() implementations have been processed, we look at
+ * the list of submit handlers and add our own at the end. The added handler
+ * determines whether or not the user is redirected done at the end of form
+ * processing, so that it's possible to close the overlay after submitting
+ * a form.
+ *
+ * @see _form_builder_handle_input_element()
+ * @see _form_builder_ie_cleanup()
+ * @see form_execute_handlers()
+ * @see form_builder()
+ * @see overlay_form_submit()
+ *
+ * @ingroup forms
+ */
+function overlay_form_after_build($form, &$form_state) {
+  if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
+    // Form API may have already captured submit handlers from the submitted
+    // button before after_build callback is invoked. This may have been done
+    // by _form_builder_handle_input_element(). If so, the list of submit
+    // handlers is stored in the $form_state array, which is something we can
+    // also alter from here, luckily. Rememeber: our goal here is to set
+    // $form_state['redirect'] to FALSE if the API function
+    // overlay_request_dialog_close() has been invoked. That's because we want
+    // to tell the parent window to close the overlay.
+    if (!empty($form_state['submit_handlers']) && !in_array('overlay_form_submit', $form_state['submit_handlers'])) {
+      $form_state['submit_handlers'][] = 'overlay_form_submit';
+    }
+    // If this element has submit handlers, then append our own.
+    if (isset($form['#submit'])) {
+      $form['#submit'][] = 'overlay_form_submit';
+    }
+  }
+  return $form;
+}
+
+/**
+ * Generic form submit handler.
+ *
+ * When we are requested to close an overlay, we don't want Form API to
+ * perform any redirection once the submitted form has been processed.
+ *
+ * When $form_state['redirect'] is set to FALSE, then Form API will simply
+ * re-render the form with the values still in its fields. And this is all
+ * we need to output the JavaScript that will tell the parent window to close
+ * the child dialog.
+ *
+ * @see overlay_get_mode()
+ * @ingroup forms
+ */
+function overlay_form_submit($form, &$form_state) { 
+  $settings = &drupal_static(__FUNCTION__);
+
+  // Check if we have a request to close the overlay.
+  $args = overlay_request_dialog_close();
+
+  // Close the overlay if the overlay module has been disabled
+  if (!module_exists('overlay')) {
+    $args = overlay_request_dialog_close(TRUE);
+  }
+
+  // If there is a form redirect to a non-admin page, close the overlay.
+  if (isset($form_state['redirect'])) {
+    // A destination set in the URL trumps $form_state['redirect'].
+    if (isset($_GET['destination'])) {
+      $url = $_GET['destination'];
+      $url_settings = array();
+    }
+    elseif (is_array($form_state['redirect'])) {
+      $url = $form_state['redirect'][0];
+      $url_settings = $form_state['redirect'][1];
+    }
+    else {
+      $url = $form_state['redirect'];
+      $url_settings = array();
+    }
+    if (!path_is_admin($url)) {
+      $args = overlay_request_dialog_close(TRUE);
+    }
+  }
+
+  // If the overlay is to be closed, pass that information through JavaScript.
+  if ($args !== FALSE) {
+    if (!isset($settings)) {
+      $settings = array(
+        'overlayChild' => array(
+          'closeOverlay' => TRUE,
+          'statusMessages' => theme('status_messages'),
+          'args' => $args,
+        ),
+      );
+      // Tell the child window to perform the redirection when requested to.
+      if (!empty($form_state['redirect'])) {
+        $settings['overlayChild']['redirect'] = url($url, $settings);
+      }
+      // If the redirect destination is the same as the parent window, just
+      // close the overlay without redirecting the parent.
+      if (url($form['overlay_parent_url']['#value']) == $settings['overlayChild']['redirect']) {
+        unset($settings['overlayChild']['redirect']);
+      }
+      drupal_add_js($settings, array('type' => 'setting'));
+    }
+    // Tell FAPI to redraw the form without redirection after all submit
+    // callbacks have been processed.
+    $form_state['redirect'] = FALSE;
+  }
+}
+
+/**
+ * Get the current overlay mode.
+ *
+ * @see overlay_set_mode()
+ */
+function overlay_get_mode() {
+  return overlay_set_mode(NULL);
+}
+
+/**
+ * Set overlay mode and add proper JavaScript and styles to the page.
+ *
+ * @param $mode
+ *   To set the mode, pass in either 'parent' or 'child'. 'parent' is used in
+ *   the context of a parent window (a regular browser window), and JavaScript
+ *   is added so that administrative links in the parent window will open in
+ *   an overlay. 'child' is used in the context of the child overlay window (the
+ *   page actually appearing within the overlay iframe) and JavaScript and CSS
+ *   are added so that Drupal behaves nicely from within the overlay.
+ *
+ *   This parameter is optional, and if omitted, the current mode will be
+ *   returned with no action taken.
+ * @return
+ *   The current mode, if any has been set, or NULL if no mode has been set.
+ * @ingroup overlay_api
+ */
+function overlay_set_mode($mode = NULL) {
+  global $base_path;
+  $overlay_mode = &drupal_static(__FUNCTION__);
+
+  // Make sure external resources are not included more than once. Also return
+  // current mode, if no mode was specified.
+  if (isset($overlay_mode) || !isset($mode)) {
+    return $overlay_mode;
+  }
+  $overlay_mode = $mode;
+
+  switch ($overlay_mode) {
+    case 'parent':
+      drupal_add_library('overlay', 'parent');
+      drupal_add_library('overlay', 'jquery-bbq');
+
+      // Allow modules to act upon overlay events.
+      module_invoke_all('overlay_parent_initialize');
+      break;
+
+    case 'child':
+      drupal_add_library('overlay', 'child');
+
+      // Allow modules to act upon overlay events.
+      module_invoke_all('overlay_child_initialize');
+      break;
+  }
+  return $overlay_mode;
+}
+
+/**
+ * Callback to request that the overlay close on the next page load.
+ *
+ * @param $value
+ *   By default, the dialog will not close. Set to TRUE or a value evaluating to
+ *   TRUE to request the dialog to close. Use FALSE to disable closing the
+ *   dialog (if it was previously enabled). The value passed will be forwarded
+ *   to the onOverlayClose callback of the overlay.
+ * @return
+ *   The current overlay close dialog mode, a value evaluating to TRUE if the
+ *   overlay should close or FALSE if it should not (default).
+ */
+function overlay_request_dialog_close($value = NULL) {
+  $close = &drupal_static(__FUNCTION__, FALSE);
+  if (isset($value)) {
+    $close = $value;
+  }
+  return $close;
+}
+
+/**
+ * Close the overlay and redirect the parent window to a new path.
+ *
+ * @param $redirect
+ *   The path that should open in the parent window after the overlay closes.
+ */
+function overlay_close_dialog($redirect = NULL) {
+  if (empty($redirect)) {
+    $path = $_GET['q'];
+  }
+  $settings = array(
+    'overlayChild' => array(
+      'closeOverlay' => TRUE,
+      'statusMessages' => theme('status_messages'),
+      'args' => $args,
+      'redirect' => url($redirect),
+    ),
+  );
+  drupal_add_js($settings, array('type' => 'setting'));
+  return $settings;
+}
+
+/**
+ * Request that the parent window refresh its toolbars on the next page load.
+ *
+ * @param $id
+ *   The CSS ID of the page element to replace (e.g. '#toolbar').
+ * @param $args
+ *   The section of the $page array to render and replace, formatted as URL
+ *   arguments. For example, to request that $page['page_top']['#toolbar'] be
+ *   rerendered, the caller would pass 'page_top/toolbar' as the $args
+ *   parameter.
+ *
+ * @see overlay_refresh_toolbars()
+ * @see Drupal.overlay.refreshToolbars()
+ */
+function overlay_request_toolbar_refresh($id, $args) {
+  if (!isset($_SESSION['overlay_refresh_toolbars']) || !is_array($_SESSION['overlay_refresh_toolbars'])) {
+    $_SESSION['overlay_refresh_toolbars'] = array();
+  }
+  $_SESSION['overlay_refresh_toolbars'][] = array($id => $args);
+}
+
+/**
+ * Request that the parent window refresh its toolbars.
+ *
+ * If the parent window's toolbars need to be reloaded on this page load, pass
+ * that request via JavaScript to the child window, so it can in turn pass the
+ * request to the parent window.
+ *
+ * @see overlay_request_toolbar_refresh()
+ */
+function overlay_refresh_toolbars($paths) {
+  $settings = array(
+    'overlayChild' => array(
+      'refreshToolbars' => $paths,
+    ),
+  );
+  drupal_add_js($settings, array('type' => 'setting'));
+  unset($_SESSION['overlay_refresh_toolbars']);
+}
+
+/**
+ * Return markup for any sections of the page that should be refreshed.
+ *
+ * This function is intended to be called via AJAX, from
+ * Drupal.overlay.refreshToolbars(). The parts of the page array to be rendered
+ * are determined by the URL. A request for overlay-ajax/page_top/toolbar will
+ * render $page['page_top']['toolbar'].
+ */
+function overlay_ajax() {
+  // Build a renderable page array, parts of which will be rendered and
+  // returned.
+  $page = &drupal_static(__FUNCTION__);
+  if (!isset($page)) {
+    $page = element_info($page);
+    foreach (module_implements('page_build') as $module) {
+      $function = $module . '_page_build';
+      $function($page);
+    }
+    drupal_alter('page', $page);
+  }
+
+  // Determine which sections of the $page object to render, by examining the
+  // URL arguments.
+  $args = explode('/', $_GET['q']);
+  unset($args[0]);
+
+  // Pull from the $page array just those sections that the client wants to
+  // rerender.
+  $to_render = $page;
+  foreach ($args as $key) {
+    $to_render = $to_render[$key];
+  }
+
+  $output = drupal_render($to_render);
+  print $output;
+}
diff --git modules/shortcut/shortcut.admin.inc modules/shortcut/shortcut.admin.inc
index bb894c0..478f971 100644
--- modules/shortcut/shortcut.admin.inc
+++ modules/shortcut/shortcut.admin.inc
@@ -232,6 +232,9 @@ function shortcut_set_customize_submit($form, &$form_state) {
       menu_link_save($link);
     }
   }
+  if (module_exists('overlay')) {
+    overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+  }
   drupal_set_message(t('The shortcut set has been updated.'));
 }
 
@@ -508,6 +511,9 @@ function shortcut_link_delete_submit($form, &$form_state) {
   menu_link_delete($shortcut_link['mlid']);
   $form_state['redirect'] = 'admin/config/system/shortcut/' . $shortcut_link['menu_name'];
   drupal_set_message(t('The shortcut %title has been deleted.', array('%title' => $shortcut_link['link_title'])));
+  if (module_exists('overlay')) {
+    overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+  }
 }
 
 /**
diff --git modules/shortcut/shortcut.css modules/shortcut/shortcut.css
index dee5d06..c741a18 100644
--- modules/shortcut/shortcut.css
+++ modules/shortcut/shortcut.css
@@ -7,7 +7,6 @@ div#toolbar div.toolbar-shortcuts ul {
   padding: 5px 0;
   height: 40px;
   line-height: 30px;
-  overflow: hidden;
   float: left;
   margin-left:5px;
 }
diff --git modules/shortcut/shortcut.module modules/shortcut/shortcut.module
index 597c70c..bcb91b0 100644
--- modules/shortcut/shortcut.module
+++ modules/shortcut/shortcut.module
@@ -268,6 +268,12 @@ function shortcut_set_save(&$shortcut_set) {
       $return = SAVED_UPDATED;
     }
   }
+  // If the overlay module is enabled, let it know that the shortcut bar needs
+  // to be refreshed on the next page load.
+  if (module_exists('overlay')) {
+    overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+  }
+
   return $return;
 }
 
@@ -298,6 +304,11 @@ function shortcut_set_delete($shortcut_set) {
   $deleted = db_delete('shortcut_set')
     ->condition('set_name', $shortcut_set->set_name)
     ->execute();
+  // If the overlay module is enabled, let it know that the shortcut bar needs
+  // to be refreshed on the next page load.
+  if (module_exists('overlay')) {
+    overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+  }
   return (bool) $deleted;
 }
 
@@ -333,6 +344,11 @@ function shortcut_set_assign_user($shortcut_set, $account) {
     ->key(array('uid' => $account->uid))
     ->fields(array('set_name' => $shortcut_set->set_name))
     ->execute();
+  // If the overlay module is enabled, let it know that the shortcut bar needs
+  // to be refreshed on the next page load.
+  if (module_exists('overlay')) {
+    overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+  }
 }
 
 /**
@@ -351,6 +367,11 @@ function shortcut_set_unassign_user($account) {
   $deleted = db_delete('shortcut_set')
     ->condition('uid', $account->uid)
     ->execute();
+  // If the overlay module is enabled, let it know that the shortcut bar needs
+  // to be refreshed on the next page load.
+  if (module_exists('overlay')) {
+    overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+  }
   return (bool) $deleted;
 }
 
diff --git modules/system/system.module modules/system/system.module
index 3aa6873..0f66716 100644
--- modules/system/system.module
+++ modules/system/system.module
@@ -3598,3 +3598,13 @@ function system_build_contextual_links($element) {
   return $build;
 }
 
+/**
+ * Implement hook_admin_paths().
+ */
+function system_admin_paths() {
+  $paths = array(
+    'admin',
+    'admin/*',
+  );
+  return $paths;
+}
diff --git modules/toolbar/toolbar.css modules/toolbar/toolbar.css
index 7aa097d..8083e7f 100644
--- modules/toolbar/toolbar.css
+++ modules/toolbar/toolbar.css
@@ -36,7 +36,7 @@ div#toolbar {
   left: 0;
   right: 0;
   top: 0;
-  z-index: 100;
+  z-index: 600;
 }
 
 div#toolbar .collapsed {
@@ -70,7 +70,6 @@ div#toolbar div.toolbar-menu {
   height: 25px;
   line-height: 20px;
   padding: 5px 10px 0;
-  overflow: hidden;
   position: relative;
 }
 
diff --git modules/toolbar/toolbar.js modules/toolbar/toolbar.js
index 49cf158..7feab2c 100644
--- modules/toolbar/toolbar.js
+++ modules/toolbar/toolbar.js
@@ -15,6 +15,16 @@ Drupal.behaviors.admin = {
       Drupal.admin.toolbar.toggle();
       return false;
     });
+
+    // Set the most recently clicked item as active.
+    $('#toolbar a').once().click(function() {
+      $('#toolbar a').each(function() {
+        $(this).removeClass('active');
+      });
+      if ($(this).parents('div.toolbar-shortcuts').length) {
+        $(this).addClass('active');
+      }
+    });
   }
 };
 
diff --git modules/toolbar/toolbar.module modules/toolbar/toolbar.module
index 26a0bc1..d66bd6a 100644
--- modules/toolbar/toolbar.module
+++ modules/toolbar/toolbar.module
@@ -201,3 +201,60 @@ function toolbar_in_active_trail($path) {
   }
   return in_array($path, $active_paths);
 }
+
+/**
+ * Implement hook_menu_link_update().
+ *
+ * If the overlay module is enabled, let it know that the toolbar needs to be
+ * refreshed on the next page load.
+ */
+function toolbar_menu_link_update($link) {
+  if ($link['menu_name'] == 'management') {
+    // If the parent to this link is the "Administration" section of the menu,
+    // signal that one of the toolbar links was changed.
+    $admin_mlid = db_query("SELECT mlid FROM {menu_links} WHERE menu_name = 'management' AND module = 'system' AND link_path = 'admin'")->fetchField();
+    if ($link['plid'] == $admin_mlid) {
+      if (module_exists('overlay')) {
+        overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+      }
+    }
+  }
+}
+
+/**
+ * Implement hook_menu_link_insert().
+ *
+ * If the overlay module is enabled, let it know that the toolbar needs to be
+ * refreshed on the next page load.
+ */
+function toolbar_menu_link_insert($link) {
+  if ($link['menu_name'] == 'management') {
+    // If the parent to this link is the "Administration" section of the menu,
+    // signal that one of the toolbar links was changed.
+    $admin_mlid = db_query("SELECT mlid FROM {menu_links} WHERE menu_name = 'management' AND module = 'system' AND link_path = 'admin'")->fetchField();
+    if ($link['plid'] == $admin_mlid) {
+      if (module_exists('overlay')) {
+        overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+      }
+    }
+  }
+}
+
+/**
+ * Implement hook_menu_link_delete().
+ *
+ * If the overlay module is enabled, let it know that the toolbar needs to be
+ * refreshed on the next page load.
+ */
+function toolbar_menu_link_delete($link) {
+  if ($link['menu_name'] == 'management') {
+    // If the parent to this link is the "Administration" section of the menu,
+    // signal that one of the toolbar links was changed.
+    $admin_mlid = db_query("SELECT mlid FROM {menu_links} WHERE menu_name = 'management' AND module = 'system' AND link_path = 'admin'")->fetchField();
+    if ($link['plid'] == $admin_mlid) {
+      if (module_exists('overlay')) {
+        overlay_request_toolbar_refresh('#toolbar', 'page_top/toolbar');
+      }
+    }
+  }
+}
diff --git profiles/default/default.info profiles/default/default.info
index 757eed6..76e1d1f 100644
--- profiles/default/default.info
+++ profiles/default/default.info
@@ -16,6 +16,7 @@ dependencies[] = dblog
 dependencies[] = search
 dependencies[] = shortcut
 dependencies[] = toolbar
+dependencies[] = overlay
 dependencies[] = field_ui
 dependencies[] = file
 dependencies[] = rdf
diff --git themes/garland/style.css themes/garland/style.css
index 41a09ff..3575a63 100644
--- themes/garland/style.css
+++ themes/garland/style.css
@@ -508,6 +508,16 @@ body.two-sidebars .region-footer {
   font-size: 1.5em;
 }
 
+/* Don't display any header elements when within the overlay, and adjust the
+   page height accordingly. */
+body.overlay #header * {
+  display: none;
+}
+
+body.overlay {
+  margin-top: -80px;
+}
+
 #wrapper #container #header h1 a:hover {
   text-decoration: none;
 }
diff --git themes/seven/style.css themes/seven/style.css
index bd668e0..2e72826 100644
--- themes/seven/style.css
+++ themes/seven/style.css
@@ -55,7 +55,7 @@ legend {
  */
 #branding {
   overflow: hidden;
-  padding: 20px 40px 0 40px;
+  padding: 20px 20px 0 20px;
   position: relative;
   background-color: #e0e0d8;
 }
@@ -115,7 +115,7 @@ legend {
  */
 div.messages {
   padding: 9px;
-  margin: 1em 0;
+  margin: 0.5em 0 0;
   color: #036;
   background: #bdf;
   border: 1px solid #ace;
@@ -158,9 +158,8 @@ div.status {
 /**
  * Console.
  */
-#page .console {
-  border-top: 1px solid #ccc;
-  padding: 9px 0 10px;
+#console {
+  margin: 9px 0 10px;
 }
 
 /**
@@ -549,7 +548,7 @@ body div.form-type-radio div.description, body div.form-type-checkbox div.descri
 }
 
 /* Buttons */
-input.form-submit {
+input.form-submit, a.button {
   cursor: pointer;
   padding: 4px 17px;
   color: #5a5a5a;
@@ -566,6 +565,11 @@ input.form-submit {
   font-size: 1.1em;
 }
 
+a.button:link, a.button:visited, a.button:hover, a.button:active {
+  text-decoration: none;
+  color: #5a5a5a;
+}
+
 div.node-form input#edit-submit,
 div.node-form input#edit-submit-1 {
   border: 1px solid #8eB7cd;
@@ -618,9 +622,14 @@ html.js input.throbbing {
 
 ul.action-links {
   margin: 1em 0;
+  padding: 0 20px 0 20px;
   overflow: hidden;
 }
 
+#block-system-main ul.action-links {
+  padding: 0;
+}
+
 ul.action-links li {
   float: left;
   margin: 0 1em 0 0;
@@ -716,24 +725,34 @@ div.admin-options div.form-item {
 }
 
 /* Overlay theming */
-body.overlay {
-  background: #fff;
+.overlay #branding {
+  background-color: #fff;
+  padding-top: 15px;
 }
-
-body.overlay #branding,
-body.overlay #page-title,
-body.overlay #page #left,
-body.overlay #page #footer {
+.overlay .primary,
+.overlay #branding h1.page-title,
+.overlay #page #left,
+.overlay #page #footer {
   display: none;
 }
-
-body.overlay #page {
+.overlay #page {
   margin: 0;
-  padding: 0;
 }
-
-body.overlay #block-system-main {
-  padding: 20px;
+.overlay #branding div.breadcrumb {
+  float: left;
+  position: relative;
+  z-index: 10;
+}
+.overlay ul.secondary {
+  background: transparent none;
+  margin: -2.4em 0 0;
+  padding: 3px 10px;
+}
+.overlay #content {
+  padding: 0 20px;
+}
+.overlay #block-system-main {
+  padding: 0;
 }
 
 /* Shortcut theming */
