(function() {

IE = !!(typeof document != 'undefined' && document.expando && document.uniqueID);

Class = function() {
  function klass() {
    this.klass = klass;
    
    if (this.initialize)
      this.initialize.apply(this, arguments);
  };
  
  for (var i = 0; i < arguments.length; i++)
    cp(klass.prototype, arguments[i].prototype || arguments[i]);

  return klass;
}

cp = copy = function(o) {
  for (var i = 1, vs = $A(arguments); i < vs.length; i++) {
    for (var id in vs[i])
      o[id] = vs[i][id];
    if (IE)
      if (vs[i].toString.toString().indexOf('[native code]') == -1)
        o.toString = vs[i].toString;
  }
  return o;
}; 

boot = function() {
  var node;
  
  if (node = document.getElementById('template'))
    template = load(node)
  if (node = document.getElementById('content'))
    content = load(node)
  else
    load(document.body);
};

map = function() {
  var data = {}, list = typeof arguments[0] == 'string' ? arguments : arguments[0];
  for (var i = 0; i < list.length; i++)
    data[list[i]] = true;
  return data;
};

items = function(name, list) {
  var items = [];
  if (list.constructor == Array)
    for (var i = 0; i < list.length; i++)
      items.push([name, list[i]]);
  else
    for (var item in list)
      items.push([name, item]);
  return items;
};

offset = function(source, id) {
  var k = 0;
  source = source.node || source;
  do {
    k     += source['offset' + id] || 0;
    source = source.offsetParent;
  } while (source);

  return k;
};

// borrowed from Prototype:
$A = function(v) {
  if (!v)
    return [];
  var n = v.length || 0, vs = new Array(n);
  while (n--)
    vs[n] = v[n];
  return vs;
};

new function() {
  
var ATTR = {};
var WRAP = {
  'td': 'tr', 'tr': 'tbody', 'tbody': 'table',
  'li': 'ul',
  'option': 'select'
};

//cp(ATTR, map('class', 'name', 'id'));
cp(ATTR, map('style', 'src', 'width', 'height', 'alt'));
cp(ATTR, map('href', 'rel', 'content', 'title', 'id'));
cp(ATTR, map('action', 'method', 'enctype', 'target', 'name', 'value', 'type'));
cp(ATTR, map('rowspan', 'colspan', 'cellspacing', 'cellpadding'));
cp(ATTR, map('classid', 'codebase', 'autoplay', 'controller', 'pluginspage', 'flashvars', 'wmode', 'quality'));
cp(ATTR, map('onclick'));

var CLOSING = map('img', 'br', 'hr', 'link');

tag = function(id, o) {
  o = o || {};
  id = id.replace(/:\d+/, '');

  var name, first, classes = [], attr = {}, inner = '', data;
  
  var sep, s = '';
  
  function word() {
    if (sep == '.') {
      classes.push(s);
      first = first || s;
    } else if (sep == '#') {
      first = first || s;
      attr['id'] = s;
    } else
      name = s || 'div';
    s = '';
    sep = '';
  }
  
  for (var ch, i = 0; i < id.length; i++)
    if ((ch = id.charAt(i)) == '.' || ch == '#') {
      word();
      sep = ch;
    } else
      s += ch;
  
  word();
   
  if (typeof o != 'object')
    data = o + '';
  else
    for (var id in o)
      switch (id) {
        case 'data':
          data = o[id]; break;
        case 'inner':
          inner = o[id]; break;
        case 'classes':
          classes = classes.concat(o[id]); break;
        default: {
          switch (o[id]) {
            case true:
              classes.push(id); break;
            case false:
              break
            default:
              if (ATTR[id])
                attr[id] = encode(o[id].toString().replace(/^function\s*\(\)\s*\{\s*/, '').replace(/\s*}$/, ''));
              else 
                inner += tag(id, o[id]);
          }
        }
      }
  
  if (classes.length > 0)
    attr['class'] = classes.join(' ');
  
  if (data)
    inner = encode(data);
  
  var s = '';
  
  s += '<' + name;
  for (var id in attr)
    s += ' ' + id.toLowerCase() + '="' + attr[id] + '"';
  if (CLOSING[name])
    s += ' />';
  else
    s += '>' + inner + '</' + name + '>';
  
  return s;
}

function encode(s) {
	if (/["\\\x00-\x1f&<>"']/.test(s))
	  s = s.replace(/([\x00-\x1f\\"&<>"'])/g, function(a, b) {
	    switch(b){
	      case '&':  return '&amp;';
	      case '<':  return '&lt;';
	      case '>':  return '&gt;';
	      case '"':  return '&quot;';
	      case '\'': return '&#39;';
	    }
	    c = b.charCodeAt();
	    return '&#x00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16) + ";";
	  });
	return s;
};

tags = function(id, list, f) {
  var s = '';
  for (var iid, i = 0; i < list.length; i++) {
    iid = id;
    if (i == 0)
      iid += '.first';
    if (i == list.length - 1)
      iid += '.last';
    s += tag(iid, f ? f(list[i], i) : list[i]);
  }
  return { inner: s };
  //return s;
}

build = function(a, b) {
  var node, s, n = 1;
  if (b || a.indexOf('<') == -1) {
    var name = a.slice(0, a.indexOf('.')), s = tag(a, b);
    while (WRAP[name]) {
      s = tag(WRAP[name], s);
      name = WRAP[name];
      n++;
    }
  } else
    s = a;
    
  if (document.e4xNode)
    node = document.createElement('div');
  else
    node = build.node = build.node || document.createElement('div');

  node.innerHTML = s;
  
  while (n-- > 0)
    node = node.firstChild;
    
  return node;
}

}

// A controller for commands sent from the UI
Component = Class({
   
  fire: function() {
    var com = this, v;
    do
      v = com.exec.apply(com, arguments);
    while (v !== true && v !== false && (com = com.parent));
    return v;
  },
  
  exec: function(cmd) {
    var name, values = [];
    
    if (cmd.indexOf(' ') == -1) {
      name = Component.Translations[cmd] || cmd;
      name = 'on' + name.charAt(0).toUpperCase() + name.substring(1);
    } else
      name = cmd;
    
    if (typeof this[name] == 'function') {
      for (var j = 1; j < arguments.length; j++)
        values.push(arguments[j]);

      try {
        return this[name].apply(this, values);
      } catch (e) {
        throw this.name + '#' + name + ': ' + (e.message || e);
      }      
    }
  },
  
  start: function(callback, period) {
    var com = this, id = setInterval(function() {
      if (callback.apply(com) === false)
        clearInterval(id);
    }, period || 20);
    return this;
  }
});

Component.Translations = {
  mousedown: 'mouseDown',
  mouseup:   'mouseUp',
  mousemove: 'mouseMove',
  keypress:  'keyPress',
  keyup:     'keyUp',
  keydown:   'keyDown',
  backspace: 'backSpace'
}


template = null;
content  = null;

n = 0;
var cons = {};

Container = Class(Component, {
  
  initialize: function(name, node, flags, parent) {
    this.name  = name;
    this.data  = node.textContent;
    this.flags = flags || {};
    this.setHandlers(this.node = node);
      
    if (parent)
      this.parent = this[parent.name] = parent;
        
    Com[name] = Com[name] || { instances: [] };
    Com[name].instances.push(this);
    //this._i = Com[name].instances.length + 1;
    
    cons[ this.node._cid = ++n ] = this;
  },
  
  //observe: function(type, f) {
  observe: function(type, b, c) {
    var node = c ? b : this.node;
    var f = c || b;
    var id = id = 'on' + type;
    var prev = node[id]
    var com = this;
    node[id] = function(event) {
      return f.call(com, event || window.event);
    }
    return this;
  },
  
  handle: function(event) {
    var x = event[IE ? 'returnValue' : 'v'];
    if (x === true || x === false)
      return x;

    var v, com, id = Component.Translations[event.type] || event.type, node = event.target || event.srcElement;
    do {
      for (var name in this)
        if (this[name] == node || (this[name] || {}).node == node) {
          v = this.exec(name == 'node' ?
            id : id + name.charAt(0).toUpperCase() + name.substring(1), event);
          if (v === true || v === false)
            return event[IE ? 'returnValue' : 'v'] = v;
        }
      if (com = this.find(function() { return this.node == node }))
        for (var name in com.flags)
          this.exec(id + name.charAt(0).toUpperCase() + name.substring(1), event);
    }
    while ((node = node.parentNode) && node != this.node.parentNode);

    return v;
  },
  
  handlers: function() {
    var map = {};
    for (var name in this)
      if (typeof this[name] == 'function')
        if (match = name.match(/^(on(abort|beforeunload|blur|change|click|dblclick|error|focus|keydown|keypress|keyup|load|mousedown|mousemove|mouseout|mouseover|mouseup|reset|resize|select|submit|unload))/i))
          map[match[1].toLowerCase()] = true;
    return map;
  },
  
  setHandlers: function(node, map) {
    map = map || this.klass.handlers;
    
    var com = this, handle = function(event) {
      return com.handle(event || window.event);
    };
    for (var name in map)
      if (map[name])
        node[name] = handle;    
  },
  
  // Replace sub tree using HTML string or tag object, generating components
  reset: function(o) {
    return this.empty().send(function() {
      var s = '';
      if (typeof o == 'string')
        s = o;
      else
        for (var id in o)
          s += tag(id, o[id]);
      this.node.innerHTML = s;
      this.load();
    });
  },
  
  update: function(v) {
    return this.empty().sets(v);
  },
  
  // Set the value of an attribute on the container
  set: function(name, v) {
    name = name.toLowerCase();
    if (name != 'class' && name != 'id')
      v ? this.node.setAttribute(name, v) : this.node.removeAttribute(name);
    return this;
  },
  
  // Read the value of an attribute on the container
  read: function(name) {
    if (!name)
      return this.reads().join('');

    name = name.toLowerCase();
    if (name == 'class')
      return this.node.className;

    return this.node.getAttribute(name, 2) || '';
  },
  
  /*
    Updates the text content or value of leaf elements and text input elements in document order within the subtree.
    Accepts any number of string arguments.
    Examples:
    load('<div class="x"> <label></label> <a href="#"><strong></strong></a> </div>').sets('One', 'Two')
      // <div class="x"> <label>One</label> <a href="#"><strong>Two</strong></a> </div>    
  */
  sets: function() {
    var i = 0, list = arguments;

    this._labels(function(element) {
      if (i == list.length)
        return true;
      if (element.type == 'text')
        element.value = list[i++];
      else
        (element.firstChild || element.appendChild(element.ownerDocument.createTextNode(''))).data = list[i++];
    });
    return this;
  },
  
  // Returns an array of document ordered values for leaf element text and input values
  reads: function() {
    var data = [];
    this._labels(function(element) {
      data.push((element.firstChild ? element.firstChild.data : element.value) || '');
    });
    return data;
  },

  _labels: function(callback) {
    var v, excludes = { IMG: true, BR: true, HR: true };
    visit(this.node);
    return v;
    
    function visit(node) {
      if (typeof v == 'undefined' && node.nodeType == 1 && !excludes[node.tagName]) {
        var nodes = node.childNodes;
        if (nodes.length == 0 || (nodes.length == 1 && nodes[0].nodeType == 3))
          return v = callback(node);
        for (var i = 0; i < nodes.length; i++)
          visit(nodes[i]);        
      }
    }
  },
  
  select: function(v) {
    var flagged = this.selected === true || this.selected === false;
    
    var a = flagged ? this.find('selected') : this.selected;
    var b = v && !v.name ? this.find(v) : v;
    
    if (a != b) {
      if (a)
        a.clear('selected');
      if (b)
        b.apply('selected');
      if (!flagged)
        this.selected = b;
    }
    return b;
  },
    
  toggle: function(v, name) {
    name = name || 'on';
    if (arguments.length == 0)
      v = !this.flags[name];
    return v ? this.apply(name) : this.clear(name);
  },
  
  apply: function(name) {
    if (name == this.name)
      throw new Error('Attempting to assign .' + name + ' as a flag when it is the component name');
      
    this.flags[name] = true;

    if (typeof this[name] != 'function' &&
        typeof this[name] != 'object')
      this[name] = true;

    return this.updateNames();
  },
  
  clear: function(name) {
    if (name == this.node.id)
      return this;

    this.flags[name] = false;

    if (typeof this[name] == 'boolean')
      this[name] = false;

    return this.updateNames();
  },
    
  updateNames: function() {
    var parts = [];
        
    for (var name in this.flags)
      if (this.flags[name])
        parts.unshift(name);
    
    if (this.name != this.node.id)
      parts.push(this.name);
    
    this.node.className = parts.join(' ');    
    return this;
  },
  
  //[dep]
  ammend: function(callback) {
    callback.apply(this);
    return this;
  },
  
  fills: function(list, callback) {
    this.empty();
    if (list.constructor == Array || list.length)
      for (var i = 0; i < list.length; i++)
        callback.call(this, list[i], i);
    else
      for (var id in list)
        callback.call(this, list[id], id);
    return this;
  },
  
  append: function(name) {
    return this.insert(name);
  },
  
  fill: function(a, b, debug) {
    this.empty();
    
    var name = b ? a : false, list = b || a;

    for (var com, values, i = 0; i < list.length; i++) {
      if (typeof list[i] == 'string')
        list[i] = [list[i]];

      com    = this.add(name || list[i][0]);
      values = name ? list[i] : list[i].slice(1);

      if (values.length > 0)
        com.update.apply(com, values);
    }

    return this;
  },
  // [/dep]

  sends: function(o, f) {
    return this.send(function() {
      if (o.constructor == Array || o.length)
        for (var i = 0; i < o.length; i++)
          f.call(this, o[i], i);
      else
        for (var id in o)
          f.call(this, o[id], id);      
    });
  },
  
  send: function(f) {
    var com, sub;
    if (com = this._next)
      if (com.name == 'sub' && com.parent == this)
        sub = com;
    // try {
      f.apply(sub || this);
    // } catch (e) {
    //   //alert(this.name + '#send: ' + (e.message || e))
    // }
    return this;
  },

  add: function(name, inner) {
    var names = name.split(/\.|#/);
    if (names.length > 1) {
      if (Com[names[0]]) {
        var com = this.insert(names[0]);
        for (var i = 1; i < names.length; i++)
          com.apply(names[i]);
        return com;
      } else {
        var prev, next;
        if (prev = (this.last() || this)._next)
          next = prev._next;
        return load(this.node.appendChild(build(name, inner)), this, prev, next);
      }
    }
    return this.insert(name);
  },
  
  prepend: function(name) {
    return this.insert(name, this.first());
  },
    
  insert: function(name, next) {
    return (Com[name] && Com[name].spawn ? Com[name].spawn() : spawn(name)).move(this, next);
  },
  
  replace: function(name) {
    var com = this.parent.insert(name, this);
    this.remove();
    return com;
  },
  
  remove: function(match) {
    if (match)
      return this.find(match).remove();

    this.detach();
    this.node.parentNode.removeChild(this.node);
    return this;
  },
  
  clone: function() {
    return load(this.node.cloneNode(true));
  },
  
  move: function(parent, next) {
    if (next)
      next.node.parentNode.insertBefore(this.node, next.node);
    else
      parent.node.appendChild(this.node);

    var last;
    if (!next) last = parent.last();

    this.detach();
    next = next || (last || parent)._next;
    this.attach(next ? next._prev : last || parent, parent, next);
        
    return this;
  },
  
  attach: function(prev, parent, next) {
    var i = this, j = this.last() || this;
    
    if (i._prev = prev) prev._next = i;
    if (j._next = next) next._prev = j;

    if (this.parent = parent) {
      this[parent.name] = parent;

      if (!parent[this.name] || this.next(this.name) == parent[this.name])
        parent[this.name] = this;
    }
  },
  
  detach: function() {
    if (this.parent) {
      if (this.parent[this.name] == this)
        this.parent[this.name] = this.next(this.name, true);
      
      this.parent = this[this.parent.name] = null;
    }
    var i = this, j = this.last() || this;

    if (i._prev) i._prev._next = j._next;
    if (j._next) j._next._prev = i._prev;
  },
  
  empty: function() {
    var com = this;
    while ((com = this._next) && this.contains(com)) com.detach();
    while (this.node.firstChild)
      this.node.removeChild(this.node.firstChild);
    return this;
  },
        
  find: function(matcher) {
    if (arguments.length < 2)
      return this.first(matcher);
      
    var com = this;
    for (var i = 0; com && (i < arguments.length); i++)
      com = com.find(arguments[i]);
    return com;
  },
    
  first: function(matcher) {
    return this.each(matcher, function() { return this });
  },
  
  last: function(matcher) {
    var com;
    this.each(matcher, function() { com = this });
    return com;
  },
  
  up: function(matcher) {
    var com = this;
    while (com = com.parent)
      if (com.match(matcher))
        return com;
  },
  
  collect: function(matcher, callback) {
    var collection = [];
    this.each(matcher, function() { collection.push(callback ? callback.apply(this) : this) });
    return collection;
  },
  
  count: function(matcher) {
    var n = 0;
    this.each(matcher, function() { n++ });
    return n;
  },
  
  invoke: function(matcher, name) {
    var results = [];
    this.each(matcher, function() { results.push(this[name]()) });
    return results;
  },
  
  each: function() {
    var matcher  = arguments[arguments.length - 2];
    var callback = arguments[arguments.length - 1];
    
    var result, com = this;
    while ((com = com._next) && this.contains(com))
      if (com.match(matcher) && typeof (result = callback.apply(com)) != 'undefined')
        return result;
  },
  
  // Apply f to each matching component of this tree, or until f returns something
  // Match by name or flag/boolean property
  visit: function(match, f) {
    var v, h = {}, com = this;
    do {
      h[com.node._cid] = true;
      if (match === true || com.name == match || com[match] === true)
        if (typeof (v = f.apply(com)) != 'undefined')
          return v;
    } while ((com = com._next) && h[com.parent.node._cid])
  },
  
  prev: function() {
    var args = [true];
    for (var i = 0; i < arguments.length; i++) args.push(arguments[i]);
    return this.seek.apply(this, args);
  },
  
  next: function() {
    var args = [false];
    for (var i = 0; i < arguments.length; i++) args.push(arguments[i]);
    return this.seek.apply(this, args);
  },

  seek: function(back) {
    var id = back ? '_prev' : '_next', com = this, matcher, sibling = false;
    
    switch (arguments.length) {
      case 2: {
        if (arguments[1] === true || arguments[1] === false)
          sibling = arguments[1];
        else
          matcher = arguments[1];
        break;
      }
      case 3: {
        sibling = arguments[2];
        matcher = arguments[1];
      }
    }

    while ((com = com[id]) && (com != template) && (!sibling || !this.parent || this.parent.contains(com)))
      if (com.match(matcher) && (!sibling || com.parent == this.parent))
        return com;
  },

  contains: function(o) {
    if (o.node) {
      while (o = o.parent)
        if (o == this) return true;
    } else {
      do
        if (o == this.node)
          return true;
      while (o = o.parentNode);
    }
    
    return false;
  },
    
  match: function(v) {
    if (!v) return true;
    if (typeof v == 'function') return v.apply(this);
    return this.name == v || this[v] === true || this.flags[v] || (typeof this.id == 'function' && this.id() == v);
  },

  equals: function(com) {
    return com && com.name == this.name;
  },
    
  remote: function(id, callback) {
    var com = this, data;
    fetch(id, function(o) {
      if (!o.error && (data = o.ok || o)) {
        if (data.constructor == Array) {
          for (var i in data)
            callback.call(com, data[i]);
        } else {
          callback.call(com, data);
        }
      }
    });
    return this;
  },
  
  // Generate components for this tree
  // Sets the _prev/_next pointers that allow traversal via #visit
  load: function() {
    var next = (this.last() || this)._next;
    var last = this, inits = [];
    
    function visits(node, parent) {
      var sub, i = 0;
      while (sub = node.childNodes[i++])
        if (sub.nodeType == 1)
          visit(sub, parent);
    }
    function visit(node, parent) {
      var first, o, com, names = load.names(node), unknowns = {};

      function yield(name) {
        chain(last, com = new Com[name](name, node, unknowns, parent));
        last = com;
        if (com.init)
          inits.push(com);
      }

      for (var name in names) {
        first = first || name;

        if (Com[name] && !com)
          yield(name);
        else
          unknowns[name] = true;
      }

      if (!com && ((parent && parent.name == 'template') || first == node.id)) { // dep
        bind(first);
        yield(first);
      }

      if (parent && com)
        parent[com.name] = parent[com.name] || com;

      o = com || parent;

      if (o)
        for (var name in unknowns)
          if (name.indexOf('-') > - 1) {
            if (com)
              com.id = name;
          } else
            o[name] = o[name] || (com ? true : node);

      
      visits(node, com || parent);

      return com;
    };

    visits(this.node, this);
    chain(last, next);
    
    for (var i = 0; i < inits.length; i++)
      inits[i].init();

    return this;
  },
     
  toHTML: function() {
    return this.node.innerHTML;
  },
    
  toString: function() {
    return this.name;
  }
});

Com = {};

bind = function(name) {
  var klass, chain = [Com[name] || Container];
  for (var i = 1; i < arguments.length; i++) {
    var o = arguments[i];
    if (typeof o == 'string') {
      if (!Com[o])
        throw new Error(o + ' is not a Component type');
      chain.push(Com[o]);
    } else
      chain.push(o);
  }
  klass = Class.apply(this, chain);
  klass.handlers  = klass.prototype.handlers();
  klass.instances = [];
  klass.ext = function(o) {
    return cp(klass, o)
  }
  return Com[name] = klass;
};

def = function(id, inner) {
  var names, name, klass;
  
  if ((names = id.split(/\.|#/)).length < 2)
    throw 'Invalid container id: ' + id;  
  
  name = names[1];
  // if (Com[name = names[1]])
  //   throw 'Attempting to redefine ' + name + ' with ' + id;
  
  //var klass = bind.apply(this, [name].concat($A(arguments).slice(2)));
  klass = Com[name] = Class(Container);  
  
  // dep:
  klass.instances = [];
  
  ext.apply(this, [name].concat($A(arguments).slice(2)));

  klass.spawn = function() {
    return load(build(id, inner));
  }
  return klass;
};

ext = function(name) {
  if (!Com[name])
    throw name + ' is not defined';
  var o = Com[name].prototype;
  for (var i = 0; i < arguments.length; i++) {
    var m = arguments[i];
    if (Com[m]) m = Com[m];
    m = m.prototype || m;
    cp(o, m);
  }
  return Com[name];
}

spawn = function(name) {
  if (Com[name])
    if (Com[name].spawn)
      return Com[name].spawn();
    else if (Com[name].tag)
      return load(build(Com[name].tag()))
  
  var com;
   
  if (com = template.find(name))
    return load(com.node.cloneNode(true));
    
  com = new Container(name, template.node.ownerDocument.createElement('div'));
  com.updateNames();
  return com;
}

com = function(node) {
  if (node)
    return node._cid ? cons[node._cid] : com(node.parentNode);
};

base = function(methods) {
  Container = Class(Container, methods);
  for (var name in Com)
    cp(Com[name].prototype, methods);
};

// Returns the root component for the tree yielded by this element
load = function(element, parent, prev, next) {
  var last = prev || parent, inits = [];
  
  function visit(element, parent) {
    var first, o, com, names = load.names(element), unknowns = {};
    
    function yield(name) {
      chain(last, com = new Com[name](name, element, unknowns, parent));
      last = com;
      if (com.init)
        inits.push(com);
    }
    
    for (var name in names) {
      first = first || name;
      
      if (Com[name] && !com)
        yield(name);
      else
        unknowns[name] = true;
    }
    
    if (!com && ((parent && parent.name == 'template') || first == element.id)) { // dep
      bind(first);
      yield(first);
    }
        
    if (parent && com)
      parent[com.name] = parent[com.name] || com;
    
    o = com || parent;
    
    if (o)
      for (var name in unknowns)
        if (name.indexOf('-') > - 1) {
          if (com)
            com.id = name;
        } else
          o[name] = o[name] || (com ? true : element);
    
    for (var i = 0, nodes = element.childNodes; i < nodes.length; i++)
      if (nodes[i].nodeType == 1)
        visit(nodes[i], com || parent);

    return com;
  };
  
  var top = visit(element, parent);
  
  chain(last, next);
  
  for (var i = 0; i < inits.length; i++)
    inits[i].init();
  
  return top;
}
// Returns a map of all the names that can be extracted from class and id attributes
load.names = function(node) {
  var all = [], names = {};
  if (node.className)
    all = node.className.split(' ');
  if (node.id)
    all.unshift(node.id.toString());
  for (var i = 0; i < all.length; i++)
    if (all[i])
      names[all[i]] = true;
  return names;
};

function chain(a, b) {
  if (a && b)
    (a._next = b)._prev = a;
}

bind('sub');

if (typeof navigator != 'undefined')
  if (/webkit/i.test(navigator.userAgent)) {
    var timeout = setTimeout(function() {
      if (document.readyState == 'loaded' || document.readyState == 'complete' ) {
        run();
      } else {
        setTimeout(arguments.callee, 10);
      }
    }, 10); 
  } else if ((/mozilla/i.test(navigator.userAgent) && !/(compatible)/i.test(navigator.userAgent)) || (/opera/i.test(navigator.userAgent))) {
    document.addEventListener('DOMContentLoaded', run, false);
  } else if (document.uniqueID && document.expando) { // http://www.hedgerwow.com/360/dhtml/ie-dom-ondocumentready.html
    var element = document.createElement('span'); 

    (function () { 
      if (document.loaded) return;

      try {
        element.doScroll('left');
    
        if (!document.body)
          throw new Error();
    
        document.loaded = true;
        run();
        element = null; 
      } catch(e) {
        setTimeout(arguments.callee, 0); 
      } 
    })();
  }

function run() {
  boot();
    for (var i = 0; i < start.callbacks.length; i++)
      start.callbacks[i]();    
}

start = function(callback) {
  start.callbacks.push(callback);
};
start.callbacks = [];

// Raw ajaxy request.
// f is passed (response_text, transport)
xhr = function(method, url, body, headers, f) {
  var o;
  try {
    o = new ActiveXObject('Msxml2.XMLHTTP')
  } catch(e) {
    try {
      o = new ActiveXObject('Microsoft.XMLHTTP')
    } catch(e) {
      o = new XMLHttpRequest()
    }
  }
  
  o.open(method, url, true);
  for (var id in headers)
    o.setRequestHeader(id, headers[id]);
  
  o.onreadystatechange = function() {
    switch (o.readyState) {
      case 4: {
        if (f)
          f(o.responseText, o);
        o.onreadystatechange = function() {};
      }
    }
  }
  o.send(body);
  return o;
}

})();
