
CONFIG = {
  ext: function(o) {
    merge(this, o)
  }  
};

var exec, ext, log = /localhost/.test(this.location || '') ? alert : function() {};
  
var IE = !!(typeof document != 'undefined'
  && document.expando && document.uniqueID);

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

new function() {

// Components: interface modeling framework.
// Created by Adam Bones (adam@oxdi.eu)

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;
}

// =================================================================================
// utils

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;
};

merge = function(base) {
  for (var o, i = 1; o = arguments[i]; i++)
    for (var id in o) {
      switch (typeof o[id]) {
        case 'object':
          if (hash(o[id])) {
            if (!hash(base[id]))
              base[id] = {};
            merge(base[id], o[id]);
            break;
          }
        case 'function':
        case 'number':
        case 'boolean':
        case 'string':
          base[id] = o[id];
      }
    }
  return base;
}

function hash(v) {
  return typeof v == 'object' && v && !((typeof v.length == 'number') || v.constructor == Array || v.node || v.nodeType);
}


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;
};

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

// 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'
}

Container = Class(Component, {
  
  initialize: function(node, name, flags, parent) {
    this.setHandlers(this.node = node);
    this.name   = name;
    this.flags  = flags || {};
    if (this.parent = parent)
      ;//this[parent.name] = parent;
    this.data = node.textContent;
    // DEP: properties for flags
    for (var flag in flags)
      if (typeof this[flag] == 'undefined')
        this[flag] = flags[flag];
  },
  
  ext: function(o) {
    return cp(this, o);
  },
  
  observe: function(type, b, c) {
    var com  = this, id = 'on' + type,
        node = c ? b : this.node,
        _f = node[id], f = c || b;

    node[id] = function(e) {
      var v;
      if (typeof window != 'undefined')
        e = e || window.event;

      if (_f)
        if (_f.call(com, e) === false)
          v = false;
      if (f.call(com, e) === false)
        v = false;
      if (v === false)
        if (e) {
          if (e.stopPropagation)
            e.stopPropagation();
          else
            e.cancelBubble = e.returnValue = true;
        }
      return v;
    }
    return this;
  },
  
  // until can remove deprecated exec()..
  // vs: list part of href="action:1:x"
  exec2: function(vs) {},
  
  observes: function(h) {
    cp(this, {
      exec2: function(id) {
        if (typeof h[id] == 'function') {
          h[id].apply(this, $A(arguments).slice(1));
          return true;
        }
      },
      
      observes: function(hh) {
        cp(h, hh);
        return this;
      }
    })
    return this.observe('click', function(e) {
      var node = e.target || e.srcElement;
      for (var i = 0; i < 2 && node && node.tagName != 'A'; i++)
        node = node.parentNode;
      if (node && node.href) {
        var v = node.getAttribute('href', 2);
        if (v.length > 2)
          if (v.charAt(0) == '#')
            if (this.exec2.apply(this, v.slice(1).split(':')))
              return false;
      }
    });
  },
    
  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 match, 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;    
  },
    
  reset: function(o) {
    var prev = this._prev;
    var next = (this.last() || this)._next;
    var s = '';
    
    if (typeof o == 'string')
      s = o;
    else
      for (var id in o)
        s += tag(id, o[id]);
    
    return load(this.send(function() {
      this.node.innerHTML = s; 
    }), this.parent, prev, next);
  },
    
  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.node.textContent || 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]
  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;
  },
  
  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 = this;
    this.visit('sub', function() {
      if (this.parent == com)
        return com = this;
    });
    f.apply(com);
    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 spawn(name).move(this, next);
    //return ((Com[name] && Com[name].spawn) ? Com[name].spawn() : spawn(name)).move(this, next);
  },
  
  replace: function(com) {
    //var com = this.parent.insert(name, this);
    var parent = this.parent, next = (this.last() || this)._next;
    this.remove();
    return com.move(parent, next);
  },
  
  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(v) {
    var root = this;
    return this.visit(v, function() {
      if (this != root)
        return this
    });
  },
  
  last: function(v) {
    var com, root = this;
    this.visit(v, function() {
      if (this != root)
        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;
  // },
  
  collect: function(m, f) {
    var vs = [];
    this.visit(m, function() { vs.push(f ? f.apply(this) : this) });
    return vs;
  },
  
  count: function(m) {
    var n = 0;
    this.visit(m, function() { n++ });
    return n;
  },
  
  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:
  visit: function(a, b) {
    var v, match, f = b || a;
    
    if (b)
      switch (typeof a) {
        case 'undefined': break;
        case 'function':
          match = a; break;
        case 'string':
          match = function() {
            return this.name == a 
              || this.flags[a] 
              || this[a] === true 
              || (typeof this.id == 'function' && this.id() == a)
          }; break;
        default:
          throw '#visit: Invalid matcher: ' + a;
      }

    var h = {}, com = this;
    do {
      h[com._i] = true;
      if (!match || match.apply(com))
        if (typeof (v = f.apply(com)) != 'undefined')
          return v;
    } while ((com = com._next) && com.parent && h[com.parent._i])
  },
  
  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);
  },
     
  toHTML: function() {
    return this.node.innerHTML;
  },
  
  // inspect: function() {
  //   return this.name;
  // },
  
  toJSON: function() {
    return this.name;
  },
    
  toString: function() {
    return this.name;
  }
});

/* ================================================================================= */

Com = {};

bind = function(name, x) {
  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.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);  
  
  
  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] || bind(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(template.node.ownerDocument.createElement('div'), name);
  com.updateNames();
  return com;
}

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


