/** phyp.project :: core.phyp.js.zero :: 20091213 **/

/** Shortcuts **/
var p = { };
var d = document;
var w = window;
var ie = !!d.all;

function nil() { }
var echo  = nil;
var trace = nil;
var debug = nil;
var bt    = nil;
var __    = nil;

/** Babel **/
__ = function(str, args) {
    return str + (args? ' ' + args: '');
};

/** Compat **/

/// IE lack
//  www.stringify.com/static/js/base64.js
if (typeof btoa == 'undefined') {
    function btoa(s) {
        var cs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 
	         'abcdefghijklmnopqrstuvwxyz' +
	         '0123456789+/=';
        var e = [ ], c = 0, b0, b1, b2, b, i0, i1, i2, i3;
        while (c < s.length) {
            b0 = s.charCodeAt(c++);
            b1 = s.charCodeAt(c++);
            b2 = s.charCodeAt(c++);
            b = (b0 << 16) + ((b1 || 0) << 8) + (b2 || 0);
            i0 = (b & (63 << 18)) >> 18;
            i1 = (b & (63 << 12)) >> 12;
            i2 = isNaN(b1)? 64: (b & (63 << 6)) >> 6;
            i3 = isNaN(b2)? 64: (b & 63);
            e.push(cs.charAt(i0));
            e.push(cs.charAt(i1));
            e.push(cs.charAt(i2));
            e.push(cs.charAt(i3));
        }
        return e.join('');
    }
}
if (typeof atob == 'undefined') {
    function atob(s) {
        var cs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
	         'abcdefghijklmnopqrstuvwxyz' +
	         '0123456789+/=';
        var d = [ ], c = 0, i0, i1, i2, i3, b, b0, b1, b2;
        while (c < s.length) {
            i0 = cs.indexOf(s.charAt(c++));
            i1 = cs.indexOf(s.charAt(c++));
            i2 = cs.indexOf(s.charAt(c++));
            i3 = cs.indexOf(s.charAt(c++));
            b = (i0 << 18) + (i1 << 12) + ((i2 & 63) << 6) + (i3 & 63);
            b0 = (b & (255 << 16)) >> 16;
            b1 = (i2 == 64)? -1: (b & (255 << 8)) >> 8;
            b2 = (i3 == 64)? -1: (b & 255);
            d.push(String.fromCharCode(b0));
            if (b1 >= 0) d.push(String.fromCharCode(b1));
            if (b2 >= 0) d.push(String.fromCharCode(b2));
        }
        return d.join('');
    }
}

/** Misc **/

/// Convert 32bit unsigned value to 8bit hex
function hex32(val) {
    var n, str1, str2;
    n = val & 0xFFFF;
    str1 = n.toString(16).toUpperCase();
    while (str1.length < 4)
	str1 = '0' + str1;
    n = (val >>> 16) & 0xFFFF;
    str2 = n.toString(16).toUpperCase();
    while (str2.length < 4)
	str2 = '0' + str2;
    return '0x' + str2 + str1;
}

/// Popup
function pop(e) {
    e.target = '_blank';
    return true;
}

/// Prevent default
function prevent(event) {
    if (event.preventDefault)
	event.preventDefault();     // Gecko
    else event.returnValue = false; // IE
}

/** Callback **/

/// Debug
var CALLBACK_DBG_ERROR    = '!^ ';
var CALLBACK_DBG_GREEDY   = '^! ';
var CALLBACK_DBG_OK       = '^^ ';
var CALLBACK_DBG_KO       = '^! ';

/// Class
function call(back, env, args, opts, ok) {
    var obj, func;
    var key, ok;
    var start, end;
    var lap, time;
    var msg;
    if (tpf(back))
	return back(env, args, opts, ok);
    if (!tpo(back)) {
	if (!tpu(back))
	    debug('w', 'call', CALLBACK_DBG_ERROR + back);
	return true;
    }
    if (tpb(ok))
	if (ok) func = back.ok || 'success';
	else func = back.ko || 'error';
    else func = back[ok];
    obj = back.obj || back;
    key = '';
    if (obj._mod) key += obj._mod + '.';
    if (obj.mod && obj.mod._mod) key += obj.mod._mod + '.';
    if (obj.mod && obj._sub) key += obj._sub + '.';
    key += func;
    if (obj[func]) {
	start = now();
	ok = obj[func](env, back.args, opts, args);
	end = now();
	time = end - start;
	lap = end - env.time;
	env.time = end;
	lap = min(lap, time);
	if (lap == time)
	    msg = '= ' + time.pad(4, ' ', true) + 'ms | ';
	else msg = '+ ' + lap.pad(4, ' ', true) + 'ms | ';
	if (lap > 20)
	    debug('w', 'call', CALLBACK_DBG_GREEDY + msg + key);
	else if (obj._mod != 'events')
	    debug('v', 'call', CALLBACK_DBG_OK + msg + key);
	return ok;
    }
    debug('w', 'call', CALLBACK_DBG_KO + key);
    return false;
}

/** Object-Oriented **/

/// Object prototype
// www.coolpage.com/developer/javascript/Correct%20OOP%20for%20Javascript.html
Object.prototype.proto = function(e) {
    if (arguments.length > 1)
	e.apply(this, Array.prototype.slice.call(arguments, 1));
    else e.call(this);
};
Function.prototype.proto = function(e) {
    this.prototype = new e();
    this.prototype.constructor = this;
};

/** JSON **/

/// Object to string
Object.prototype.toString = function(z) {
    var i, k, v, e = [ ];
    if (z == null) z = 3;
    if (!z) return '>';
    for (i in this) {
	if (this[i] == null) continue;
	k = '\'' + i + '\'';
	switch (type(this[i])) {
	case 'function': continue; break;
	case 'object':   v = this[i].toString(z - 1); break;
	case 'string':   v = '\'' + this[i].encode() + '\''; break;
	case 'number':   v = this[i]; break;
	case 'boolean':  v = this[i]? 'true': 'false'; break;
	default:         v = ''; break;
	}
	e.push(k + ': ' + v);
    }
    return '{ ' + e.join(', ') + ' }';
};
Array.prototype.toString = function(z) {
    var i, k, v, e = [ ];
    if (z == null) z = 3;
    if (!z) return '>';
    for (i = 0; i < this.length; i++) {
	switch (type(this[i])) {
	case 'function': continue; break;
	case 'object':   v = this[i].toString(z - 1); break;
	case 'string':   v = '\'' + this[i].encode() + '\''; break;
	case 'number':   v = this[i]; break;
	case 'boolean':  v = this[i]? 'true': 'false'; break;
	default:         v = ''; break;
	}
	e.push(v);
    }
    return '[ ' + e.join(', ') + ' ]';
};

/// String to object
Object.prototype.toObj = function() {
    return this.clone();
};
String.prototype.toObj = function() {
    var kv, k, v, i, a, o;
    var s = this.sub(2, -2);
    var es = s.split(', ');
    for (i in es) {
	if (tpf(es[i])) continue;
	a = (es[i].indexOf(':') == -1);
	if (!o) o = a? [ ]: { };
	kv = es[i].split(': ');
	k = kv[0].sub(1, -1);
	v = (a)? k: kv[1];
	v = v.decode();
	switch (v[0]) {
	case '\'':
	    v = v.sub(1, -1);
	    break;
	case '[':
	case '{':
	    v = v.toObj();
	    break;
	default:
	    if (v.indexOf('.') != -1)
		v = parseFloat(v);
	    else v = parseInt(v);
	}
	if (a) o.push(v);
	else o[k] = v;
    }
    return o;
};

/** XML **/

/// Debug
var XML_DBG_INVALID_KEY = 'x! ';

/// To XML
Object.prototype.toXML = function(tag, header) {
    var i, arr;
    var ls = [ ];
    if (!tag.match(/^[a-z0-9]+$/i)) {
	debug('w', 'toXML', XML_DBG_INVALID_KEY + tag);
	return '';
    }
    if (tag.match(/^[0-9]/)) tag = '_' + tag;
    if (header) ls.push('<?xml version="1.0" encoding="utf-8"?>');
    arr = (this[0] != null);
    ls.push('<' + tag + ' type="' + (arr? 'array': 'object') + '">');
    for (i in this)
	if (nf(this[i])) {
	    if (!tpu(this[i]))
		ls.push(this[i].toXML(arr? 'element': i));
	}
    ls.push('</' + tag + '>');
    return ls.join("\r\n");
};
String.prototype.toXML = function(tag) {
    var str;
    if (this.indexOf("\n") != -1)
	str = '<![CDATA[' + this + ']]>';
    else str = this.encode();
    if (tag.match(/^[0-9]/)) tag = '_' + tag;
    return '<' + tag + ' type="string">' + str + '</' + tag + '>';
};
Number.prototype.toXML = function(tag) {
    if (tag.match(/^[0-9]/)) tag = '_' + tag;
    return '<' + tag + ' type="number">' + this + '</' + tag + '>';
};
Boolean.prototype.toXML = function(tag) {
    var str;
    str = (this == true)? 'true': 'false';
    if (tag.match(/^[0-9]/)) tag = '_' + tag;
    return '<' + tag + ' type="boolean">' + str + '</' + tag + '>';
};

/// From XML
//  Not an object prototype, thanks to IE...
function fromXML(e, arr) {
    if (!e) return false;
    var i, j, v, t, c, n;
    var x = arr? [ ]: { };
    var l = e.childNodes.length;
    for (i = 0; i < l; i++) {
	c = e.childNodes[i];
	n = c.nodeName;
	if (n.sub(0, 1) == '_')
	    n = n.sub(1);
	if (!n || n.firstChar() == '#')
	    continue;
	try {
	    v = c.firstChild.nodeValue;
	    t = c.getAttribute('type');
	} catch(err) {
	    // alert(err + ": " + c);
	}
	switch (t) {
	case 'arr':
	case 'array': v = fromXML(c, true); break;
	case 'obj':
	case 'object': v = fromXML(c); break;
	case 'bool':
	case 'boolean': v = (v == 'true'); break;
	case 'int':
	case 'float':
	case 'num':
	case 'number': v = parseFloat(v); break;
	default:
	    if (v && t != type(v))
		debug('v', 'xml', '<> ' + [ t, type(v), n ]);
	    if (!v) v = '';
	    v = v.decode();
	}
	if (arr) x.push(v);
	else x[n] = v;
    }
    return x;
}

/** Memory **/

/// Debug
var MEM_DBG_TYPE_ERROR  = 'e! ';

/// Object size
Object.prototype.msize = function() {
    var s = 0;
    try {
	for (i in this) {
	    if (this[i] == null) continue;
	    if (i == 'obj' || i == 'ca' || i == 'parent')
		continue; // skip obj, cache, and parents
	    if (nn(this[i]) !== false) continue;
	    switch (type(this[i])) {
	    case 'function': break;
	    case 'object':   s += this[i].msize(); break;
	    case 'string':   s += this[i].length; break;
	    case 'number':   s += 8;
	    case 'boolean':  s++;
	    }
	}
    } catch(e) {
	debug('w', 'mem', MEM_DBG_TYPE_ERROR + i);
    }
    return s;
};
Array.prototype.msize = function() {
    var s = 0;
    try {
	for (i = 0; i < this.length; i++) {
	    if (nn(this[i]) !== false) continue;
	    switch (type(this[i])) {
	    case 'function': break;
	    case 'object':   s += this[i].msize(); break;
	    case 'string':   s += this[i].length; break;
	    case 'number':   s += 8;
	    case 'boolean':  s++;
	    }
	}
    } catch(e) {
	debug('w', 'mem', MEM_DBG_TYPE_ERROR + i);
    }
    return s;
};

/** Number **/

/// Human size
Number.prototype.hsize = function() {
    var u = 0;
    var s = this;
    var us = ' kMGT'.toArray();
    while (s > 1000) { u++; s /= 1024; }
    if (s < 2)
	return round(s * 10) / 10 + us[u];
    return round(s) + us[u];
};

/// Zeros filled
Number.prototype.zeros = function(n) {
    if (!n) n = 2;
    return ('' + this).pad(n, '0', true);
};

/// Circular mod
Number.prototype.cmod = function(n) {
    return (this + n) % n;
};

/** Object **/

/// Merge
Object.prototype.merge = function(obj) {
    var i;
    for (i in obj) {
	switch (type(obj[i])) {
	case 'function': break;
	case 'object':
	    if (this[i] == null) this[i] = { };
	    this[i].merge(obj[i]);
	    break;
	default: this[i] = obj[i];
	}
    }
};

/// Indexes & values
Object.prototype.indexes = function() {
    var i, x;
    x = [ ];
    for (i in this)
	if (nf(this[i]))
	    x.push(i);
    return x;
};
Object.prototype.values = function(chr) {
    var i, x;
    x = [ ];
    for (i in this)
	if (nf(this[i]))
	    x.push(this[i]);
    return x;
};

/// Clone
//  www.faqts.com/knowledge_base/view.phtml/aid/6231
Object.prototype.clone = function() {
    var i, e = new this.constructor();
    for (i in this)
	if (this[i] &&
	    tpo(this[i]) &&
	    this[i].clone)
	    e[i] = this[i].clone();
	else e[i] = this[i];
    return e;
};