/* ================================================================================= */
new function() {

var n = 0; // counter for components yielded so far
var h = {}; // map for components yielded so far (index by position in sequence)

load = function(o, parent, prev, next) {
  if (typeof o == 'string') 
    return load(build.apply(this, arguments))

  var top, last = prev || parent, inits = [];
  
  function link(com) {
    if (last && com)
      (last._next = com)._prev = last;
  }
  
  function visit(o, parent) {
    var com, node;
    
    if (o.node) {
      node = o.node;
      com = o;
    } else {
      node = o;
    }
    if (node.nodeType == 1) {
      if (!com) {
        var lead, name = node.id, names = load.names(node);
        for (var nname in names) {
          lead = lead || nname;
          if (Com[nname]) {
            name = nname;
            break;
          }
        }
        if (!name && parent && parent.name == 'template') // DEP: yield for template components
          bind(name = lead);
        if (name) {
          delete(names[name]);
          com = new (Com[name] || Container)(node, name, names, parent);
          (h[name] = h[name] || {})[com._i = ++n] = com; // add to the map
          if (parent) // DEP: first component of type as property
            if (!parent[name]
              || (parent[name].node && !parent[name].node.parentNode)) // fix for #reset not removing dead properties after setting innerHTML
              parent[name] = com;
          if (com.init)
            inits.push(com);        
        } 
      }      
      link(com);
      last = com || last;
      for (var i = 0, nodes = node.childNodes; i < nodes.length; i++)
        visit(nodes[i], com || parent);

      return com; 
    }    
  }
  
  top = visit(o, parent);

  link(next);
  
  for (var i = 0; i < inits.length; i++)
    inits[i].init();

  return top;
}

// Get the enclosing component (from the map)
com = lookup = function(node, containers) {
  if (node) {
    if (node.nodeType == 1)
      for (var name in load.names(node))
        if (h[name])
          for (var i in h[name])
            if (h[name][i].node == node
              || (node.e4xNode && node.e4xNode == h[name][i].node.e4xNode)) // fix for == bug in o3/dom
              return h[name][i];
    if (containers)
      return lookup(node.parentNode);  
  }
};

// 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;
};

load.count = function() {
  return n;
}

}
/* ================================================================================= */

template = null;
content  = null;

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

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

var done = false, f = function() {
  var com = boot();
  for (var i = 0; i < start.callbacks.length; i++)
    start.callbacks[i].apply(com || document.body);
}

if (typeof navigator != 'undefined')
  if (/webkit/i.test(navigator.userAgent)) {
    var timeout = setTimeout(function() {
      if (document.readyState == 'loaded' || document.readyState == 'complete' ) {
        f();
      } 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', f, false);
  } else if (document.uniqueID && document.expando) { // http://www.hedgerwow.com/360/dhtml/ie-dom-ondocumentready.html
    var element = document.createElement('span'); 

    (function () { 
      if (done) return;

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

}


bind('sub');

// commands:
new function() {
  var ctx = {}, h = {};

  cmd = function(a, b) {
    var o = this, hh = h, x = b || a;
    if (b) {
      var id = a;
      if (a.name) {
        id = a._i; ctx[id] = a;
      }
      hh = hh[id] = (hh[id] || {});
    }
    for (var id in x)
      hh[id.toLowerCase()] = x[id];
    return b || a;
  }
  exec = function(vs) {
    var o = this, hh = h, i = 0, v;
    while (typeof (v = vs[i++]) == 'string' && (v = v.toLowerCase()) &&
           typeof hh[v] == 'object') {
      hh = hh[v]; o = ctx[v] || this;
    }
    if (typeof hh[v] == 'function') {
      //try {
        hh[v].apply(o, vs.slice(i));
      // } catch (e) {
      //   log(v + ': ' + (e.message || e));
      // }
      return true;
    }
  };
  if (typeof document != 'undefined')
    new Container(document).observe('click', function(e) {
      var v, node = e.target || e.srcElement;

      for (var i = 0; i < 2 && node && node.tagName != 'A'; i++)
        node = node.parentNode;

      if (node && node.getAttribute)
        if (v = node.getAttribute('href', 2))
          if (v.length > 1) // ignore solo hash
            if (v.charAt(0) == '#')
              //if (exec(v.slice(1).split(':').concat([node])))
              if (exec(v.slice(1).split(':')))
                return false;
    });
}

}

// =================================================================================

var tag, tags, html, build, $;

new function() {

  var CLOSING = map('meta', 'link', 'img', 'br', 'hr', 'link', 'input', 'embed'),
      ATTR    = map(
        'class', 'name', 'id', 'lang',
        'style', 'src', 'width', 'height', 'alt', 'media',
        'href', 'rel', 'content', 'title', 'id', 'charset', 'http-equiv',
        'action', 'method', 'enctype', 'target', 'name', 'value', 'type', 'selected', 'checked',
        'rowspan', 'colspan', 'cellspacing', 'cellpadding',
        'classid', 'codebase', 'autoplay', 'controller', 'pluginspage', 'wmode', 'flashVars', 'scale',
        'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onchange', 'onload', 'onsubmit',
        'colSpan', 'rowSpan'
      ),
      WRAP = {
        'td': 'tr', 'tr': 'tbody', 'tbody': 'table',
        'li': 'ul',
        'option': 'select'
      };

  tag = function(id, o) {
    var name, names = {}, attr = {}, inner = '', sep, s = '';
  
    function word() {
      switch (sep) {
        case '.': {
          names[s] = true;
          break;        
        }
        case '#': {
          attr['id'] = s;
          break;
        }
        case '$': {
          if (!$[s])
            throw 'Unknown $: ' + s;
        
          o = $[s](o);
          break;
        }
        default: {
          name = s || 'div';
        }
      }    
      s = ''; sep = '';
    }
  //try {
    for (var ch, i = 0; i < id.length; i++)
      if ((ch = id.charAt(i)) == '.' || ch == '#' || ch == '$') {
        word();
        sep = ch;
      } else
        s += ch;
  //} catch (e) { throw id }
    word();
  
    function exclude(v) {
      return v === null || v === false || typeof v == 'undefined';
    }
  
    function unpack(o) {
      if (!exclude(o))
        switch (typeof o) {
          case 'number':
          case 'string': {
            inner += encode('' + o);
            break;
          }
          case 'object': {
            var v;
            if (o)
              for (var id in o) 
                if (!exclude(v = o[id])) {
                  id = id.replace(/:.*$/, '');

                  if (typeof v == 'function') // code
                    v = v.toString().replace(/^function\s*\(\)\s*\{\s*/, '').replace(/\s*}$/, '');

                  switch (id) {
                    case 'data':
                      inner += encode('' + v); break;
                    case '':
                    case 'inner':
                      switch (typeof v) {
                        case 'number':
                        case 'string': {
                          inner += '' + v;
                          break;
                        }
                        case 'object':
                          unpack(v);
                      }
                      break;
                    case 'tag':
                      name = v; break;
                    case 'flags': {
                      for (var id in v)
                        cp(names, v);
                      break;
                    }
                    case 'classes':
                      for (var i = 0; i < v.length; i++)
                        names[v[i]] = true;
                      break;
                    default:
                      switch (typeof v) {
                        case 'boolean':
                          names[id] = v; break;
                          // if (!ATTR[id]) {
                          //   names[id] = v;
                          //   break;
                          // }
                        case 'number':
                        case 'string':
                          if (ATTR[id] || id.indexOf('data-') == 0) {
                            attr[id] = encode(v);
                            break;
                          }
                        default: 
                           inner += tag(id, v);
                      }
                  }
                }
          } // object
        } // switch
    } // unpack
  
    unpack(o);
  
    var classes = [];
    for (var nname in names)
      if (names[nname])
        classes.push(nname);
    if (classes.length > 0)
      attr['class'] = classes.join(' ');
    
    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, o, f) {
    var s = '';
    if (o)
      if (o.length)
        for (var iid, i = 0; i < o.length; i++) {
          iid = id;
          if (i == 0)
            iid += '.first';
          if (i == o.length - 1)
            iid += '.last';
          s += tag(iid, f ? f(o[i], i) : o[i]);
        }
      else
        for (var v in o)
          if (o[v])
            s += tag(id, f ? f(v, o[v]) : v);
    return { inner: s };
  }

  $ = function(id, f) {
    $[id] = f;
  }
  $.list = function(o) {
    if (o.n) {
      var v = {}, i = 0;
      while (i++ < o.n) v[o.id + ':' + i] = o.f ? o.f(i) : {};
      return { inner: v };
    }
    return tags(o.id, o.vs, o.f);
  }

  build = function(a, b) {
    var node, s, n = 1;
    if (b || a.indexOf('<') == -1) {
      s = tag(a, b);
      var i = a.indexOf('.'), name = i > -1 ? a.slice(0, i) : a;
      while (WRAP[name]) {
        s = tag(WRAP[name], { inner: 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;
  }

}









// o3 dependent tools: (api, urls ...)
// requires: sweep

var url, xhr, html;

// dep:
var O3, editor, dialog, pointer;

url = function(o, h) {
  var s;

  // From document
  if (o.toDocument)
    o = o.toDocument();
  if (s = o._id) {
    if (o.URL)
      s = s.replace(/\/[^\/]+$/, '/' + o.URL.replace(/\s/g,'+'));
  } else
    s = o.toString();

	// Add leading slash
  // if (s.indexOf('#') != 0 && s.indexOf('http://') != 0 && s.indexOf('mailto:') != 0)
  //   if (s.charAt(0) != '/')
  if (!/^(\/|#|http:|mailto:)/.test(s))
      s = '/' + s;

  // Add query string
  if (typeof h == 'object') {
    var vs = [];
    for (var id in h)
      vs.push(escape(id) + '=' + escape(h[id]));
    if (vs.length > 0) {
      switch (s.indexOf('?')) {
        case -1:
          s += '?'; break;
        case (s.length - 1):
          break;
        default:
          s += '&'
      }
      s += vs.join('&');
    }
  }
  return s;
};

// xhr:
new function() {
  var n = 0, h = {
    'custom-header':'true', 'Content-type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest', 'If-Modified-Since': 'Thu, 1 Jan 1970 00:00:00 GMT' // Stop IE7 caching
  };
  // object means post
  // no object means get
  // url is optional in posts, default is /api/
  xhr = function() {
    var com, o, m, path, body = '', f;

    for (var v, i = 0; i < arguments.length; i++)
      switch (typeof (v = arguments[i])) {
        case 'string':
          m = 'GET'; path = v; break;
        case 'object':
          if (v.nodeType == 1)
            com = new Container(v);
          else if (v.node && v.flags)
            com = v;
          else {
            m = 'POST'; path = path || '/api/'; body = JSON.stringify(v);
          }
          break;
        case 'function':
          f = v; break;
      }

    path = url(path, { n: ++n }); // beat local caches

    try {
      o = new ActiveXObject('Msxml2.XMLHTTP')
    } catch(e) {
      try {
        o = new ActiveXObject('Microsoft.XMLHTTP')
      } catch(e) {
        o = new XMLHttpRequest()
      }
    }
      
    o.open(m, path, true);
    for (var id in h)
      o.setRequestHeader(id, h[id]);

    o.onreadystatechange = function() {
      switch (o.readyState) {
        case 4: {
          var s;
          if (f)
            f(!(s = o.getResponseHeader('Content-Type') || '') || /json/.test(s) || /api|schemas/.test(url) ?
              JSON.parse(o.responseText) : o.responseText, o);
          if (com) com.clear('busy');
          o.onreadystatechange = function() {};
        }
      }
    }
    if (com) com.apply('busy');
    o.send(body);
    return o;
  }  
}

html = function(o) {
  o = o || {};

  return '<!DOCTYPE HTML>'
    + tag('html', {
      'lang': 'en',
      'head': {
        ':meta': o.data && {
          ':1': o.data.Title && tag('title', o.data.Title), // hack around title property being attribute
          ':2': tags('meta', { 'Description': !!o.data.Description, 'Keywords': !!o.data.Keywords }, function(name) {
            return { name: name.toLowerCase(), content: o.data[name] }
          })
        },
        'meta': {
          'http-equiv': 'Content-Type',
          'content': 'text/html; charset=utf-8'
        },
        ':js': tags('script', cp(map('components'), o.js || {}), function(name) {
          return {
            src: '/js/' + name + '.js', type: 'text/javascript', charset: 'utf-8'
          }
        }).inner,
        ':css': tags('link', cp(map('base'), o.css || {}), function(name) {
          return { rel: 'stylesheet', type: 'text/css', href: '/css/' + name + '.css' }
        }).inner,
        style: o.style && {
          type: 'text/css', media: 'screen', inner: o.style
        }
      },
      'body': {
        ':1': o.data && o.data._id && {
          classes: [o.data._id.replace('/', '-')]
        },
        ':2': o.body || '',
        ':3': o.data && o.data['Footer HTML']
      }
    });  
}

// ... dep

O3 = {
      
  url: url,
  
  load: xhr, sends: xhr, send2: xhr,
  
  cookie: function(x) {
    for (var id in x || {})
      document.cookie = id + '=' + x[id] +'; path=/';
    
    var h = {}, parts = document.cookie.split(';');
    
    for (var pair, i = 0; i < parts.length; i++)
      if (pair = parts[i].split('='))
        if (pair.length == 2) {
          var key = pair[0].replace(' ', ''), v = pair[1];
          try {
            v = JSON.parse(v);
          } catch (e) { }
          if (v != null)
            h[key] = v;
        }
    return h;
  }
};

base({

  onExec: function(ev) {
    return this.exec.apply(this, arguments);
  },
  
  build: function(callback, ord) {
    var com;
    callback.apply(com = (this.find('contents') || this));
    if (ord)
      com.ord();
    return this;
  },
      
  ord: function() {
    var first, last, com, parent = this, ons = !!this.find(function() { return this.parent == parent && this.on });

    this.each(function() {
      if (this.parent == parent) {
        this.clear('last');
        if (first)
          this.clear('first');
        else if (!ons || this.on)
          first = this.apply('first');
        if (!ons || this.on)
          last = this;
      }
    });
    if (last)
      last.apply('last');
    return this;
  },
  
  setup: function(o) {
    this.controls(function(name) {
      this.update(o[name]);
    });
    return this;
  },
  
  controls: function(callback) {
    var map = {}, v;
    this.each('control', function() {
      var field, name = this.read('name') || ((field = this.up('field')) && field.first('control') == this ? field.reads()[0].replace(':', '') : false);
      if (name) {
        var value = this.value();
        if (callback)
          if (typeof (v = callback.call(this, name, value)) != 'undefined')
            return v;
        map[name] = value;
      }
    });
    return typeof v == 'undefined' ? map : v;
  },
  
  hide: function(v) {
    var com = v ? this.find(v) : this;
    try { com.node.style.display = 'none' } catch (e) {}
    return com;
  },
  
  show: function(v) {
    var com = v ? this.find(v) : this;
    try { com.node.style.display = '' } catch (e) {}
    return com;
  },
  
  fade: function(rate, finalize) {
    return this.morphO(1, 0, rate, function() {
      if (finalize === true)
        this.remove();
      else if (finalize)
        finalize.call(this);
    });
  },
  
  appear: function(rate, finalize) {
    return this.morphO(0, 1, rate, finalize);
  },
  
  morphO: function(i, j, rate, finalize) {
    var s = this.node.style;
    
    // Trigger hasLayout in IE (fixes text rendering bug)
    if (window.ActiveXObject)
      s.width = this.node.offsetWidth + 'px';

    return this.morph(i, j, rate, function(k) {
      s.display = k == 0 ? 'none' : '';
        
      if (window.ActiveXObject)
        s.filter = 'alpha(opacity=' + (k * 100) + ')';
      else
        s.opacity = k;
    }, function() {
      s.display = s.opacity = s.filter = '';
      if (finalize)
        finalize.call(this);
    });
  },

  morph: function(i, j, rate, iterator, finalize) {
    rate = rate || 0.05;
    
    var k = i;
    
    iterator.call(this, i);
    
    return this.start(function() {
      k += (i < j ? 1 : -1) * rate;
      iterator.call(this, Math.round(-100 * (Math.cos(Math.PI * k) - 1) / 2) / 100);
      
      if ((j > i && k >= j) || (j < i && k <= j)) {
        if (finalize)
          finalize.call(this);
        return false;
      }
    }, 20);
  }
});

bind('drop', {

  onClick: function() {
    return true;
  },
  
  onExec: function(id, o) {
    if ((o.button || o.ic || {}).parent == this) // the first button outside of the list - open/closes the drop
      return !this.toggle();
    if (!this.sticky)
      this.toggle();
    o.drop = this;   
  },
  
  ons: function(match) {
    return this.build(function() {
      this.each('button', function() {
        this.toggle(this.match(match));
      });
      this.ord();
    });
  },
  
  toggle: function(v) {
    var o = Com.drop;
    if (o.open)
      if (o.open != this)
        o.open.clear('open');
    if (arguments.length == 0)
      v = !this.open;
    o.open = v ? this.apply('open') : !this.clear('open');
    return this;
  }
});
 
bind('ic', {
  
  onClick: function() {
    var id;
    if (id = this.id()) {
      var o = {};
      o[this.name] = this;
      this.fire('exec', id, o);
    }
    if (this.read('href').indexOf('#') > -1)
      return false;
  },
  
  id: function() {
    var v, i;
    if (v = this.read('href'))
      if ((i = v.indexOf('#')) > -1)
        v = v.slice(i + 1);
    return v || this.read('title') || this.read();
  }  
})

bind('button', 'ic');

bind('inner');

bind('list');

bind('control', {

  onFocus: function() {
    //this.klass.focus = this;
    if (this.def)
      this.clear('def').node.value = '';
  },
  
  // onBlur: function() {
  //   // Bitofahack: if this was caused by sending an editor update command (which will move focus to inner windowm, then back to this control), ignore it
  //   if (!this.editing)
  //     this.klass.focus = false;
  // },

  onMouseDown: function() {
    return !this.up('selector');
  },
  
  onKeyDown: function(event) {
    if (!this.multi)
      if (event.keyCode == 13)
        this.fire('enter', this);

    this._value = this.node.value;
  },
  
  onKeyUp: function() {
    if (this.node.value != this._value) {
      //this.editing = true;
      this.fire('edit', this);
      this.node.focus();
      //this.editing = false;
    }
  },

  update: function(v) {
    this.node.value = v || '';
    return this;
  },
  
  value: function() {
    return this.number ? parseInt(this.node.value) : this.node.value
  }
});

bind('switch', 'control', {
  
  update: function(v) {
    return this.toggle(this.node.checked = v);
  },
  
  value: function() {
    return this.on || this.node.checked;
  }
});

pointer = function(event) {
  return [ event.pageY || (event.clientY + (document.documentElement.scrollTop  || document.body.scrollTop)),
           event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)) ];
};

// http://www.JSON.org/json2.js
new function() {
if(!this.JSON){JSON={}}(function(){function f(n){return n<10?'0'+n:n}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z'};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,subs={'\\b':'\\\\b','\\t':'\\\\t','\\n':'\\\\n','\\f':'\\\\f','\\r':'\\\\r','"':'\\"','\\\\':'\\\\\\\\'},rep;function quote(string){if(/["\\\x00-\x1f]/.test(string)){string=string.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=subs[b];if(c)return c;c=b.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16)})}return'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key)}if(typeof rep==='function'){value=rep.call(holder,key,value)}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null'}gap+=indent;partial=[];if(typeof value.length==='number'&&!value.propertyIsEnumerable('length')){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null'}v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v}if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' '}}else if(typeof space==='string'){indent=space}rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}return str('',{'':value})}}if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j}throw new SyntaxError('JSON.parse');}}})();
}