/// Join
Object.prototype.join = function(chr) {
    var i, a = [ ];
    for (i in this)
	if (nf(this[i]))
	    a.push(i + ': ' + this[i]);
    return a.join(chr);
};

/** Object and array **/

/// Index of
Array.prototype.indexOf = function(v) {
    var i;
    for (i = 0; i < this.length; i++)
	if (this[i] == v)
	    return i;
    return -1;
};
Object.prototype.indexOf = function(v) {
    var i;
    for (i in this)
	if (nf(this[i]) &&
	    this[i] == v)
	    return i;
    return null;
};

/// In object
Array.prototype.inArray = function(v) {
    return this.indexOf(v) != -1;
};
Object.prototype.inObj = function(v) {
    return this.indexOf(v) != null;
};

/// Except
Array.prototype.except = function(v) {
    if (!this.inArray(v))
	return this;
    var i, vs = [ ];
    for (i = 0; i < this.length; i++)
	if (this[i] != v)
	    vs.push(this[i]);
    return vs;
};

/// Move up
Array.prototype.moveUp = function(v) {
    var j, i = this.indexOf(v);
    if (i == -1) this.unshift(v);
    else {
	for (j = i; j > 0; j--)
	    this[j] = this[j - 1];
	this[0] = v;
    }
};

/// Compact
Array.prototype.compact = function(ws) {
    var s, a, v, i, j;
    a = [ ];
    for (i = 0; i < this.length; i++) {
	s = '';
	for (j = 0; j < ws.length; j++)
	    s += this[i][j].pad(abs(ws[j]), ' ', ws[j] < 0);
	a.push(s);
    }
    return a;
};

/** String **/

/// Upper case first letter
String.prototype.ucFirst = function() {
    if (!this) return '';
    return this.charAt(0).toUpperCase() + this.sub(1);
};

/// Lower case first letter
String.prototype.lcFirst = function() {
    if (!this) return '';
    return this.charAt(0).toLowerCase() + this.sub(1);
};

/// Find first upper case letter
String.prototype.firstUp = function(offset) {
    var i;
    if (!this) return -1;
    if (!offset) offset = 0;
    for (i = offset; i < this.length; i++)
	if (this.charAt(i).toUpperCase() == this.charAt(i))
	    return i;
    return -1;
};

/// Upper split
String.prototype.splitUp = function(chr, low, first) {
    var i, s = '';
    if (!chr) chr = ' ';
    i = this.firstUp(1);
    if (i < 1) {
	s = this;
	if (low) s = s.toLowerCase();
    } else {
	if (!first) {
	    s = this.sub(0, i);
	    if (low) s = s.toLowerCase();
	    s += chr;
	} else s = '';
	s += this.sub(i).splitUp(chr, low);
    }
    return s;
};

/// First char // Obsolete? [0] would do the trick
String.prototype.firstChar = function() {
    return this.charAt(0);
};

/// Last char
String.prototype.lastChar = function() {
    return this.charAt(this.length - 1);
};

/// Last word
String.prototype.lastWord = function() {
    return this.substr(this.lastIndexOf(' ') + 1);
};

/// First word
String.prototype.firstWord = function() {
    if (this.indexOf(' ') == -1)
	return this.substr(0, this.length); // Need to clone
    return this.substr(0, this.indexOf(' '));
};