new function() {

FILTER  = '';

var UPLOADS = 0;

var project, tip, site, user;

start(function() {  
  tip = content.find('tip');
});


O3.user = function(f) {
  var key;
  if (key = O3.cookie().key)
    xhr(url('/session/user', { key: key }), function(r) {
      if (user = user || r.page)
        f(user);
    });
  else f({})
}

bind('content', {
  
  'Download 2D drawing': function(part) {
    this.exec('Download file', part, '2D', 'Drawing');
  },
  
  'Download 3D model': function(part) {
    this.exec('Download file', part, '3D', 'Model');
  },

  'Download file': function(part, type, name) {
    var params = {
      '3D Partstream ID': part['Partstream ID'], type: type
    };
    var formats;
    if (formats = CONFIG[name + ' formats']) {
      content.dialog('Download ' + name.toLowerCase() + ' for ' + part['Part number']).build(function() {
        this.apply('download');
        this.add('options').fills(formats, function(o) {
          this.add('option').sets(o.name);
        });
      }).apply('progress').ext({

        'Submit dialog': function() {
          var d = this, format, name = this.find('options').value();
          for (var i = 0; i < formats.length; i++) {
            if (formats[i].name == name) {
              format = formats[i];
              break;
            }
          };
          xhr('/part_stream/download', cp(params, format), function(r) {
            if (r.ok && r.ok.location) {
              window.location = r.ok.location;
              content.exec('Close dialog');
            }
            if (r.error)
              d.remove('control') && d.remove('button') && d.find('explanation').sets(r.reason);
          });
          this.apply('busy')
          return true;
        }
      }).pos();
    }
  },

  'View partstream': function(part) {
    xhr('/part_stream/view', {
      '3D Partstream ID': part['Partstream ID']
    }, function(r) {
      if (r.ok && r.ok.location)
        content.dialog('').build(function() {
          this.reset({
            'iframe': {
              width: 700,
              height: 560,
              src: r.ok.location
            }
          });
          // this.node.id = 'Viewer';
          // this.node.innerHTML = '<embed width="650" height="450" vmpclassid="{03F998B2-0E00-11D3-A498-00104B6EB52E}" genieminimumversion="50333440" componentminimumversion="50333440" hostminimumversion="50333440" xmltext="" pluginurl="" instancename="MetaCtl0" properties="mts3interfaceversion=05.00.02.29;name=MetaCtl0;starthidden=true;" broadcastkeyfileurl="http://www.viewpoint.com/installer/UniversalBroadcastKey.mtx" id="MetaCtl0" name="MetaCtl0" script="true" type="application/x-mtx" source="'+r.ok.location+'" componentfilename="SceneComponent.mtc" component="ISceneComponent"/>';
          // new MTSPlugin( r.ok.location, 700, 400, "bkey.mtx", 'popup', 'contenttype=3; layer=Viewer; ComponentMinimumVersion=50335395; GenieMinimumVersion=50335395; HostMinimumVersion= 50335395; RequiredVersions=SWFView.dll=3.0.14.163, Cursors.dll=3.0.15.16, VMPSpeech.dll=3.0.14.163, LensFlares.dll=3.0.14.163, SreeD.dll=3.0.14.163, SreeDMMX.dll=3.0.14.163, Mts3Reader.dll=3.0.14.163;');
          // this.reset({
          //   'iframe': {
          //     width: 700,
          //     height: 400,
          //     src: r.ok.location
          //     //src: 'http://www.3dpublisher.net/SWService/outfile.asp?viewfile=/SWDownloads1/2044368049-267177/PT0609200975912.MTS'
          //   }
          // })
        }).pos().node.style.marginLeft = '-395px';
    });
  },

  'Add part to a project': function(part) {
    O3.user(function(user) {
      xhr(url('/trapi/projects/all', { person_id: user.id }), function(r) {
        var projects;
        if (r.ok && (projects = r.object))
          content.dialog('Add ' + (part['Part number'] || 'part') + ' to a project', 'Add').build(function() {
            this.apply('add');
            this.add('inner').apply('fields').send(function() {
              this.add('field').sets('Add part to:').build(function() {
                this.reset({
                  'select.options.control': {
                    name: 'project',
                    inner: tags('option.option', ['new project'].concat(projects), function(project) {
                      return {        
                        data: project.Name || project,
                        value: project._id || false,
                        selected: ((!O3.cookie().project_id && project == 'new project') || (project._id == O3.cookie().project_id)) && 'selected'
                      }
                    }).inner
                  }
                }).options.observe('change', function() {
                  this.next('field').update(this.first().node.selected);                
                })
              });
              this.add('field').sets('Name:').build(function() {
                this.add('single');
              }).ext({
                update: function(on) {
                  on ? this.show() : this.hide();
                }
              }).update(!O3.cookie().project_id);
            }); 
            this.add('inner').apply('feedback').hide();
          }).ext({

            'Submit dialog': function() {
              var mod = this.controls(), d = this;

              function createPartAssignment(project_id) {
                xhr('/trapi/',{ _type: 'PartAssignment', project_id: parseInt(project_id.split('/')[1]), part_id: part.id }, function(r) {
                  if (r.ok) {
                    O3.cookie({ project_id: project_id });

                    content.find('globals').send(function() {
                      if (!this.find('projects'))
                        load('a.link.projects', { data: 'Projects', href: '/' + project_id }).move(this, this.find('logout'));
                    })
                    d.finalize('Added ' + (part['Part number'] || 'part'));
                  }
                });              
              }

              if (mod.project == 'new project')
                xhr('/trapi',{ _type: 'Project', Template: 'Project', Name: mod.Name, person_id: user.id }, function(r) {
                  if (r.ok)
                    createPartAssignment(r.ids[0]);
                });
              else
                createPartAssignment(mod.project);
              return false;
            },

            finalize: function(message) {
              this.hide('fields');
              this.find('explanation').sets(message);

              this.find('button').sets('View project').set('href', url(O3.cookie().project_id))
                .next('button').sets('Close').set('title', 'Close dialog');
            }

          }).pos();      
      })    
    })
  },
        
  'Rename this project': function() {

    content.apply('dimmed').add('dialog').build(function() {
      this.add('explanation').sets('Rename this project');

      this.add('inner.fields').send(function() {
        this.add('field').sets('Name:').build(function() {
          this.add('single');
        });
        this.add('buttons').adds({ 'Cancel': 0, 'Rename': 1 });
      });
      this.add('inner.feedback').hide().send(function() {
        this.add('inner');
        this.add('buttons').adds({ 'Cancel': 0, 'Back': 1 });
      });

      this.apply('edit')
    }).ext({

      'onRename': function() {
        var rename = this.find('single').value();
        if (rename) {
          xhr('/trapi', { _type: 'Project', _id: window.location.pathname.slice(1), Name: rename }, function(r) {
            window.location.href = window.location;
          })
          this.close();
        } else {
          this.apply('error');
          this.hide('fields');
          this.show('feedback').sets('Name cannot be blank.');
        }
      },

      'onBack': function() {
        this.clear('error');
        this.hide('feedback');
        this.show('fields');
      },

      'onCancel': function() {
        this.close();
      }
    }).pos();
  },
    
  'Delete this project': function() {
    this.dialog('Are you sure you wish to delete this project?', 'Delete').build(function() {
      this.apply('important');
    }).ext({
      'Submit dialog': function() {
        var pn = window.location.pathname;
        xhr('/trapi/',{ _delete: true, _id: pn.slice(1) }, function(r) {
          if (!content.find('projectsList').visit('opt', function() {
            if (this.node.value != pn) {
              O3.cookie({ project_id: this.node.value.slice(1) });
              return window.location = this.node.value;
            }
          })) {
            O3.cookie({ project_id: null });
            window.location = '/';
          }
          
        });
      }
    });
  },
  
  'Edit part details': function(detail, pa, id) {
    this.dialog('Edit ' + id, 'Save').build(function() {
      this.apply('edit').add('field').sets('New value:').build(function() {
        this.add('multi').update(pa[id]);
      });
    }).pos().ext({
      
      'Submit dialog': function() {
        detail.update(pa[id] = this.controls()['New value']);
        xhr('/trapi/',pa);
      }
    });
  },
  
  'Delete part from project': function(o, ic) {
    xhr('/trapi',{ _delete: true, _id: 'part_assignments/' + o.id }, function(r) {
      if (r.ok)
        ic.up('part').fade(0.05, true)
    });
  },
  
  'Add custom part': function() {
    this.dialog().build(function() {
      var i = UPLOADS++;
      
      this.apply('upload').reset({
        '.explanation': 'Upload a custom part to your project',
        'form.inner.fields': {
          'target':  'target-' + i,
          'method':  'POST',
          'enctype': 'multipart/form-data',
          'action':  '/assets/upload?html=true',
          '.field:1': {
            'label': 'Name:',
            '.inner.contents': {
              'input.single.control': { type: 'text' }
            }
          },
          '.field.progress:2': {
            'label': 'Drawing/Image of part (MAX:1MB):',
            '.inner.contents': {
              'input.control.file': {
                'name': 'asset',
                'type': 'file'
              }
            }
          },
          '.field:3': {
            'label': 'Additional notes:',
            '.inner.contents': {
              'textarea.multi.control': ''
            }
          }
        },
        'iframe': {
          style: 'width: 0; height: 0; border: 0',
          name: 'target-' + i,
          onload: function() {
            var url;
            try {
              if (url = (this.contentDocument || this.contentWindow.document).body.innerHTML)
                xhr('/assets/reformat', {
                  id: url.match(/\/\/[^\/]+\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)/)[1],
                  format: { mime: 'image/jpeg', width: 99, height: 80 }
                }, function(r) {
                  content.open.finalize(r.url);
                });
            } catch (e) {}
          }
        }
      });
    }).pos().ext({
      
      finalize: function(url) {
        var mods = this.clear('busy').controls();
        xhr('/trapi/', {
          _type: 'PartAssignment',
          project_id: parseInt(window.location.pathname.split('/')[2]),
          'Part details': mods['Name'], 
          'Image URL': url, 
          'Custom details': mods['Additional notes']
        }, function(r) {
          if (r.ok)
            window.location.href = window.location.pathname;
        });
      },

      'Submit dialog': function() {
        if (this.find('file').value())
          this.apply('busy').find('fields').node.submit();
        else
          this.finalize();
        return true;
      }
    });
  },
  
  'Make enquiry': function() {    
    this.dialog('Please tell us as much as you can about your requirements so that we may better respond to your enquiry.').build(function() {
      this.apply('mail');
      this.add('inner').apply('fields').send(function() {
        this.add('multi').set('name', 'Enquiry');
      });
      this.add('inner').apply('feedback').send(function() {
      });
      
    }).pos().ext({
      
      'Submit dialog': function() {
        var com = this;
        if (!this.done)
          xhr('/mail/enquiry', {message: this.controls()['Enquiry'], project_id: window.location.pathname.replace(/[^\d]+/g,'')}, function(r) {
            com.hide('fields');
            com.find('weak').hide();
            if(r.ok){
              com.find('explanation').sets('Thanks - your enquiry has been recieved');
            }else if(r.reason){
              com.find('explanation').sets(r.reason);
            }else{
              com.find('explanation').sets('Unknown error');
            }
            com.done = true;
          });     
        else
          content.exec('Close dialog');
        return false;
      }
    });
  },
  
  'Submit dialog': function() {
    this.exec('Close dialog');
  },
  
  'Close dialog': function() {
    this.node.ownerDocument.documentElement.className = '';
    
    if (this.open) {
      this.clear('dimmed');
      this.open = !this.open.remove();
    }
  },
  
  onClick: function(event) {
    if (Com.drop.open)
      return Com.drop.open.toggle(false);
  },
  
  dialog: function(explanation, action) {
    this.node.ownerDocument.documentElement.className = 'fixed';
    
    return Com.dialog.spawn().move(this.apply('dimmed')).build(function() {
      this.apply('cleared');
      this.add('explanation').sets(explanation);
    }).ammend(function() {
      this.add('button').sets(action || 'OK').set('title', 'Submit dialog');
      this.add('button').apply('weak').sets('Cancel').set('title', 'Close dialog');
    });
  }
});

bind('dialog', {

  close: function() {
    content.clear('dimmed').open = !this.remove();
  },

  pos: function() {
    try {
      this.node.style.marginTop = '-' + Math.round(this.node.offsetHeight / 2) + 'px';
    } catch (e) {}
    return this;
    //return this.set('style', 'margin-top: -' + Math.round(this.node.offsetHeight / 2) + 'px')
  }
  
}).spawn = function() {
  return content.open = load('.dialog.pane', { '.TR': '', '.contents.inner': '', '.BL': '' });
}

def('div.buttons', '', {
  adds: function(o) {
    for (var id in o)
      this.add('button').toggle(o[id] < 1, 'weak').sets(id);
  }
});

bind('folder', {
    
  onExec: function() {
    this.toggle(!this.folder_on, 'folder_on');
    return false;
  }
});

base({
  
  /// Load the JSON stashed in .href
  stash: function() {
    var v = this.read('href') || '';
    if (v.charAt(0) == '#')
      return JSON.parse(v.slice(1));
  }
});

bind('pitem', {
  
  onExec: function(id, ev) {
    if (ev.ic) {
      if (ev.ic.read() == 'Delete part') {
        this.remove();
        xhr('/trapi', cp({ _delete: true }, ev.ic.stash()));
        return false;      
      }
    }
  }
});