/// HTML special chars
String.prototype.html = function(noq) {
    var s = this.replace(/&/g, '&amp;');
    if (!noq) s = s.replace(/\"/g, '&quot;');
    s = s.replace(/</g, '&lt;');
    s = s.replace(/>/g, '&gt;');
    return s;
};

/// Substring
String.prototype.sub = function(a, b) {
    if (a < 0) a += this.length;
    if (b == null) return this.substring(a);
    if (b < 0) b += this.length;
    return this.substring(a, b);
};

/// To array
String.prototype.toArray = function() {
    var i, a = [ ];
    for (i = 0; i < this.length; i++)
	a.push(this[i]);
    return a;
};

/// Pad
String.prototype.pad = function(len, chr, left) {
    var str = this;
    if (!chr) chr = ' ';
    if (str.length > len) {
	str = str.sub(0, len - 3) + '...';
    } else {
	while (str.length < len)
	    if (left) str = chr + str;
	    else str += chr;
    }
    return str;
};
Number.prototype.pad = function(len, chr, left) {
    return ('' + this).pad(len, chr, left);
};

/** Coords **/

/// Lib
var coords = {

    /// Neg
    'neg': function(a) {
	var i;
	var b = { };
	for (i in a)
	    if (nf(a[i]))
		b[i] = -a[i];
	return b;
    },

    /// Add
    'add': function(a, b) {
	var i;
	var c = { };
	for (i in a)
	    if (nf(a[i]) && b[i] != null)
		c[i] = a[i] + b[i];
	return c;
    },

    /// Substract
    'sub': function(a, b) {
	return this.add(a, this.neg(b));
    },

    /// Multiply
    'mul': function(a, r) {
	var i;
	var c = { };
	for (i in a) {
	    if (nf(a[i])) {
		c[i] = a[i] * r;
		if (i != 'a') c[i] = round(c[i]);
	    }
	}
	return c;
    },

    /// Proportional
    'prop': function(a, b, r) {
	return this.add(a, this.mul(this.sub(b, a), r));
    },

    /// Inside
    'inside': function(a, b) {
	return (a.x >= b.x &&
		a.x + a.w <= b.x + b.w &&
		a.y >= b.y &&
		a.y + a.h <= b.y + b.h);
    },

    /// Boundary
    'boundary': function(a, b) {
	return { 'x': between(a.x, b.x, b.x + b.w - a.w),
		 'y': between(a.y, b.y, b.y + b.h - a.h) };
    },

    /// Intersect
    'inter': function(a, b) {
	var x1 = max(a.x, b.x);
	var y1 = max(a.y, b.y);
	var x2 = min(a.x + a.w, b.x + b.w);
	var y2 = min(a.y + a.h, b.y + b.h);
	if (x2 < x1 || y2 < y1)
	    return false;
	return { 'x': x1,
		 'y': y1,
		 'w': x2 - x1,
		 'h': y2 - y1 };
    }

};

/** Range **/

/// Lib
var range = {

    /// Set
    'set': function() { },

    /// Get
    'get': function() { },

    /// Clear
    'clear': function() {
	var s;
	if (d.selection && d.selection.empty)
	    d.selection.empty();
	else if (w.getSelection
		 && (s = w.getSelection())
		 && s.removeAllRanges)
	    s.removeAllRanges();
	else select();
    }

};

/** String **/

/// Path
String.prototype.path = function(dir) {
    var f = this;
    if (!f || (!f.match(/^[a-z]+:/) && f.firstChar() != '/'))
	f = '/' + f;
    f = f.replace(/^.*\/~\//, HOME + '/');
    while (f.indexOf('./') != -1)
	f = f.replace(/\/+\.\//g, '/').replace(/\/*[^\/]*\/\.\.\//g, '/');
    return f;
};

/// Name
String.prototype.name = function(h) {
    var s = this;
    if (s.indexOf('&') != -1)
	s = s.substr(0, s.indexOf('&'));
    if (s.isFolder())
	s = s.substr(0, s.length - 1);
    if (!h)
	return s.substr(s.lastIndexOf('/') + 1);
    return s.substring(s.lastIndexOf('/') + 1, s.lastIndexOf('.'));
};

/// Title
String.prototype.title = function() {
    return this.replace(/[\._]/g, ' ');
};

/// Extension
String.prototype.ext = function() {
    if (this.isFolder())
	return 'folder';
    if (this.lastIndexOf('/') >= this.lastIndexOf('.'))
	return false;
    return this.substr(this.lastIndexOf('.') + 1).toLowerCase();
};

/// Is folder?
String.prototype.isFolder = function() {
    return (this.lastChar() == '/');
};

/// Folder
String.prototype.folder = function() {
    if (this.isFolder()) return this + '';
    return this.substr(0, this.lastIndexOf('/') + 1);
};

/// Parent
String.prototype.parent = function() {
    if (this.isFolder() && this != '/')
	return this.sub(0, -1).folder();
    return this.folder();
};

/** Arrays **/

/// First & last elements
Array.prototype.first = function() {
    return this[0];
};
Array.prototype.last = function() {
    return this[this.length - 1];
};

/** Encode / decode **/

/// Debug
var DECODE_DBG_ERROR      = 'd! ';

/// Encode
String.prototype.encode = function(p) {
    var e = encodeURIComponent(this);
    if (!p) return e;
    e = e.replace(/%2F/g, '/').replace(/%3A/g, ':');
    return e.replace(/\/\.\.?\//g, '');
};

/// Decode
String.prototype.decode = function() {
    try {
	return decodeURIComponent(this);
    } catch(e) {
	debug('w', 'decode', DECODE_DBG_ERROR + this);
	return this;
    }
};

/// Decode
Boolean.prototype.decode = function() {
    debug('w', 'protos', DECODE_DBG_ERROR + 'boolean');
    return this;
};

/** Date **/

/// Now
function now() { var t = new Date(); return t.getTime(); }

/// Human date and time
Date.prototype.htime = function() { return this.strftime('%X'); };
Date.prototype.hdate = function() { return this.strftime('%x'); };
Date.prototype.hdatetime = function() { return this.strftime('%c'); };

/// Strftime (swiss knife)
Date.prototype.strftime = function(f) {
    var i, letters, str;
    var days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
		 'Thursday', 'Friday', 'Saturday' ];
    var sdays = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
    var months = [ 'January', 'February', 'March', 'April', 'May',
		   'June', 'July', 'August', 'Septembre',
		   'October', 'November', 'December' ];
    var smonths = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
		    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
    var fy = new Date(this.getFullYear(), 0, 1);
    var fd = fy.getDay();
    var fw = (fd < 2)? fd: 7;
    var ty = this - fy;
    var dy = 86400000;
    // Build values
    var rx = { };
    // Day
    rx.e = this.getDate();                       // %e: day of the month
    rx.d = rx.e.zeros();                         // %d: day of the month with leading zero
    rx.w = this.getDay();                        // %w: numeric rep. of the day of the week
    rx.u = rx.w + 1;                             // %u: ISO-8601 rep. of the day of the week
    rx.a = __("babelDay" + sdays[rx.w]);         // %a: abb. textual rep. of the day
    rx.A = __("babelDay" + days[rx.w]);          // %A: full textual rep. of the day
    rx.J = ceil(ty / dy);                        // %J: day of the year
    rx.j = rx.J.zeros(3);                        // %j: day of the year with leading zeros (3)
    // Week
    rx.U = ceil((rx.J - fd) / 7).cmod(53);       // %U: week (Sun start)
    rx.V = ceil((rx.J - fw) / 7).cmod(53);       // %V: week (ISO-8601)
    rx.W = ceil((rx.J - fd - 1) / 7).cmod(53);   // %W: week (Mon start)
    // Month
    rx.n = this.getMonth() + 1;                  // %n: 1/2 digit rep. of the month
    rx.m = rx.n.zeros();                         // %m: 2 digit rep. of the month
    rx.b = __("babelMonth" + smonths[rx.n - 1]); // %b: abb. month name
    rx.B = __("babelMonth" + months[rx.n - 1]);  // %B: full month name
    // Year
    rx.Y = this.getFullYear();                   // %Y: 4 digit rep. of the year
    rx.y = ('' + rx.Y).sub(-2);                  // %y: 2 digit rep. of the year
    rx.G = rx.Y - (rx.J < fw);                   // %G: 4 digit ISO-8601 rep. of the year
    rx.g = ('' + rx.G).sub(-2);                  // %g: 2 digit ISO-8601 rep. of the year
    rx.C = ceil(rx.Y / 100);                     // %C: 2 digit rep. of the century
    // Time
    rx.h = this.getHours();                      // %h: 1/2 digit 24-hour rep. of the hour
    rx.H = rx.h.zeros();                         // %H: 2 digit 24-hour rep. of the hour
    rx.l = ((rx.h - 1) % 12) + 1;                // %i: 1/2 digit 12-hour rep. of the hour
    rx.I = rx.l.zeros();                         // %I: 2 digit 12-hour rep. of the hour
    rx.L = this.getMinutes();                    // %L: 1/2 digit rep. of the minute
    rx.M = rx.L.zeros();                         // %M: 2 digit rep. of the minute
    rx.p = (rx.h < 12)? 'AM': 'PM';              // %p: uppercase AM/PM
    rx.P = rx.p.toLowerCase();                   // %P: lowercase AM/PM
    rx.t = this.getSeconds();                    // %t: 1/2 digit rep. of the second
    rx.S = rx.t.zeros();                         // %S: 2 digit rep. of the second
    rx.k = this.getTime();                       // %k: 1000x Unix time
    rx.s = floor(rx.k / 1000);                   // %s: Unix time
    // Locale
    f = f.replace('%X', "%T");
    f = f.replace('%x', "%F");
    f = f.replace('%c', "%F %T");
    /*
    f = f.replace('%X', __("babelLocTime"));     // %X: preferred time rep.
    f = f.replace('%x', __("babelLocDate"));     // %x: preferred date rep.
    f = f.replace('%c', __("babelLocDatetime")); // %c: preferred date and time rep.
    */
    // Replacements
    rx['%'] = '%';
    f = f.replace('%r', '%I:%M:%S %p');          // %r: same as %I:%M:%S %p
    f = f.replace('%R', '%H:%M');                // %R: same as %H:%M
    f = f.replace('%T', '%H:%M:%S');             // %T: same as %H:%M:%S
    f = f.replace('%D', '%m/%d/%y');             // %D: same as %m/%d/%y
    f = f.replace('%F', '%Y-%m-%d');             // %F: same as %Y-%m-%d
    // Execute
    letters = f.toArray();
    str = '';
    for (i = 0; i < letters.length; i++)
	if (letters[i] == '%')
	    str += rx[letters[++i]];
	else str += letters[i];
    return str;
};

/// Human ETA
Number.eta = function() {
    /// TODO
};

/** DOM **/

/// Debug
var DOM_DBG_NO_FRAME    = '!f ';

/// DOM constructor
function dom(tag, atts, nodes) {
    return new Node(tag, atts, nodes);
}

/// Class
function Node(tag, atts, nodes) {
    // Element
    if (tpo(tag)) {
	this.e = tag;
	return;
    }
    var i, s, e;
    // Switch
    if (!tpo(atts)) {
	s = atts;
	atts = (tpo(nodes))? nodes: { };
	nodes = s;
    }
    if (!tps(tag)) {
	debug('e', 'node', "bad tag: " + tag);
	return false;
    }
    // Classname
    if (tag.indexOf('.') != -1) {
	s = tag.split('.');
	tag = s[0];
	atts['class'] = s[1];
    }
    // Element
    e = d.createElement(tag);
    for (i in atts) {
	if (tpf(atts[i]))
	    continue;
	try {
	    if (i == 'class')
		e.className = atts[i];
	    else e.setAttribute(i, atts[i]);
	} catch(err) { alert(err + '\n' + tag + ', ' + i + ', ' + atts[i]); }
    }
    // Nodes
    if (nodes) {
	if (!tpo(nodes) || nodes.e)
	    nodes = { 'node': nodes };
	for (i in nodes) {
	    if (tpf(nodes[i]))
		continue;
	    if (tps(nodes[i]) || tpn(nodes[i]))
		e.appendChild(d.createTextNode(nodes[i]));
	    else {
		try {
		    e.appendChild(nodes[i].e);
		} catch(err) {
		    alert(err + ': ' + nodes[i]);
		}
	    }
	}
    }
    this.e = e;
}

/// Attributes
Node.prototype.getAtts = function() { return this.e.attributes; };
Node.prototype.getAtt = function(att) { return this.e.getAttribute(att); };
Node.prototype.setAtt = function(att, v) { this.e.setAttribute(att, v); return this; };
Node.prototype.rmAtt = function(att) { this.e.removeAttribute(att); return this; };
Node.prototype.rmAtts = function(atts) {
    var i;
    for (i = 0; i < atts.length; i++)
	this.rmAtt(atts[i]);
    return this;
};
Node.prototype.setAtts = function(atts) {
    var att;
    for (att in atts)
	if (nf(atts[att]))
	    this.setAtt(att, atts[att]);
    return this;
};

/// Insert, move & remove
Node.prototype.append = function(m) {
    var i;
    if (!m.e)
	for (i = 0; i < m.length; i++)
	    this.e.appendChild(m[i].e);
    else this.e.appendChild(m.e);
    return this;
};
Node.prototype.insert = function(m, n) {
    var i;
    if (!m.e)
	for (i = 0; i < m.length; i++)
	    this.e.insertBefore(m[i].e, n.e);
    else this.e.insertBefore(m.e, n.e);
    return this;
};
Node.prototype.after = function(m) { this.parent().insert(m, this.next()); return this; };
Node.prototype.before = function(m) { this.parent().insert(m, this); return this; };
Node.prototype.remove = function(m) { this.e.removeChild(m.e); return this; };
Node.prototype.shift = function() { var e = this.first(); this.remove(e); return e; };
Node.prototype.unshift = function(m) { this.first().before(m); return this; };
Node.prototype.pop = function() { var e = this.last(); this.remove(e); return e; };
Node.prototype.detach = function() { this.parent().remove(this); return this; };
Node.prototype.moveEnd = function() { var f = this.parent(); f.remove(this); f.append(this); return this; };
Node.prototype.moveAfter = function(m) { this.detach(); this.after(m); return this; };
Node.prototype.moveBefore = function(m) { this.detach(); this.before(m); return this; };

/// Childnodes
Node.prototype.length = function() { return this.e.childNodes.length; };
Node.prototype.node = function(i) { return dom(this.e.childNodes[parseInt(i)]); };
Node.prototype.index = function(m) {
    var i, f = m.parent();
    for (i = 0; i < f.length(); i++)
	if (f.node(i) == m)
	    return i;
    return -1;
};
Node.prototype.first = function() { return dom(this.e.firstChild); };
Node.prototype.last = function() { return dom(this.e.childNodes[this.e.childNodes.length - 1]); };
Node.prototype.gfirst = function() { return this.first().first(); };
Node.prototype.clear = function(n) { n |= 0; while (this.length() > n) this.shift(); return this; };
Node.prototype.truncate = function(n) { n |= 0; while (this.length() > n) this.pop(); return this; };

/// Siblings
Node.prototype.previous = function(txt) {
    if (!this.e.previousSibling)
	return false;
    var m = dom(this.e.previousSibling);
    return (!m.name() && !txt)? m.previous(): m;
};
Node.prototype.next = function(txt) {
    if (!this.e.nextSibling)
	return false;
    var m = dom(this.e.nextSibling);
    return (!m.name() && !txt)? m.next(): m;
};

/// Parents
Node.prototype.parent = function() { return dom(this.e.parentNode); };
Node.prototype.gparent = function() { return this.parent().parent(); };

/// Elements
Node.prototype.id = function(id) { return dom(this.e.getElementById(id)); };
Node.prototype.tags = function(tag) {
    var i;
    var ms = [ ];
    var es = this.e.getElementsByTagName(tag);
    for (i = 0; i < es.length; i++)
	ms.push(dom(es[i]));
    return ms;
};
Node.prototype.tag = function(tag) { return this.tags(tag)[0]; }
Node.prototype.classNodes = function(c) {
    var i;
    var ms = [ ];
    var es = this.e.all || this.e.getElementsByTagName('*');
    for (i = 0; i < es.length; i++)
	if (es[i].className && es[i].className.split(' ').inArray(c))
	    ms.push(dom(es[i]));
    return ms;
};
Node.prototype.classNode = function(c) { return this.classNodes(c)[0]; };
Node.prototype.attNodes = function(att) {
    var i;
    var ms = [ ];
    var es = this.e.all || this.e.getElementsByTagName('*');
    for (i = 0; i < es.length; i++)
	if (es[i].attributes && es[i].getAttribute(att) != null)
	    ms.push(dom(es[i]));
    return ms;
};
Node.prototype.attNode = function(att) { return this.attNodes(att)[0]; };

/// Frames
Node.prototype.body = function() { return this.doc()? dom(this.doc().e.body): false; };
Node.prototype.doc = function() {
    var doc;
    if (this.e.contentDocument) doc = this.e.contentDocument; // NS6
    else if (this.e.contentWindow) doc = this.e.contentWindow.document; // IE5.5, IE6
    else if (this.e.document) doc = this.e.document; // IE5
    else {
	debug('e', 'fb', DOM_DBG_NO_FRAME);
	return false;
    }
    return dom(doc);
};

/// Contents
Node.prototype.clone = function(a) { return new node(this.e.cloneNode(a)); }
Node.prototype.html = function(h) {
    if (h == null)
	return this.e.innerHTML;
    this.e.innerHTML = h;
    return this;
};
Node.prototype.text = function(t) {
    if (ie) return (t == null)? this.e.innerText: this.e.innerText = t;
    if (t == null)
	return this.e.textContent;
    this.e.textContent = t;
    return this;
};
Node.prototype.name = function() {
    if (!this.e.nodeName) return false;
    var n = this.e.nodeName.toLowerCase();
    return (n.firstChar() == '#')? '': n;
};
Node.prototype.value = function() {
    // TODO: select, checkbox, textarea, form...
    return this.e.value;
};
Node.prototype.reset = function() {
    // TODO: select, checkbox, textarea, form...
    return this.e.value = '';
};
Node.prototype.submit = function() { this.e.submit(); return this; };
Node.prototype.focus = function() { this.e.focus(); return this; };
Node.prototype.blur = function() { this.e.blur(); return this; };
Node.prototype.select = function() { this.e.select(); return this; };
Node.prototype.enable = function() { this.e.disabled = false; return this; };
Node.prototype.disable = function() { this.e.disabled = true; return this; };

/// CSS
Node.prototype.css = function(c, on) {
    trace('css', c);
    var cs = this.e.className.split(' ');
    if (c == null)
	return cs;
    if (on == null) {
	this.e.className = c;
    } else {
	if (on && !cs.inArray(c)) cs.push(c);
	if (!on) cs = cs.except(c);
	this.e.className = cs.join(' ');
    }
    if (!this.e.className)
	this.rmAtt('class');
    return this;
};
Node.prototype.toggle = function(a, b) {
    if (this.css().inArray(a)) {
	this.css(a, false);
	this.css(b, true);
    } else {
	this.css(b, false);
	this.css(a, true);
    }
    return this;
};
Node.prototype.scrollers = function(on) {
    var i;
    var es = this.classNodes('scroll');
    for (i = 0; i < es.length; i++) {
	if (on) {
	    es[i].e.scrollLeft = parseInt(es[i].getAtt('scrollX'));
	    es[i].e.scrollTop = parseInt(es[i].getAtt('scrollY'));
	} else {
	    es[i].setAtts({ 'scrollX': es[i].e.scrollLeft,
			    'scrollY': es[i].e.scrollTop });
	}
    }
    return this;
};

/// Style
String.prototype.toCss = function() {
    if (s == 'float')
	return ie? 'styleFloat': 'cssFloat';
    if (s.indexOf('-') == -1)
	return s;
    var t;
    var ss = s.split('-');
    t = ss.shift();
    while (ss.length)
	t += ss.shift().ucFirst();
    return t;
};
Node.prototype.getStyle = function(s) { return this.e.style[s.toCss()]; };
Node.prototype.setStyle = function(s, v) { return this.e.style[s.toCss()] = v; };
Node.prototype.setStyles = function(ss, r) {
    var s;
    if (!r) r = '';
    for (s in ss) {
	if (tpf(ss[s]) || ss[s] == null)
	    continue;
	if (tpo(ss[s]))
	    this.setStyles(ss[s], r + s + '-');
	else this.setStyle(r + s, ss[s]);
    }
};
Node.prototype.getAlpha = function() {
    var a, m;
    if (ie) {
	a = this.getStyle('filter');
	if (a.match(/opacity=([0-9]+),/, m))
	    return m[1] / 100;
	return 1;
    }
    return this.getStyle('opacity');
};
Node.prototype.setAlpha = function(a) {
    // debug('v', 'node', "set alpha: " + a);
    if (ie) {
	if (a == null) this.setStyle('filter', '');
	else this.setStyle('filter', 'Alpha(opacity=' + round(a * 100) + ',style=0)');
    }
    if (a == null) this.setStyle('opacity', '');
    else this.setStyle('opacity', a);
    return this;
};

/// Coords
Node.prototype.getCoords = function(mod, rec) {
    var cs = { };
    var nns  = [ 'ol', 'ul', 'li', 'img', 'div', 'tr', 'table' ];
    var recs = [ 'ol', 'ul', 'li' ];
    var cls  = [ 'so', 'sf', 'm' ];
    if (!ie) {
	cls.push('bs');
	cls.push('c');
    }
    if (!mod || mod % 2) {
	cs.x = this.e.offsetLeft;
	cs.y = this.e.offsetTop;
    }
    if (!mod || mod == 2) {
	cs.w = this.e.offsetWidth;
	cs.h = this.e.offsetHeight;
    }
    if (rec) {
	if (rec === true) rec = { };
	if (!nns.inArray(this.name()) ||
	    (this.name() == 'ul' && mod == 3) ||
	    (this.css().inArray('bk') && !ie) ||
	    cls.inArray(this.css().shift()) ||
	    (this.name() == 'li' &&
	     (rec.li || rec.ul))) {
	    cs.x = cs.y = 0;
	} else {
	    if (recs.inArray(this.name()))
		rec[this.name()] = true;
	}
	if (mod == 3) {
	    cs.w = cs.h = 0;
	    if (this.parent() && this.parent().e.scrollLeft)
		cs.x -= this.parent().e.scrollLeft;
	    if (this.parent() && this.parent().e.scrollTop)
		cs.y -= this.parent().e.scrollTop;
	}
    }
    if (rec && this.e != d.body) {
	// if (cs.x || cs.y)
	// debug('v', 'gd' ,"+ " + cs + " (" + nn(e) + "." + c(e).firstWord() + ")");
	// var a = gd(pn(e), 3, rec);
	// debug('v', 'gd' ,"= " + da(cs, a));
	// return da(cs, a);
	return coords.add(cs, this.parent().getCoords(3, rec));
    }
    // cs.a = alpha(e);
    return cs;
};
Node.prototype.setCoords = function(cs) {
    var ss = { };
    if (cs.x != null && !isNaN(cs.x))
	ss.left = cs.x + 'px';
    if (cs.y != null && !isNaN(cs.y))
	ss.top = cs.y + 'px';
    if (cs.w != null)
	ss.width = max(0, cs.w) + 'px';
    if (cs.h != null)
	ss.height = max(0, cs.h) + 'px';
    this.setStyles(ss);
    if (cs.a != null)
	this.setAlpha(cs.a);
    return this;
};
Node.prototype.getZone = function() {
    var zs, n;
    switch (this.name()) {
    case 'ul':
    case 'img': n = this; break;
    default: n = this.gparent();
    }
    zs = n.getCoords(1, true);
    zs.merge(n.parent().getCoords(2));
    return zs;
};
Node.prototype.inZone = function(zs, full) {
    var cs = this.getCoords(0, true);
    if (!full)
	return (cs.x + cs.w > zs.x &&
		cs.x < zs.x + zs.w &&
		cs.y + cs.h > zs.y &&
		cs.y < zs.y + zs.h);
    return (cs.x >= zs.x &&
	    cs.x + cs.w <= zs.x + zs.w &&
	    cs.y >= zs.y &&
	    cs.y + cs.h <= zs.y + zs.h);
};
Node.prototype.getMask = function(zs, wd, o) {
    var os, ws;
    var cs = this.getCoords(0, true);
    if (o) {
	os = { x: -o, y: -o,
	       w: 2 * o, h: 2 * o };
	cs = coords.add(cs, os);
    }
    if (!wd) wd = { 'x': 0, 'y': 0 };
    ws = { 'x': wd.x, 'y': wd.y,
	   'w': 0, 'h': 0 };
    if (cs.x + cs.w > zs.x + zs.w)
	cs.w = (zs.x + zs.w - cs.x);
    if (cs.y + cs.h > zs.y + zs.h)
	cs.h = (zs.y + zs.h - cs.y);
    if (cs.x < zs.x) {
	cs.w -= (zs.x - cs.x);
	cs.x = zs.x;
    }
    if (cs.y < zs.y) {
	cs.h -= (zs.y - cs.y);
	cs.y = zs.y;
    }
    return coords.sub(cs, ws);
};
Node.prototype.getScroll = function() {
    return { 'x': this.e.scrollX,
	     'y': this.e.scrollY };
};
Node.prototype.setScroll = function(scroll) {
    this.e.scrollX = scroll.x;
    this.e.scrollY = scroll.y;
    return this;
};

/** Picture **/

/// Class
function picture(env, src, opts, back) {
    var img = new Image();
    img.onload = function() { call(back, env, this, opts, true); };
    img.onerror = function() { call(back, env, this, opts, false); };
    img.src = src;
    return true;
}

/** Cache **/

/// Debug
var CACHE_DBG_FULL   = '!! ';
var CACHE_DBG_ADD    = 'c+ ';
var CACHE_DBG_NO_KEY = 'c! ';
var CACHE_DBG_REMOVE = 'c- ';
var CACHE_DBG_EMPTY  = 'c. ';

/// Class
function cache(name, opts) {

    /** Core **/

    /// Cache infos
    this._name = name;          /// Name
    this._opts = opts || { };   /// Opts
    this._data = { };           /// Data
    this._size = 0;             /// Size

    /// Expiration
    if (!this._opts.expire)
	this._opts.expire = 60; /// Expire

    /** Public **/

    /// Push
    this.push = function(e) {
	var use, size, expire, item, args, cached, flush;
	// Infos
	size = e.size || 0;
	expire = e.expire? e.expire: this._opts.expire;
	// Remove existing?
	if (this._data[e.key])
	    this.remove(e.key);
	// Cache size
	if (this._opts.max)
	    if (e.size > this._opts.max) {
		debug('e', this.n, CACHE_DBG_FULL + size);
		return false;
	    } else this.clear(this._opts.max - size);
	this._size += size;
	// Cache value
	cached = now();
	item = { 'value':  e.value,
		 'cached': cached,
		 'size':   size,
		 'expire': expire };
	this._data[e.key] = item;
	use = (this._opts.max)? round(100 * this._size / this._opts.max) + '%': this._size;
	debug('v', this._name, CACHE_DBG_ADD + e.key + ' ' + use);
	// Autoflush?
	if (p.timers && (this._opts.auto || e.expire)) {
	    args = { 'key':    e.key,
		     'cached': cached };
	    flush = { 'obj':  this,
		      'done': '_flush',
		      'args': args,
		      'ms':   expire * 1000 };
	    p.timers.r(flush);
	}
	// Done.
	return true;
    }

    /// Cached?
    this.cached = function(key) {
	var ok;
	// Not cached?
	if (!this._data[key])
	    return false;
	// Expired?
	if (!this._data[key].expire)
	    return true;
	ok = (this._data[key].cached +
	      this._data[key].expire * 1000 > now());
	if (!ok) this.remove(key);
	return ok;
    }

    /// Value
    this.value = function(key) {
	if (!this._data[key]) {
	    debug('w', this._name, CACHE_DBG_NO_KEY + key);
	    return false;
	}
	return this._data[key].value;
    }

    /// Remove
    this.remove = function(key) {
	return this._flush({ 'key': key }, true);
    }

    /// Clear
    this.clear = function(size) {
	while (this._size > size)
	    this._flush(this._oldest());
	return true;
    }

    /// Empty
    this.empty = function() {
	var key;
	for (key in this._data)
	    if (nf(this._data[key]))
		delete this._data[key];
	this._size = 0;
	debug('i', this._name, CACHE_DBG_EMPTY);
    }

    /** Private **/

    /// Flush
    this._flush = function(args, force) {
	// Already flushed?
	if (!this._data[args.key])
	    return false;
	// Check
	if (force !== true &&
	    this._data[args.key].cached != args.cached)
	    return false;
	// Flush?
	if (force === true || !this._opts.off || !p.off) {
	    this._size -= this._data[args.key].size;
	    delete this._data[args.key];
	    debug('v', this.n, CACHE_DBG_REMOVE + args.key);
	}
	return true;
    }

    /// Oldest
    this._oldest = function() {
	var key;
	var oldest = { 'key': -1, 'cached': now() };
	for (key in this._data)
	    if (nf(this._data[key]) &&
		this._data[i].cached < oldest.cached)
		oldest = { 'key': key, 'cached': this._data[key].cached };
	return oldest;
    }

}

/** Pile **/

/// Debug
var PILE_DBG_EMPTY = '>! ';
var PILE_DBG_SKIP  = '-> ';
var PILE_DBG_DONE  = '>> ';

/// Class
function pile(name) {

    /** Core **/

    /// Pile infos
    this._name   = name; /// Name
    this._values = [ ];  /// Values
    this._length = 0;    /// Length

    /** Public **/

    /// Add
    this.add = function(e) {
	this.remove(e);
	this.push(e);
	return true;
    }

    /// Push
    this.push = function(e) {
	this._values.push(e);
	this._length = this._values.length;
	return true;
    }

    /// Remove
    this.remove = function(e) {
	while (this._values.indexOf(e) != -1)
	    this._skip(this._values.indexOf(e));
	this._length = this._values.length;
	return true;
    }
    
    /// Back
    this.back = function(action) {
	var e;
	var ok = false;
	if (!this._values.length) {
	    debug('v', this._name, PILE_DBG_EMPTY);
	    return false;
	}
	do {
	    e = this._values.pop();
	    ok = action.obj[action.done](e);
	    if (!ok)
		debug('v', this._name, PILE_DBG_SKIP + e);
	} while (!ok && this._values.length);
	if (ok) {
	    this._values.push(e);
	    debug('v', this._name, PILE_DBG_DONE + e);
	} else {
	    if (action.fail) action.obj[action.fail]();
	    debug('v', this._name, PILE_DBG_EMPTY);
	}
	this._length = this._values.length;
	return ok;
    }
    
    /// Empty
    this.empty = function() {
	delete this._values;
	this._values = [ ];
	this._length = 0;
    }

    /** Private **/

    /// Skip
    this._skip = function(id) {
	var i;
	if (id == -1) return false;
	for (i = id; i < this._values.length - 1; i++)
	    this._values[i] = this._values[i + 1];
	this._values.pop();
	return true;
    }
    
}

/** History **/

/// Class
function hist(name) {

    /** Core **/

    /// History infos
    this._name   = name; /// Name
    this._pos    = -1;   /// Position
    this._values = [ ];  /// Values

    /** Public **/

    /// Go
    this.go = function(inc) {
	// Out of bounds?
	if (this._pos + inc < 0 ||
	    this._pos + inc >= this._values.length)
	    return false;
	this._pos += inc;
	return { 'pos':   this._pos,
		 'max':   this._values.length,
		 'value': this._values[this._pos] };
    }

    /// Move
    this.current = function() { return this.go(0); }
    this.back = function() { return this.go(-1); }
    this.forward = function() { return this.go(1); }
    this.first = function() {
	this._pos = 0; return this.current();
    }
    this.last = function() {
	this._pos = this._values.length - 1;
	return this.current();
    }
    
    /// Push
    this.push = function(value) {
	while (this._values.length > this._pos + 1)
	    this._values.pop();
	this.append(value);
    }

    /// Append
    this.append = function(value) {
	this._values.push(value);
	this._pos = this._values.length - 1;
    }

    /// Replace
    this.replace = function(value) {
	this._pos = this._values.length - 1;
	this._values[this._pos] = value;
    }

    /// Empty
    this.empty = function() {
	delete this._values;
	this._values = [ ];
	this._pos = -1;
    }
    
}

/** List **/

/// Debug
var LIST_DBG_BROKEN = 'l! ';

/// Class
function list(name) {

    /** Core **/

    /// List infos
    this._name   = name; /// Name
    this._length = 0;    /// Length
    this._values = [ ];  /// Values

    /** Public **/

    /// Next
    this.next = function() {
	var id;
	for (id = 0; id <= this._length; id++)
	    if (this._values[id] == null)
		break;
	return id;
    }
    
    /// Put
    this.put = function(id, value, force) {
	// Taken?
	if (this._values[id] != null && !force)
	    return false;
	if (this._values[id] == null)
	    this._length++;
	this._values[id] = value;
	return true;
    }

    /// Get
    this.get = function(id) {
	return this._values[id];
    }

    /// Add
    this.add = function(value) {
	var id = this.next();
	return this.put(id, value)? id: false;
    }

    /// Index of
    this.indexOf = function(value) {
	return this._values.indexOf(value);
    }

    /// First
    this.first = function(value) {
	var id;
	for (id = 0; id < this._values.length; id++)
	    if (this._values[i] != null)
		return id;
	return -1;
    }

    /// Remove
    this.remove = function(id) {
	if (this._values[id] == null ||
	    !(delete this._values[id])) {
	    debug('w', this.n, LIST_DBG_BROKEN + id);
	    return false;
	}
	this._length--;
	this.free();
	return true;
    }
    
    /// Empty
    this.empty = function() {
	var id;
	for (id = 0; id < this._values.length; id++)
	    this.remove(id);
	this.free();
	return !this._length;
    }

    /// Exec
    this.exec = function(action) {
	var id, key, r;
	var ok = true;
	for (id = 0; id < this._values.length; id++) {
	    if (this._values[id] == null)
		continue;
	    key = action.key? id: this._values[id];
	    r = action.obj[action.done](key, action.args);
	    ok &= (action.reverse)? !r: r;
	}
	return ok;
    }

    /// Free
    this.free = function() {
	var id;
	for (id = this._values.length - 1; id >= 0; id--)
	    if (this._values[id] == null)
		this._values.pop();
	    else break;
	return true;
    }

}

/** Shortcuts **/

/// Types
function type(i) { return (typeof i); };
function tpo(i) { return type(i) == 'object'; };
function tpf(i) { return type(i) == 'function'; };
function tps(i) { return type(i) == 'string'; };
function tpb(i) { return type(i) == 'boolean'; };
function tpn(i) { return type(i) == 'number'; };
function tpu(i) { return type(i) == 'undefined'; };
function nf(i) { return !tpf(i); };

/// Math
function abs(f) { return tpn(f)? Math.abs(f): 0; }
function round(f) { return tpn(f)? Math.round(f): 0; }
function floor(f) { return tpn(f)? Math.floor(f): 0; }
function ceil(f) { return tpn(f)? Math.ceil(f): 0; }
function sqrt(f) { return tpn(f)? Math.sqrt(f): 0; }
function pow(f, a) { return tpn(f)? Math.pow(f, a): 0; }
function max(a, b) { return Math.max(a, b); }
function min(a, b) { return Math.min(a, b); }
function between(a, b, c) { return min(c, max(a, b)); }

/** Boot **/

var h = dom(d);
var onloads = [ ];
function boot() {
    var mod;
    while (mod = onloads.pop())
	p[mod].init();
}
w.onload = boot;