bind('actions', {
  
  onExec: function(id, ev) {
    this.parent.fire('exec', ev.ic.read(), JSON.parse(id), ev.ic);
    return false;      
  }
});

bind('pref', {
  
  onExec: function(v, ev) {
    if (window.parent && top.cycle) {
      v = parseInt(ev.ic.read());
      this.update(v);
      xhr('/trapi',cp({ 'Preferability': v }, ev.ic.stash()));
    }
    return false;
  },
  
  update: function(n) {
    this.each('ic', function() { this.toggle(!this.match('remove') && n-- >= 0) });
    return this;
  }
});

bind('prefInfo', {
  
  onMouseover: function() {
    tip.update(this, function() {
      this.add('inner').sets('This colunm shows whether or not the part is a perferred size or if availability is limited.');
      this.add('inner').sets('Please enquire about items with a low availability.');
    });
  }
});

bind('a', {
    
  onMouseover: function() {
    tip.update(this, this.ic.read());
  }
});

bind('tip', {
  
  update: function(parent, v) {
    if (this.parent != parent)
      return this.move(parent).build(function() {
        this.empty();
        typeof v == 'function' ? v.apply(this) : this.sets(v);
      });
  }
});

bind('login', {
    
  onEnter: function(control) {
    if (control == this.last('control'))
      this.exec('login');
  },
  
  onLogin: function() {
    var com = this;
    xhr(this, '/session/login', this.controls(), function(r) {
      if (r.ok && r.key) {
        O3.cookie({ key: r.key });
        window.location = window.location.href.replace('?Login=true', '');
      }
      com.find('error').sets(r.reason || '');
    })
  }
});
new Image().src = '/images/thumper.gif';

bind('Account', {
  
  onEnter: function(control) {
    if (control == this.last('control'))
      this.exec('submit');
  },
  
  onSubmit: function() {
    this.each('error', function() {
      this.sets('');
    });
    var com = this;
    O3.user(function(user) {
      xhr('/trapi',cp({ _type: 'Person' }, user, com.controls()), function(r) {
        if (r.error)
          if (r.error.fields)
            com.fire('error', r.error.fields).node.scrollIntoView();
          else if (r.reason)
            com.find('error').sets(r.reason).node.scrollIntoView();
        if (r.ok)
          window.location = window.location.href.replace('Account', user && user._id ? 'Account updated' : 'Signup completed')
      });      
    })
  },
  
  onError: function(o) {
    this.controls(function(name, value) {
      this.next('error').sets(o[name] || '');
    })
  }
});

bind('filter', {
  init: function() {
    this.observe('click', function(e) {
      var s = this.read('href');
      FILTER = s.slice(s.indexOf('?') + 1);
      xhr(s, function(r) {
        content.find('below').reset(r);
      })
      if (e.stopPropagation)
        e.stopPropagation();
      return false;
    })
  }
})


bind('error');
bind('part');
bind('options', 'control', {
  
  value: function() {
    var option = this.node.options[this.node.selectedIndex];
    return option.value || option.text;
  }
});
bind('option');
bind('opt');

bind('detail', {
  
  init: function() {
    this.observe('click', function() {
      var v = this.id();
      this.fire('exec', 'Edit part details', this, { _id: v[0] }, v[1]);
      return false;
    })
  },
  
  update: function(s) {
    return this.sets(s || 'Click to edit').toggle(!s, 'blank');
  },
  
  id: function() {
    return this.read('href').slice(1).split('-')
  }
});

// function Project(user, name, body) {
//   var BR = "\n\n";
//   
//   var s = '', o, parts = user['Projects'][name]['Parts'];
//   
//   s += 'Web enquiry from ' + user['Email'] + ':';
//   s += BR;
//   s += "Project '" + name + "'";
//   s += BR;
//   for (var id in parts) {
//     for (var _id in parts[id])
//       switch (_id) {
//         case 'Product thumbnail': s += _id + ': ' + O3.asset({ 'Image file': parts[id][_id]['Image file'] }) + "\n"; break;
//         default: {
//           if (o = parts[id][_id])
//             if (typeof o == 'string')
//               if (!/ID/.test(_id))
//                 s += _id + ': ' + o + "\n";
//         }
//       }
//   }
//   s += "\n";
//   s += 'Enquiry:' + "\n";
//   s += body;
//   
//   return s;
// }

// =================================================================================

bind('globals', {
    
  onLogout: function() {
    O3.cookie({ key: null })
    window.location = window.location.href;
    return false;
  }
})

bind('tabs', {
  
  init: function() {
    var com;
    if (com = this.find('search'))
      com.observe('click', function() {
        this.parent.toggle(!this.expanded, 'expanded').next('search', true).toggle().send(function() {
          if (this.on)
            this.find('control').node.focus();
        });
        return false;
      })
  }
});
bind('listing')
}


var preComNames = map("sub", "drop", "ic", "button", "inner", "list", "control", "switch", "content", "dialog", "buttons", "folder", "pitem", "actions", "pref", "prefInfo", "a", "tip", "login", "Account", "filter", "error", "part", "options", "option", "opt", "detail", "globals", "tabs", "listing", "pane", "label", "article", "instruction", "p", "link", "banner", "indicator", "image", "field");
// var n2 = map("sub", "drop", "ic", "button", "inner", "list", "control", "switch", "content", "dialog", "buttons", "folder", "pitem", "actions", "pref", "prefInfo", "a", "tip", "login", "Account", "filter", "error", "part", "options", "option", "opt", "detail", "globals", "tabs", "listing");

for (var id in preComNames)
  if (!Com[id]) bind(id);

function doSendPw(e) {
  var form = e.parentNode;
  xhr('/mail/password', { UsernameOrEmail: form.elements[0].value }, function(r) {
    if (r.error)
      form.childNodes[2].innerHTML = r.reason;
    if (r.ok)
      window.location = '/'
  });
}