// VERSION 2.1.9

// PAGESETUP

PAGESETUP.files = [];
PAGESETUP.combined_libs = {
	'yui/animation/animation.js': PAGESETUP.default_libs[0], 
	'yui/animation/animation-min.js': PAGESETUP.default_libs[0],	
	'yui/animation/animation-debug.js': PAGESETUP.default_libs[0],
	'yui/yahoo/yahoo.js': PAGESETUP.default_libs[0], 
	'yui/yahoo/yahoo-min.js': PAGESETUP.default_libs[0],	
	'yui/yahoo/yahoo-debug.js': PAGESETUP.default_libs[0],
	'yui/event/event.js': PAGESETUP.default_libs[0], 
	'yui/event/event-min.js': PAGESETUP.default_libs[0],	
	'yui/event/event-debug.js': PAGESETUP.default_libs[0],
	'yui/dom/dom.js': PAGESETUP.default_libs[0], 
	'yui/dom/dom-min.js': PAGESETUP.default_libs[0],	
	'yui/dom/dom-debug.js': PAGESETUP.default_libs[0],
	'yui/connection/connection.js': PAGESETUP.default_libs[0], 
	'yui/connection/connection-min.js': PAGESETUP.default_libs[0],	
	'yui/connection/connection-debug.js': PAGESETUP.default_libs[0],
	'yui/dragdrop/dragdrop.js': PAGESETUP.default_libs[0], 
	'yui/dragdrop/dragdrop-min.js': PAGESETUP.default_libs[0],	
	'yui/dragdrop/dragdrop-debug.js': PAGESETUP.default_libs[0],
	'yui/element/element.js': PAGESETUP.default_libs[0], 
	'yui/element/element-min.js': PAGESETUP.default_libs[0],	
	'yui/element/element-debug.js': PAGESETUP.default_libs[0], 
	'yui/get/get.js': PAGESETUP.default_libs[0], 
	'yui/get/get-min.js': PAGESETUP.default_libs[0],	
	'yui/get/get-debug.js': PAGESETUP.default_libs[0], 
	'yui/yuiloader/yuiloader.js': PAGESETUP.default_libs[0], 
	'yui/yuiloader/yuiloader-min.js': PAGESETUP.default_libs[0],	
	'yui/yuiloader/yuiloader-debug.js': PAGESETUP.default_libs[0], 
	'yui/selector/selector.js': PAGESETUP.default_libs[0], 
	'yui/selector/selector-min.js': PAGESETUP.default_libs[0],	
	'yui/selector/selector-debug.js': PAGESETUP.default_libs[0],
	'yui/json/json.js': PAGESETUP.default_libs[0], 
	'yui/json/json-min.js': PAGESETUP.default_libs[0],	
	'yui/json/json-debug.js': PAGESETUP.default_libs[0],
	'yui/carousel/carousel.js': PAGESETUP.default_libs[0], 
	'yui/carousel/carousel-min.js': PAGESETUP.default_libs[0],	
	'yui/carousel/carousel-debug.js': PAGESETUP.default_libs[0],
	'yui/tabview/tabview.js': PAGESETUP.default_libs[0], 
	'yui/tabview/tabview-min.js': PAGESETUP.default_libs[0],	
	'yui/tabview/tabview-debug.js': PAGESETUP.default_libs[0],
	'yui/yahoo-dom-event/yahoo-dom-event.js': PAGESETUP.default_libs[0],
	'edmui/edmunds/edmunds.js': PAGESETUP.default_libs[0], 
	'edmui/edmunds/edmunds-min.js': PAGESETUP.default_libs[0], 
	'edmui/flash/flash.js': PAGESETUP.default_libs[0], 
	'edmui/flash/flash-min.js': PAGESETUP.default_libs[0], 
	'edmui/envcheck/envcheck.js': PAGESETUP.default_libs[0], 
	'edmui/envcheck/envcheck-min.js': PAGESETUP.default_libs[0], 
	'edmui/flashplatform/flashplatform.js': PAGESETUP.default_libs[0], 
	'edmui/flashplatform/flashplatform-min.js': PAGESETUP.default_libs[0], 
	'edmui/expander/expander.js': PAGESETUP.default_libs[0], 
	'edmui/expander/expander-min.js': PAGESETUP.default_libs[0], 
	'edmui/drawer/drawer.js': PAGESETUP.default_libs[0], 
	'edmui/drawer/drawer-min.js': PAGESETUP.default_libs[0]
};
PAGESETUP.breakdown_libs = [
	'yui/yahoo/yahoo.js',
    'yui/dom/dom.js', 
    'yui/event/event.js', 
    'yui/connection/connection.js', 
    'yui/animation/animation.js', 
    'yui/element/element.js', 
    'yui/get/get.js', 
    'yui/yuiloader/yuiloader.js', 
	'yui/selector/selector.js', 
	'yui/json/json.js',
	'yui/carousel/carousel.js', 
	'yui/tabview/tabview.js', 
    'edmui/edmunds/edmunds.js',
    'edmui/flash/flash.js', 
	'edmui/envcheck/envcheck.js', 
	'edmui/flashplatform/flashplatform.js', 
	'edmui/expander/expander.js', 
	'edmui/drawer/drawer.js'
];
PAGESETUP.modules = {};
PAGESETUP.queue = {high:[], normal:[], low:[]};
PAGESETUP.scope = {}; 
PAGESETUP.thirdpartyids = [];
PAGESETUP.thirdpartydetails = {}; 
PAGESETUP.addModule = function(mod) {
    if (PAGESETUP.modules[mod]) {
        ++PAGESETUP.modules[mod];
    } else {
        PAGESETUP.modules[mod] = 1;
    }
};

PAGESETUP.execControls = function(start) {
    if (!PAGESETUP.timer.chunk_exec_start) {PAGESETUP.timer.chunk_exec_start = (new Date()).getTime() - PAGESETUP.timer.start;}
    var start = start || 0;
    if (!this.merged) {
        this.merged = PAGESETUP.queue.high;
        this.merged = this.merged.concat(PAGESETUP.queue.normal).concat(PAGESETUP.queue.low);
    } 
    var merged = this.merged;
    setTimeout(function() {
        var item = merged.shift();
        if (merged.length > 0) {
            setTimeout(arguments.callee, 25);
        } else {
            PAGESETUP.timer.chunk_exec_end = (new Date()).getTime() - PAGESETUP.timer.start;
        }
        item.call();
    }, 0);
};

PAGESETUP.addControl = function(fn, priority) {
     switch(priority) {
         case 'high':
            this.queue.high.push(fn);
         break;
         case 'low':
            this.queue.low.push(fn);
         break;
         case 'normal':
         default:
            this.queue.normal.push(fn);
         break;
     }
};

window.onload = function() {
	PAGESETUP.timer.load_evt = (new Date()).getTime() - PAGESETUP.timer.start;
};

/* SiteCatalyst code version: H.20.3.
Copyright 1997-2009 Omniture, Inc. More info available at
http://www.omniture.com */
var s_account=edomniacct || "edmundsiltest";
var s=s_gi(s_account);
/************************** CONFIG SECTION **************************/
/* You may add or alter any code config here. */
s.charSet="UTF-8";
/* Conversion Config */
s.currencyCode="USD";
/* Link Tracking Config */
s.trackDownloadLinks=true;
s.trackExternalLinks=false;
s.trackInlineStats=true;
s.linkDownloadFileTypes="exe,zip,wav,mp3,mov,mpg,avi,wmv,doc,pdf,xls";
s.linkInternalFilters="javascript:,insideline.com,carspace.com,insideline.com";
s.linkLeaveQueryString=false;
s.linkTrackVars="None";
s.linkTrackEvents="None";

/* WARNING: Changing any of the below variables will cause drastic
changes to how your visitor data is collected.  Changes should only be
made when instructed to do so by your account manager.*/
s.dc="112";

/************* DO NOT ALTER ANYTHING BELOW THIS LINE ! **************/
var s_code='',s_objectID;function s_gi(un,pg,ss){var c="s._c='s_c';s.wd=window;if(!s.wd.s_c_in){s.wd.s_c_il=new Array;s.wd.s_c_in=0;}s._il=s.wd.s_c_il;s._in=s.wd.s_c_in;s._il[s._in]=s;s.wd.s_c_in++;s"
+".an=s_an;s.cls=function(x,c){var i,y='';if(!c)c=this.an;for(i=0;i<x.length;i++){n=x.substring(i,i+1);if(c.indexOf(n)>=0)y+=n}return y};s.fl=function(x,l){return x?(''+x).substring(0,l):x};s.co=func"
+"tion(o){if(!o)return o;var n=new Object,x;for(x in o)if(x.indexOf('select')<0&&x.indexOf('filter')<0)n[x]=o[x];return n};s.num=function(x){x=''+x;for(var p=0;p<x.length;p++)if(('0123456789').indexO"
+"f(x.substring(p,p+1))<0)return 0;return 1};s.rep=s_rep;s.sp=s_sp;s.jn=s_jn;s.ape=function(x){var s=this,h='0123456789ABCDEF',i,c=s.charSet,n,l,e,y='';c=c?c.toUpperCase():'';if(x){x=''+x;if(c=='AUTO"
+"'&&('').charCodeAt){for(i=0;i<x.length;i++){c=x.substring(i,i+1);n=x.charCodeAt(i);if(n>127){l=0;e='';while(n||l<4){e=h.substring(n%16,n%16+1)+e;n=(n-n%16)/16;l++}y+='%u'+e}else if(c=='+')y+='%2B';"
+"else y+=escape(c)}x=y}else{x=x?s.rep(escape(''+x),'+','%2B'):x;if(x&&c&&s.em==1&&x.indexOf('%u')<0&&x.indexOf('%U')<0){i=x.indexOf('%');while(i>=0){i++;if(h.substring(8).indexOf(x.substring(i,i+1)."
+"toUpperCase())>=0)return x.substring(0,i)+'u00'+x.substring(i);i=x.indexOf('%',i)}}}}return x};s.epa=function(x){var s=this;return x?unescape(s.rep(''+x,'+',' ')):x};s.pt=function(x,d,f,a){var s=th"
+"is,t=x,z=0,y,r;while(t){y=t.indexOf(d);y=y<0?t.length:y;t=t.substring(0,y);r=s[f](t,a);if(r)return r;z+=y+d.length;t=x.substring(z,x.length);t=z<x.length?t:''}return ''};s.isf=function(t,a){var c=a"
+".indexOf(':');if(c>=0)a=a.substring(0,c);if(t.substring(0,2)=='s_')t=t.substring(2);return (t!=''&&t==a)};s.fsf=function(t,a){var s=this;if(s.pt(a,',','isf',t))s.fsg+=(s.fsg!=''?',':'')+t;return 0}"
+";s.fs=function(x,f){var s=this;s.fsg='';s.pt(x,',','fsf',f);return s.fsg};s.si=function(wd){var s=this,c=''+s_gi,a=c.indexOf(\"{\"),b=c.lastIndexOf(\"}\"),m;c=s_fe(a>0&&b>0?c.substring(a+1,b):0);if"
+"(wd&&wd.document&&c){wd.setTimeout('function s_sv(o,n,k){var v=o[k],i;if(v){if(typeof(v)==\"string\"||typeof(v)==\"number\")n[k]=v;else if (typeof(v)==\"array\"){n[k]=new Array;for(i=0;i<v.length;i"
+"++)s_sv(v,n[k],i)}else if (typeof(v)==\"object\"){n[k]=new Object;for(i in v)s_sv(v,n[k],i)}}}function s_si(t){var wd=window,s,i,j,c,a,b;wd.s_gi=new Function(\"un\",\"pg\",\"ss\",\"'+c+'\");wd.s=s_"
+"gi(\"'+s.oun+'\");s=wd.s;s.sa(\"'+s.un+'\");s.tfs=wd;s.pt(s.vl_g,\",\",\"vo1\",t);s.lnk=s.eo=s.linkName=s.linkType=s.wd.s_objectID=s.ppu=s.pe=s.pev1=s.pev2=s.pev3=\\'\\';if(t.m_l&&t.m_nl)for(i=0;i<"
+"t.m_nl.length;i++){n=t.m_nl[i];if(n){m=t[n];c=t[\"m_\"+n];if(m&&c){c=\"\"+c;if(c.indexOf(\"function\")>=0){a=c.indexOf(\"{\");b=c.lastIndexOf(\"}\");c=a>0&&b>0?c.substring(a+1,b):0;s[\"m_\"+n+\"_c"
+"\"]=c;if(m._e)s.loadModule(n);if(s[n])for(j=0;j<m._l.length;j++)s_sv(m,s[n],m._l[j])}}}}}var e,o,t;try{o=window.opener;if(o&&o.s_gi){t=o.s_gi(\"'+s.un+'\");if(t)s_si(t)}}catch(e){}',1)}};s.c_d='';s"
+".c_gdf=function(t,a){var s=this;if(!s.num(t))return 1;return 0};s.c_gd=function(){var s=this,d=s.wd.location.hostname,n=s.fpCookieDomainPeriods,p;if(!n)n=s.cookieDomainPeriods;if(d&&!s.c_d){n=n?par"
+"seInt(n):2;n=n>2?n:2;p=d.lastIndexOf('.');if(p>=0){while(p>=0&&n>1){p=d.lastIndexOf('.',p-1);n--}s.c_d=p>0&&s.pt(d,'.','c_gdf',0)?d.substring(p):d}}return s.c_d};s.c_r=function(k){var s=this;k=s.ap"
+"e(k);var c=' '+s.d.cookie,i=c.indexOf(' '+k+'='),e=i<0?i:c.indexOf(';',i),v=i<0?'':s.epa(c.substring(i+2+k.length,e<0?c.length:e));return v!='[[B]]'?v:''};s.c_w=function(k,v,e){var s=this,d=s.c_gd("
+"),l=s.cookieLifetime,t;v=''+v;l=l?(''+l).toUpperCase():'';if(e&&l!='SESSION'&&l!='NONE'){t=(v!=''?parseInt(l?l:0):-60);if(t){e=new Date;e.setTime(e.getTime()+(t*1000))}}if(k&&l!='NONE'){s.d.cookie="
+"k+'='+s.ape(v!=''?v:'[[B]]')+'; path=/;'+(e&&l!='SESSION'?' expires='+e.toGMTString()+';':'')+(d?' domain='+d+';':'');return s.c_r(k)==v}return 0};s.eh=function(o,e,r,f){var s=this,b='s_'+e+'_'+s._"
+"in,n=-1,l,i,x;if(!s.ehl)s.ehl=new Array;l=s.ehl;for(i=0;i<l.length&&n<0;i++){if(l[i].o==o&&l[i].e==e)n=i}if(n<0){n=i;l[n]=new Object}x=l[n];x.o=o;x.e=e;f=r?x.b:f;if(r||f){x.b=r?0:o[e];x.o[e]=f}if(x"
+".b){x.o[b]=x.b;return b}return 0};s.cet=function(f,a,t,o,b){var s=this,r,tcf;if(s.apv>=5&&(!s.isopera||s.apv>=7)){tcf=new Function('s','f','a','t','var e,r;try{r=s[f](a)}catch(e){r=s[t](e)}return r"
+"');r=tcf(s,f,a,t)}else{if(s.ismac&&s.u.indexOf('MSIE 4')>=0)r=s[b](a);else{s.eh(s.wd,'onerror',0,o);r=s[f](a);s.eh(s.wd,'onerror',1)}}return r};s.gtfset=function(e){var s=this;return s.tfs};s.gtfso"
+"e=new Function('e','var s=s_c_il['+s._in+'],c;s.eh(window,\"onerror\",1);s.etfs=1;c=s.t();if(c)s.d.write(c);s.etfs=0;return true');s.gtfsfb=function(a){return window};s.gtfsf=function(w){var s=this"
+",p=w.parent,l=w.location;s.tfs=w;if(p&&p.location!=l&&p.location.host==l.host){s.tfs=p;return s.gtfsf(s.tfs)}return s.tfs};s.gtfs=function(){var s=this;if(!s.tfs){s.tfs=s.wd;if(!s.etfs)s.tfs=s.cet("
+"'gtfsf',s.tfs,'gtfset',s.gtfsoe,'gtfsfb')}return s.tfs};s.mrq=function(u){var s=this,l=s.rl[u],n,r;s.rl[u]=0;if(l)for(n=0;n<l.length;n++){r=l[n];s.mr(0,0,r.r,0,r.t,r.u)}};s.br=function(id,rs){var s"
+"=this;if(s.disableBufferedRequests||!s.c_w('s_br',rs))s.brl=rs};s.flushBufferedRequests=function(){this.fbr(0)};s.fbr=function(id){var s=this,br=s.c_r('s_br');if(!br)br=s.brl;if(br){if(!s.disableBu"
+"fferedRequests)s.c_w('s_br','');s.mr(0,0,br)}s.brl=0};s.mr=function(sess,q,rs,id,ta,u){var s=this,dc=s.dc,t1=s.trackingServer,t2=s.trackingServerSecure,tb=s.trackingServerBase,p='.sc',ns=s.visitorN"
+"amespace,un=s.cls(u?u:(ns?ns:s.fun)),r=new Object,l,imn='s_i_'+(un),im,b,e;if(!rs){if(t1){if(t2&&s.ssl)t1=t2}else{if(!tb)tb='2o7.net';if(dc)dc=(''+dc).toLowerCase();else dc='d1';if(tb=='2o7.net'){i"
+"f(dc=='d1')dc='112';else if(dc=='d2')dc='122';p=''}t1=un+'.'+dc+'.'+p+tb}rs='http'+(s.ssl?'s':'')+'://'+t1+'/b/ss/'+s.un+'/'+(s.mobile?'5.1':'1')+'/H.20.3/'+sess+'?AQB=1&ndh=1'+(q?q:'')+'&AQE=1';if"
+"(s.isie&&!s.ismac){if(s.apv>5.5)rs=s.fl(rs,4095);else rs=s.fl(rs,2047)}if(id){s.br(id,rs);return}}if(s.d.images&&s.apv>=3&&(!s.isopera||s.apv>=7)&&(s.ns6<0||s.apv>=6.1)){if(!s.rc)s.rc=new Object;if"
+"(!s.rc[un]){s.rc[un]=1;if(!s.rl)s.rl=new Object;s.rl[un]=new Array;setTimeout('if(window.s_c_il)window.s_c_il['+s._in+'].mrq(\"'+un+'\")',750)}else{l=s.rl[un];if(l){r.t=ta;r.u=un;r.r=rs;l[l.length]"
+"=r;return ''}imn+='_'+s.rc[un];s.rc[un]++}im=s.wd[imn];if(!im)im=s.wd[imn]=new Image;im.s_l=0;im.onload=new Function('e','this.s_l=1;var wd=window,s;if(wd.s_c_il){s=wd.s_c_il['+s._in+'];s.mrq(\"'+u"
+"n+'\");s.nrs--;if(!s.nrs)s.m_m(\"rr\")}');if(!s.nrs){s.nrs=1;s.m_m('rs')}else s.nrs++;im.src=rs;if(rs.indexOf('&pe=')>=0&&(!ta||ta=='_self'||ta=='_top'||(s.wd.name&&ta==s.wd.name))){b=e=new Date;wh"
+"ile(!im.s_l&&e.getTime()-b.getTime()<500)e=new Date}return ''}return '<im'+'g sr'+'c=\"'+rs+'\" width=1 height=1 border=0 alt=\"\">'};s.gg=function(v){var s=this;if(!s.wd['s_'+v])s.wd['s_'+v]='';re"
+"turn s.wd['s_'+v]};s.glf=function(t,a){if(t.substring(0,2)=='s_')t=t.substring(2);var s=this,v=s.gg(t);if(v)s[t]=v};s.gl=function(v){var s=this;if(s.pg)s.pt(v,',','glf',0)};s.rf=function(x){var s=t"
+"his,y,i,j,h,l,a,b='',c='',t;if(x){y=''+x;i=y.indexOf('?');if(i>0){a=y.substring(i+1);y=y.substring(0,i);h=y.toLowerCase();i=0;if(h.substring(0,7)=='http://')i+=7;else if(h.substring(0,8)=='https://"
+"')i+=8;h=h.substring(i);i=h.indexOf(\"/\");if(i>0){h=h.substring(0,i);if(h.indexOf('google')>=0){a=s.sp(a,'&');if(a.length>1){l=',q,ie,start,search_key,word,kw,cd,';for(j=0;j<a.length;j++){t=a[j];i"
+"=t.indexOf('=');if(i>0&&l.indexOf(','+t.substring(0,i)+',')>=0)b+=(b?'&':'')+t;else c+=(c?'&':'')+t}if(b&&c){y+='?'+b+'&'+c;if(''+x!=y)x=y}}}}}}return x};s.hav=function(){var s=this,qs='',fv=s.link"
+"TrackVars,fe=s.linkTrackEvents,mn,i;if(s.pe){mn=s.pe.substring(0,1).toUpperCase()+s.pe.substring(1);if(s[mn]){fv=s[mn].trackVars;fe=s[mn].trackEvents}}fv=fv?fv+','+s.vl_l+','+s.vl_l2:'';for(i=0;i<s"
+".va_t.length;i++){var k=s.va_t[i],v=s[k],b=k.substring(0,4),x=k.substring(4),n=parseInt(x),q=k;if(v&&k!='linkName'&&k!='linkType'){if(s.pe||s.lnk||s.eo){if(fv&&(','+fv+',').indexOf(','+k+',')<0)v='"
+"';if(k=='events'&&fe)v=s.fs(v,fe)}if(v){if(k=='dynamicVariablePrefix')q='D';else if(k=='visitorID')q='vid';else if(k=='pageURL'){q='g';v=s.fl(v,255)}else if(k=='referrer'){q='r';v=s.fl(s.rf(v),255)"
+"}else if(k=='vmk'||k=='visitorMigrationKey')q='vmt';else if(k=='visitorMigrationServer'){q='vmf';if(s.ssl&&s.visitorMigrationServerSecure)v=''}else if(k=='visitorMigrationServerSecure'){q='vmf';if("
+"!s.ssl&&s.visitorMigrationServer)v=''}else if(k=='charSet'){q='ce';if(v.toUpperCase()=='AUTO')v='ISO8859-1';else if(s.em==2)v='UTF-8'}else if(k=='visitorNamespace')q='ns';else if(k=='cookieDomainPe"
+"riods')q='cdp';else if(k=='cookieLifetime')q='cl';else if(k=='variableProvider')q='vvp';else if(k=='currencyCode')q='cc';else if(k=='channel')q='ch';else if(k=='transactionID')q='xact';else if(k=='"
+"campaign')q='v0';else if(k=='resolution')q='s';else if(k=='colorDepth')q='c';else if(k=='javascriptVersion')q='j';else if(k=='javaEnabled')q='v';else if(k=='cookiesEnabled')q='k';else if(k=='browse"
+"rWidth')q='bw';else if(k=='browserHeight')q='bh';else if(k=='connectionType')q='ct';else if(k=='homepage')q='hp';else if(k=='plugins')q='p';else if(s.num(x)){if(b=='prop')q='c'+n;else if(b=='eVar')"
+"q='v'+n;else if(b=='list')q='l'+n;else if(b=='hier'){q='h'+n;v=s.fl(v,255)}}if(v)qs+='&'+q+'='+(k.substring(0,3)!='pev'?s.ape(v):v)}}}return qs};s.ltdf=function(t,h){t=t?t.toLowerCase():'';h=h?h.to"
+"LowerCase():'';var qi=h.indexOf('?');h=qi>=0?h.substring(0,qi):h;if(t&&h.substring(h.length-(t.length+1))=='.'+t)return 1;return 0};s.ltef=function(t,h){t=t?t.toLowerCase():'';h=h?h.toLowerCase():'"
+"';if(t&&h.indexOf(t)>=0)return 1;return 0};s.lt=function(h){var s=this,lft=s.linkDownloadFileTypes,lef=s.linkExternalFilters,lif=s.linkInternalFilters;lif=lif?lif:s.wd.location.hostname;h=h.toLower"
+"Case();if(s.trackDownloadLinks&&lft&&s.pt(lft,',','ltdf',h))return 'd';if(s.trackExternalLinks&&h.substring(0,1)!='#'&&(lef||lif)&&(!lef||s.pt(lef,',','ltef',h))&&(!lif||!s.pt(lif,',','ltef',h)))re"
+"turn 'e';return ''};s.lc=new Function('e','var s=s_c_il['+s._in+'],b=s.eh(this,\"onclick\");s.lnk=s.co(this);s.t();s.lnk=0;if(b)return this[b](e);return true');s.bc=new Function('e','var s=s_c_il['"
+"+s._in+'],f,tcf;if(s.d&&s.d.all&&s.d.all.cppXYctnr)return;s.eo=e.srcElement?e.srcElement:e.target;tcf=new Function(\"s\",\"var e;try{if(s.eo&&(s.eo.tagName||s.eo.parentElement||s.eo.parentNode))s.t"
+"()}catch(e){}\");tcf(s);s.eo=0');s.oh=function(o){var s=this,l=s.wd.location,h=o.href?o.href:'',i,j,k,p;i=h.indexOf(':');j=h.indexOf('?');k=h.indexOf('/');if(h&&(i<0||(j>=0&&i>j)||(k>=0&&i>k))){p=o"
+".protocol&&o.protocol.length>1?o.protocol:(l.protocol?l.protocol:'');i=l.pathname.lastIndexOf('/');h=(p?p+'//':'')+(o.host?o.host:(l.host?l.host:''))+(h.substring(0,1)!='/'?l.pathname.substring(0,i"
+"<0?0:i)+'/':'')+h}return h};s.ot=function(o){var t=o.tagName;t=t&&t.toUpperCase?t.toUpperCase():'';if(t=='SHAPE')t='';if(t){if(t=='INPUT'&&o.type&&o.type.toUpperCase)t=o.type.toUpperCase();else if("
+"!t&&o.href)t='A';}return t};s.oid=function(o){var s=this,t=s.ot(o),p,c,n='',x=0;if(t&&!o.s_oid){p=o.protocol;c=o.onclick;if(o.href&&(t=='A'||t=='AREA')&&(!c||!p||p.toLowerCase().indexOf('javascript"
+"')<0))n=s.oh(o);else if(c){n=s.rep(s.rep(s.rep(s.rep(''+c,\"\\r\",''),\"\\n\",''),\"\\t\",''),' ','');x=2}else if(o.value&&(t=='INPUT'||t=='SUBMIT')){n=o.value;x=3}else if(o.src&&t=='IMAGE')n=o.src"
+";if(n){o.s_oid=s.fl(n,100);o.s_oidt=x}}return o.s_oid};s.rqf=function(t,un){var s=this,e=t.indexOf('='),u=e>=0?','+t.substring(0,e)+',':'';return u&&u.indexOf(','+un+',')>=0?s.epa(t.substring(e+1))"
+":''};s.rq=function(un){var s=this,c=un.indexOf(','),v=s.c_r('s_sq'),q='';if(c<0)return s.pt(v,'&','rqf',un);return s.pt(un,',','rq',0)};s.sqp=function(t,a){var s=this,e=t.indexOf('='),q=e<0?'':s.ep"
+"a(t.substring(e+1));s.sqq[q]='';if(e>=0)s.pt(t.substring(0,e),',','sqs',q);return 0};s.sqs=function(un,q){var s=this;s.squ[un]=q;return 0};s.sq=function(q){var s=this,k='s_sq',v=s.c_r(k),x,c=0;s.sq"
+"q=new Object;s.squ=new Object;s.sqq[q]='';s.pt(v,'&','sqp',0);s.pt(s.un,',','sqs',q);v='';for(x in s.squ)if(x&&(!Object||!Object.prototype||!Object.prototype[x]))s.sqq[s.squ[x]]+=(s.sqq[s.squ[x]]?'"
+",':'')+x;for(x in s.sqq)if(x&&(!Object||!Object.prototype||!Object.prototype[x])&&s.sqq[x]&&(x==q||c<2)){v+=(v?'&':'')+s.sqq[x]+'='+s.ape(x);c++}return s.c_w(k,v,0)};s.wdl=new Function('e','var s=s"
+"_c_il['+s._in+'],r=true,b=s.eh(s.wd,\"onload\"),i,o,oc;if(b)r=this[b](e);for(i=0;i<s.d.links.length;i++){o=s.d.links[i];oc=o.onclick?\"\"+o.onclick:\"\";if((oc.indexOf(\"s_gs(\")<0||oc.indexOf(\".s"
+"_oc(\")>=0)&&oc.indexOf(\".tl(\")<0)s.eh(o,\"onclick\",0,s.lc);}return r');s.wds=function(){var s=this;if(s.apv>3&&(!s.isie||!s.ismac||s.apv>=5)){if(s.b&&s.b.attachEvent)s.b.attachEvent('onclick',s"
+".bc);else if(s.b&&s.b.addEventListener)s.b.addEventListener('click',s.bc,false);else s.eh(s.wd,'onload',0,s.wdl)}};s.vs=function(x){var s=this,v=s.visitorSampling,g=s.visitorSamplingGroup,k='s_vsn_"
+"'+s.un+(g?'_'+g:''),n=s.c_r(k),e=new Date,y=e.getYear();e.setYear(y+10+(y<1900?1900:0));if(v){v*=100;if(!n){if(!s.c_w(k,x,e))return 0;n=x}if(n%10000>v)return 0}return 1};s.dyasmf=function(t,m){if(t"
+"&&m&&m.indexOf(t)>=0)return 1;return 0};s.dyasf=function(t,m){var s=this,i=t?t.indexOf('='):-1,n,x;if(i>=0&&m){var n=t.substring(0,i),x=t.substring(i+1);if(s.pt(x,',','dyasmf',m))return n}return 0}"
+";s.uns=function(){var s=this,x=s.dynamicAccountSelection,l=s.dynamicAccountList,m=s.dynamicAccountMatch,n,i;s.un=s.un.toLowerCase();if(x&&l){if(!m)m=s.wd.location.host;if(!m.toLowerCase)m=''+m;l=l."
+"toLowerCase();m=m.toLowerCase();n=s.pt(l,';','dyasf',m);if(n)s.un=n}i=s.un.indexOf(',');s.fun=i<0?s.un:s.un.substring(0,i)};s.sa=function(un){var s=this;s.un=un;if(!s.oun)s.oun=un;else if((','+s.ou"
+"n+',').indexOf(','+un+',')<0)s.oun+=','+un;s.uns()};s.m_i=function(n,a){var s=this,m,f=n.substring(0,1),r,l,i;if(!s.m_l)s.m_l=new Object;if(!s.m_nl)s.m_nl=new Array;m=s.m_l[n];if(!a&&m&&m._e&&!m._i"
+")s.m_a(n);if(!m){m=new Object,m._c='s_m';m._in=s.wd.s_c_in;m._il=s._il;m._il[m._in]=m;s.wd.s_c_in++;m.s=s;m._n=n;m._l=new Array('_c','_in','_il','_i','_e','_d','_dl','s','n','_r','_g','_g1','_t','_"
+"t1','_x','_x1','_rs','_rr','_l');s.m_l[n]=m;s.m_nl[s.m_nl.length]=n}else if(m._r&&!m._m){r=m._r;r._m=m;l=m._l;for(i=0;i<l.length;i++)if(m[l[i]])r[l[i]]=m[l[i]];r._il[r._in]=r;m=s.m_l[n]=r}if(f==f.t"
+"oUpperCase())s[n]=m;return m};s.m_a=new Function('n','g','e','if(!g)g=\"m_\"+n;var s=s_c_il['+s._in+'],c=s[g+\"_c\"],m,x,f=0;if(!c)c=s.wd[\"s_\"+g+\"_c\"];if(c&&s_d)s[g]=new Function(\"s\",s_ft(s_d"
+"(c)));x=s[g];if(!x)x=s.wd[\\'s_\\'+g];if(!x)x=s.wd[g];m=s.m_i(n,1);if(x&&(!m._i||g!=\"m_\"+n)){m._i=f=1;if((\"\"+x).indexOf(\"function\")>=0)x(s);else s.m_m(\"x\",n,x,e)}m=s.m_i(n,1);if(m._dl)m._dl"
+"=m._d=0;s.dlt();return f');s.m_m=function(t,n,d,e){t='_'+t;var s=this,i,x,m,f='_'+t,r=0,u;if(s.m_l&&s.m_nl)for(i=0;i<s.m_nl.length;i++){x=s.m_nl[i];if(!n||x==n){m=s.m_i(x);u=m[t];if(u){if((''+u).in"
+"dexOf('function')>=0){if(d&&e)u=m[t](d,e);else if(d)u=m[t](d);else u=m[t]()}}if(u)r=1;u=m[t+1];if(u&&!m[f]){if((''+u).indexOf('function')>=0){if(d&&e)u=m[t+1](d,e);else if(d)u=m[t+1](d);else u=m[t+"
+"1]()}}m[f]=1;if(u)r=1}}return r};s.m_ll=function(){var s=this,g=s.m_dl,i,o;if(g)for(i=0;i<g.length;i++){o=g[i];if(o)s.loadModule(o.n,o.u,o.d,o.l,o.e,1);g[i]=0}};s.loadModule=function(n,u,d,l,e,ln){"
+"var s=this,m=0,i,g,o=0,f1,f2,c=s.h?s.h:s.b,b,tcf;if(n){i=n.indexOf(':');if(i>=0){g=n.substring(i+1);n=n.substring(0,i)}else g=\"m_\"+n;m=s.m_i(n)}if((l||(n&&!s.m_a(n,g)))&&u&&s.d&&c&&s.d.createElem"
+"ent){if(d){m._d=1;m._dl=1}if(ln){if(s.ssl)u=s.rep(u,'http:','https:');i='s_s:'+s._in+':'+n+':'+g;b='var s=s_c_il['+s._in+'],o=s.d.getElementById(\"'+i+'\");if(s&&o){if(!o.l&&s.wd.'+g+'){o.l=1;if(o."
+"i)clearTimeout(o.i);o.i=0;s.m_a(\"'+n+'\",\"'+g+'\"'+(e?',\"'+e+'\"':'')+')}';f2=b+'o.c++;if(!s.maxDelay)s.maxDelay=250;if(!o.l&&o.c<(s.maxDelay*2)/100)o.i=setTimeout(o.f2,100)}';f1=new Function('e"
+"',b+'}');tcf=new Function('s','c','i','u','f1','f2','var e,o=0;try{o=s.d.createElement(\"script\");if(o){o.type=\"text/javascript\";'+(n?'o.id=i;o.defer=true;o.onload=o.onreadystatechange=f1;o.f2=f"
+"2;o.l=0;':'')+'o.src=u;c.appendChild(o);'+(n?'o.c=0;o.i=setTimeout(f2,100)':'')+'}}catch(e){o=0}return o');o=tcf(s,c,i,u,f1,f2)}else{o=new Object;o.n=n+':'+g;o.u=u;o.d=d;o.l=l;o.e=e;g=s.m_dl;if(!g)"
+"g=s.m_dl=new Array;i=0;while(i<g.length&&g[i])i++;g[i]=o}}else if(n){m=s.m_i(n);m._e=1}return m};s.vo1=function(t,a){if(a[t]||a['!'+t])this[t]=a[t]};s.vo2=function(t,a){if(!a[t]){a[t]=this[t];if(!a"
+"[t])a['!'+t]=1}};s.dlt=new Function('var s=s_c_il['+s._in+'],d=new Date,i,vo,f=0;if(s.dll)for(i=0;i<s.dll.length;i++){vo=s.dll[i];if(vo){if(!s.m_m(\"d\")||d.getTime()-vo._t>=s.maxDelay){s.dll[i]=0;"
+"s.t(vo)}else f=1}}if(s.dli)clearTimeout(s.dli);s.dli=0;if(f){if(!s.dli)s.dli=setTimeout(s.dlt,s.maxDelay)}else s.dll=0');s.dl=function(vo){var s=this,d=new Date;if(!vo)vo=new Object;s.pt(s.vl_g,','"
+",'vo2',vo);vo._t=d.getTime();if(!s.dll)s.dll=new Array;s.dll[s.dll.length]=vo;if(!s.maxDelay)s.maxDelay=250;s.dlt()};s.t=function(vo,id){var s=this,trk=1,tm=new Date,sed=Math&&Math.random?Math.floo"
+"r(Math.random()*10000000000000):tm.getTime(),sess='s'+Math.floor(tm.getTime()/10800000)%10+sed,y=tm.getYear(),vt=tm.getDate()+'/'+tm.getMonth()+'/'+(y<1900?y+1900:y)+' '+tm.getHours()+':'+tm.getMin"
+"utes()+':'+tm.getSeconds()+' '+tm.getDay()+' '+tm.getTimezoneOffset(),tcf,tfs=s.gtfs(),ta='',q='',qs='',code='',vb=new Object;s.gl(s.vl_g);s.uns();s.m_ll();if(!s.td){var tl=tfs.location,a,o,i,x='',"
+"c='',v='',p='',bw='',bh='',j='1.0',k=s.c_w('s_cc','true',0)?'Y':'N',hp='',ct='',pn=0,ps;if(String&&String.prototype){j='1.1';if(j.match){j='1.2';if(tm.setUTCDate){j='1.3';if(s.isie&&s.ismac&&s.apv>"
+"=5)j='1.4';if(pn.toPrecision){j='1.5';a=new Array;if(a.forEach){j='1.6';i=0;o=new Object;tcf=new Function('o','var e,i=0;try{i=new Iterator(o)}catch(e){}return i');i=tcf(o);if(i&&i.next)j='1.7'}}}}"
+"}if(s.apv>=4)x=screen.width+'x'+screen.height;if(s.isns||s.isopera){if(s.apv>=3){v=s.n.javaEnabled()?'Y':'N';if(s.apv>=4){c=screen.pixelDepth;bw=s.wd.innerWidth;bh=s.wd.innerHeight}}s.pl=s.n.plugin"
+"s}else if(s.isie){if(s.apv>=4){v=s.n.javaEnabled()?'Y':'N';c=screen.colorDepth;if(s.apv>=5){bw=s.d.documentElement.offsetWidth;bh=s.d.documentElement.offsetHeight;if(!s.ismac&&s.b){tcf=new Function"
+"('s','tl','var e,hp=0;try{s.b.addBehavior(\"#default#homePage\");hp=s.b.isHomePage(tl)?\"Y\":\"N\"}catch(e){}return hp');hp=tcf(s,tl);tcf=new Function('s','var e,ct=0;try{s.b.addBehavior(\"#default"
+"#clientCaps\");ct=s.b.connectionType}catch(e){}return ct');ct=tcf(s)}}}else r=''}if(s.pl)while(pn<s.pl.length&&pn<30){ps=s.fl(s.pl[pn].name,100)+';';if(p.indexOf(ps)<0)p+=ps;pn++}s.resolution=x;s.c"
+"olorDepth=c;s.javascriptVersion=j;s.javaEnabled=v;s.cookiesEnabled=k;s.browserWidth=bw;s.browserHeight=bh;s.connectionType=ct;s.homepage=hp;s.plugins=p;s.td=1}if(vo){s.pt(s.vl_g,',','vo2',vb);s.pt("
+"s.vl_g,',','vo1',vo)}if(s.usePlugins)s.doPlugins(s);var l=s.wd.location,r=tfs.document.referrer;if(!s.pageURL)s.pageURL=l.href?l.href:l;if(!s.referrer&&!s._1_referrer){s.referrer=r;s._1_referrer=1}"
+"if((vo&&vo._t)||!s.m_m('d')){s.m_m('g');if(s.lnk||s.eo){var o=s.eo?s.eo:s.lnk;if(!o)return '';var p=s.pageName,w=1,t=s.ot(o),n=s.oid(o),x=o.s_oidt,h,l,i,oc;if(s.eo&&o==s.eo){while(o&&!n&&t!='BODY')"
+"{o=o.parentElement?o.parentElement:o.parentNode;if(!o)return '';t=s.ot(o);n=s.oid(o);x=o.s_oidt}oc=o.onclick?''+o.onclick:'';if((oc.indexOf(\"s_gs(\")>=0&&oc.indexOf(\".s_oc(\")<0)||oc.indexOf(\".t"
+"l(\")>=0)return ''}ta=n?o.target:1;h=s.oh(o);i=h.indexOf('?');h=s.linkLeaveQueryString||i<0?h:h.substring(0,i);l=s.linkName;t=s.linkType?s.linkType.toLowerCase():s.lt(h);if(t&&(h||l))q+='&pe=lnk_'+"
+"(t=='d'||t=='e'?s.ape(t):'o')+(h?'&pev1='+s.ape(h):'')+(l?'&pev2='+s.ape(l):'');else trk=0;if(s.trackInlineStats){if(!p){p=s.pageURL;w=0}t=s.ot(o);i=o.sourceIndex;if(s.gg('objectID')){n=s.gg('objec"
+"tID');x=1;i=1}if(p&&n&&t)qs='&pid='+s.ape(s.fl(p,255))+(w?'&pidt='+w:'')+'&oid='+s.ape(s.fl(n,100))+(x?'&oidt='+x:'')+'&ot='+s.ape(t)+(i?'&oi='+i:'')}}if(!trk&&!qs)return '';s.sampled=s.vs(sed);if("
+"trk){if(s.sampled)code=s.mr(sess,(vt?'&t='+s.ape(vt):'')+s.hav()+q+(qs?qs:s.rq(s.un)),0,id,ta);qs='';s.m_m('t');if(s.p_r)s.p_r();s.referrer=''}s.sq(qs);}else{s.dl(vo);}if(vo)s.pt(s.vl_g,',','vo1',v"
+"b);s.lnk=s.eo=s.linkName=s.linkType=s.wd.s_objectID=s.ppu=s.pe=s.pev1=s.pev2=s.pev3='';if(s.pg)s.wd.s_lnk=s.wd.s_eo=s.wd.s_linkName=s.wd.s_linkType='';if(!id&&!s.tc){s.tc=1;s.flushBufferedRequests("
+")}return code};s.tl=function(o,t,n,vo){var s=this;s.lnk=s.co(o);s.linkType=t;s.linkName=n;s.t(vo)};if(pg){s.wd.s_co=function(o){var s=s_gi(\"_\",1,1);return s.co(o)};s.wd.s_gs=function(un){var s=s_"
+"gi(un,1,1);return s.t()};s.wd.s_dc=function(un){var s=s_gi(un,1);return s.t()}}s.ssl=(s.wd.location.protocol.toLowerCase().indexOf('https')>=0);s.d=document;s.b=s.d.body;if(s.d.getElementsByTagName"
+"){s.h=s.d.getElementsByTagName('HEAD');if(s.h)s.h=s.h[0]}s.n=navigator;s.u=s.n.userAgent;s.ns6=s.u.indexOf('Netscape6/');var apn=s.n.appName,v=s.n.appVersion,ie=v.indexOf('MSIE '),o=s.u.indexOf('Op"
+"era '),i;if(v.indexOf('Opera')>=0||o>0)apn='Opera';s.isie=(apn=='Microsoft Internet Explorer');s.isns=(apn=='Netscape');s.isopera=(apn=='Opera');s.ismac=(s.u.indexOf('Mac')>=0);if(o>0)s.apv=parseFl"
+"oat(s.u.substring(o+6));else if(ie>0){s.apv=parseInt(i=v.substring(ie+5));if(s.apv>3)s.apv=parseFloat(i)}else if(s.ns6>0)s.apv=parseFloat(s.u.substring(s.ns6+10));else s.apv=parseFloat(v);s.em=0;if"
+"(String.fromCharCode){i=escape(String.fromCharCode(256)).toUpperCase();s.em=(i=='%C4%80'?2:(i=='%U0100'?1:0))}s.sa(un);s.vl_l='dynamicVariablePrefix,visitorID,vmk,visitorMigrationKey,visitorMigrati"
+"onServer,visitorMigrationServerSecure,ppu,charSet,visitorNamespace,cookieDomainPeriods,cookieLifetime,pageName,pageURL,referrer,currencyCode';s.va_l=s.sp(s.vl_l,',');s.vl_t=s.vl_l+',variableProvide"
+"r,channel,server,pageType,transactionID,purchaseID,campaign,state,zip,events,products,linkName,linkType';for(var n=1;n<51;n++)s.vl_t+=',prop'+n+',eVar'+n+',hier'+n+',list'+n;s.vl_l2=',tnt,pe,pev1,p"
+"ev2,pev3,resolution,colorDepth,javascriptVersion,javaEnabled,cookiesEnabled,browserWidth,browserHeight,connectionType,homepage,plugins';s.vl_t+=s.vl_l2;s.va_t=s.sp(s.vl_t,',');s.vl_g=s.vl_t+',track"
+"ingServer,trackingServerSecure,trackingServerBase,fpCookieDomainPeriods,disableBufferedRequests,mobile,visitorSampling,visitorSamplingGroup,dynamicAccountSelection,dynamicAccountList,dynamicAccount"
+"Match,trackDownloadLinks,trackExternalLinks,trackInlineStats,linkLeaveQueryString,linkDownloadFileTypes,linkExternalFilters,linkInternalFilters,linkTrackVars,linkTrackEvents,linkNames,lnk,eo,_1_ref"
+"errer';s.va_g=s.sp(s.vl_g,',');s.pg=pg;s.gl(s.vl_g);if(!ss)s.wds()",w=window,l=w.s_c_il,n=navigator,u=n.userAgent,v=n.appVersion,e=v.indexOf('MSIE '),m=u.indexOf('Netscape6/'),a,i,s;if(un){un=un.toLowerCase();if(l)for(i=0;i<l.length;i++){s=l[i];if(!s._c||s._c=='s_c'){if(s.oun==un)return s;else if(s.fs&&s.sa&&s.fs(s.oun,un)){s.sa(un);return s}}}}w.s_an='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
w.s_sp=new Function("x","d","var a=new Array,i=0,j;if(x){if(x.split)a=x.split(d);else if(!d)for(i=0;i<x.length;i++)a[a.length]=x.substring(i,i+1);else while(i>=0){j=x.indexOf(d,i);a[a.length]=x.subst"
+"ring(i,j<0?x.length:j);i=j;if(i>=0)i+=d.length}}return a");w.s_jn=new Function("a","d","var x='',i,j=a.length;if(a&&j>0){x=a[0];if(j>1){if(a.join)x=a.join(d);else for(i=1;i<j;i++)x+=d+a[i]}}return x");
w.s_rep=new Function("x","o","n","return s_jn(s_sp(x,o),n)");w.s_d=new Function("x","var t='`^@$#',l=s_an,l2=new Object,x2,d,b=0,k,i=x.lastIndexOf('~~'),j,v,w;if(i>0){d=x.substring(0,i);x=x.substring(i+2);l=s_sp(l,'');for(i=0;i<62;i++)l2[l[i]]=i;t=s_sp(t,'');d"
+"=s_sp(d,'~');i=0;while(i<5){v=0;if(x.indexOf(t[i])>=0) {x2=s_sp(x,t[i]);for(j=1;j<x2.length;j++){k=x2[j].substring(0,1);w=t[i]+k;if(k!=' '){v=1;w=d[b+l2[k]]}x2[j]=w+x2[j].substring(1)}}if(v)x=s_jn("
+"x2,'');else{w=t[i]+' ';if(x.indexOf(w)>=0)x=s_rep(x,w,t[i]);i++;b+=62}}}return x");w.s_fe=new Function("c","return s_rep(s_rep(s_rep(c,'\\\\','\\\\\\\\'),'\"','\\\\\"'),\"\\n\",\"\\\\n\")");w.s_fa=new Function("f","var s=f.indexOf('(')+1,e=f.indexOf(')'),a='',c;while(s>=0&&s<e){c=f.substring(s,s+1);if(c==',')a+='\",\"';else if((\"\\n\\r\\t \").indexOf(c)<0)a+=c;s++}return a?'\"'+a+'\"':"
+"a");w.s_ft=new Function("c","c+='';var s,e,o,a,d,q,f,h,x;s=c.indexOf('=function(');while(s>=0){s++;d=1;q='';x=0;f=c.substring(s);a=s_fa(f);e=o=c.indexOf('{',s);e++;while(d>0){h=c.substring(e,e+1);if(q){i"
+"f(h==q&&!x)q='';if(h=='\\\\')x=x?0:1;else x=0}else{if(h=='\"'||h==\"'\")q=h;if(h=='{')d++;if(h=='}')d--}if(d>0)e++}c=c.substring(0,s)+'new Function('+(a?a+',':'')+'\"'+s_fe(c.substring(o+1,e))+'\")"
+"'+c.substring(e+1);s=c.indexOf('=function(')}return c;");c=s_d(c);if(e>0){a=parseInt(i=v.substring(e+5));if(a>3)a=parseFloat(i)}else if(m>0)a=parseFloat(u.substring(m+10));else a=parseFloat(v);if(a>=5&&v.indexOf('Opera')<0&&u.indexOf('Opera')<0){w.s_c=new Function("un","pg","ss","var s=this;"+c);return new s_c(un,pg,ss)}else s=new Function("un","pg","ss","var s=new Object;"+s_ft(c)+";return s");return s(un,pg,ss)};

// ETRACK 2.0.9
/**
 * 	@class A class that collects tracking information on any given page and sends that info in the query string of a given URI 
 *	(i.e. 1x1 pixel, empty script with 204 status, ...etc.) It also provides an automatic way of processing TID values attached 
 *	to DOM elements. In a nutshell, ETRACKBASE allows you to do the following:
 *
 *	- Instantiate multiple instances of the class to implement different tracking contexts on a page.
 *	- Define a tid attribute in the DOM element you want to track.
 *	- Define optional pre-track and post-track JavaScript functions to be run before and after the tracking URI is fired respectively.
 *	- Process static links with tid attribute and handle them using an implementation-specific function
 *
 *	@example
 * 	Example 1:
 * 	<a href="http://redesign/edmui/2.0.x/resources/examples/edw/index.html" target="_blank">Sending EDW data through 1x1 pixel</a>
 *
 *	Example 2:
 * 	<a href="http://redesign/edmui/2.0.x/resources/examples/edw/index204.html" target="_blank">Sending EDW data through script that returns 204 status</a>
 *
 *	Example 3:
 * 	<a href="http://redesign/edmui/2.0.x/resources/examples/edw/indexall.html" target="_blank">Sending EDW data using both 1x1 and 204 script</a>
 *
 *	@constructor Instantiates a new tracking object that collects environment data (i.e. broswer info) automatically
 *	and recieves key/value pairs from the controller logic through publically available method set().
 *
 *	@param	string	track_url	The transport mechanism through which data is sent to the server. Default value is http://edw.edmunds.com/media/edw/edw1x1.gif
 *	@return	object	An ETRACKBASE instance
 *
 * 	@author Ismail Elshareef
 * 	@version 2.0.9
 */
ETRACKBASE = window.ETRACKBASE || function(track_url, bug) {
		
	/**
	 *	@property	Class version
	 */
	var VERSION = '2.0.9';
	
	/**
	 *	@property	Base tracking URI
	 */
	var _track_url = track_url || 'http://edw.edmunds.com/media/edw/edw1x1.gif';
		
	/**
	 *	@property	Last fired tracking URI
	 */
	var tracking_uri = '';
	
	/**
	 *	@property	<img> tag in DOM
	 */
	var bugInDom = (bug) ? document.getElementById(bug) : '';
	
    /**
     *	Resturn a string with all plugins available in the browser
     *
	 *	@param	void
	 *	@return string	List of plugins separated by a |
	 *	@private
	 */
	function _getSerlizedPlugins() {
		// Get an array of browser plugins
      	var plugins = navigator.plugins;
	    var cnt = plugins.length;
		// For IE7, initialize variable as an array, otherwise a string. This is a performance measure
	    var str = (navigator.userAgent.indexOf('MSIE') !== -1 && navigator.userAgent.indexOf('7') !== -1) ? [] : '';
	    // Loops through the array of plugins
		for (var i = 0; i < cnt; i++) {
			// If the plugin has a name property
	        if (plugins[i].name) {
				// Add it to str
	            if (typeof str == 'string') {
	                str += plugins[i].name+'|';
	            } else {
	                str.push(plugins[i].name+'|');
	            }
	        }
	    }
		// Return the list in a string format
	    return (typeof str == 'string') ? str : str.join('');
 	}

	/**
     * 	@property 	object	Information about the user's browser settings and state. This information is evaluated automatically
	 *	for every ETRACKBASE instance and is always sent out in the final tracking URI that is fired by the browser.
   	 *	@private
     */
	var _environment = {
		edwref: document.referrer, 
		edwurl: window.location.href,
		title: document.title, 
		querystring: function() {var q = document.location.search.replace('?', ''); q=q.replace('&', ';'); q=q.replace('%26', ';'); return q;}(),
		ua: navigator.userAgent, 
		edwscrres: screen.width+'X'+screen.height, 
		edwwinoffset: window.screenX+'X'+window.screenY,
		hascookie: navigator.cookieEnabled,
		hasjava: navigator.javaEnabled(),
		plugins: _getSerlizedPlugins()
	};
	
	/**
     * 	@property	object	User defined information that is added for every ETRACKBASE instance.
   	 *	@private
     */
	var _params = {
		edwtimestamp: new Date().getTime()
	};
	
	/**
     *	Introduce a delay in milliseconds
     *
	 *	@param int	mseconds	Milliseconds by which to delay execution
	 *	@return void
	 *	@private
	 */
	function _delay(mseconds) {
	   var currentTime = new Date();
	   var endTime = currentTime.getTime() + mseconds;
	   while (currentTime.getTime() < endTime) {
	      currentTime = new Date();
	   }
	}
	
	/**
     * 	@property	object	Placeholders for preTrack and postTrack functions that can be 
	 *	optionally defined per implementation and have track() execute them before and after
	 *	the tracking URI is fired respectively
   	 *	@private
     */
	var aux = {
		/**
	     *	To be called right before the tracking URI is fired. Can be defined by user using
		 *	[ETRACKBASE instance name].setFn('preTrack', function() {.....});
	     *
		 *	@param void
		 *	@return void
		 *	@private
		 */
		preTrack: function() {}, 
		/**
	     *	To be called right after the tracking URI is fired. Can be defined by user using
		 *	[ETRACKBASE instance name].setFn('postTrack', function() {.....});
	     *
		 *	@param void
		 *	@return void
		 *	@private
		 */
		postTrack: function() {}
	};
	
	/**
     *	Fire off a tracking URI with an optional event and event data appended to its query string.
     *
	 *	@param	string	event	The event title
	 *	@param 	object	obj		The event data
	 *	@param	int		delay	How many milliseconds to delay execution by? Default is 10
	 *	@return string	The tracking URI fired
	 *	@private
	 */
    function _track(event, obj, delay) {
		// Fire preTrack funcion.
		aux.preTrack();
		// Initialize local variables
	    var evtdata = '', edwdata = '', envdata = '';
		// Process event data object if available
	    if (obj) {
	        if (typeof obj != 'object') {
	            evtdata = escape(obj);
	        } else {
	        	for (var i in obj) {
					if (obj.hasOwnProperty(i) && typeof i == 'string') {
						evtdata += i+'|'+escape(obj[i])+';';
					}
		        }
			}
			// Append an ampersand at the end
			evtdata += '&';
	    } else { // If no event data passed, put NA& in its place
			evtdata = 'NA&';
		}
		
		// Process user-defined parameters
		for (var q in _params) {
			// If the value is anything but null, process it
			if (_params.hasOwnProperty(q) && typeof q == 'string' && _params[q] !== null) {
				edwdata += q+'='+escape(_params[q])+'&';
			}
		}
		
		// Process environment data
		for (var t in _environment) {
			if (_environment.hasOwnProperty(t) && typeof t == 'string' && _environment[t] !== null) {
				envdata += t+'='+escape(_environment[t])+'&';
			}
		}
		
		// Prep to fire the tracking URI
		// If there's an event being passed
		if (event) {
			tracking_uri = _track_url+'?'+'eventtype='+escape(event)+'&eventdata='+evtdata+edwdata+envdata+'ts='+ new Date().getTime();
	    } else { // Otherwise
			tracking_uri = _track_url+'?'+edwdata+envdata+'ts='+ new Date().getTime();
		}
		
		// Initial check for bugInDom variable
		if (bugInDom === '') {
			bugInDom = document.getElementById('etrack-bug');
		}
		
		// If the img DOM element exists, update its SRC attribute
		if (bugInDom) {
			bugInDom.src = tracking_uri;
		} else { // Otherwise, create a JS img object and assign its SRC and wait before deleting it
			// Fire the URI
			var bug = document.createElement('img');
			bug.src = tracking_uri;
			// Introduce delay to make sure the URI is fired properly, especially in cases when the browser is 
			// ready to navigate to another page
			_delay(delay || 10);
			// GC the image object
			bug = null; // gc
		}
		
		// Execute the postTrack function
		aux.postTrack();
		// Return the tracking URI string
		return tracking_uri;
	}
	/**
     *	Set a parameter to be sent in the tracking URI when it fires.
     *
	 *	@param string	key The parameter
	 *	@param string	The parameter value
	 *	@return string	The parameter value
	 *	@private
	 */
	function _set(key, value) {
		_params[key] = value;
		return _params[key];
	}
	/**
     *	Set a parameter to be sent in the tracking URI when it fires.
     *
	 *	@param	void
	 *	@return	object	Key/Value pairs of the data in this pixel
	 *	@private
	 */
	function _copy() {
		var obj = {env: {}, params: {}};
		for(var key in _environment) {
			if (_environment.hasOwnProperty(key)) {
				obj.env[key] = _environment[key];
			}
		}
		for(var key in _params) {
			if (_params.hasOwnProperty(key)) {
				obj.params[key] = _params[key];
			}
		}
		return obj;
	}
	/**
     *	Copy the passed object into the pixel
     *
	 *	@param	object	{env: object, params: object}
	 *	@return	void
	 *	@private
	 */
	function _clone(obj) {
		if (obj && obj.env && obj.params) {
			for(var key in obj.env) {
				if (obj.env.hasOwnProperty(key)) {
					_environment[key] = obj.env[key];
				}
			}
			for(var key in obj.params) {
				if (obj.params.hasOwnProperty(key)) {
					_params[key] = obj.params[key];
				}
			}
		}
	}
	/**
     *	Set the preTrack and postTrack functions to be run before and after the tracking URI is fired respectively
     *
	 *	@param string	key The parameter
	 *	@param string	The parameter value
	 *	@return bool	TRUE if setting was done. Otherwise, FALSE
	 *	@private
	 */
	function _setFn(key, value) {
		// Make sure the function exists and the new value is a function
		if (aux[key] && typeof value == 'function') {
			aux[key] = value;
			return true;
		}
		return false;
	}
	/**
     *	Get the value of a parameter that was set using set()
     *
	 *	@param string	key The parameter
	 *	@return string	The parameter value
	 *	@private
	 */
	function _get(key) {
		return _params[key] || _environment[key] || null;
	}
	/**
     *	Get the last tracking URI fired by the instance
     *
	 *	@param void
	 *	@return string	The last tracking URI fired
	 *	@private
	 */
	function _getLastTrackingUri() {
		var uri = tracking_uri;
		tracking_uri = '';
		return uri;
	}
	/**
     *	Clear all user-defined parameters
     *
	 *	@param string	key The parameter
	 *	@return bool	Always return TRUE
	 *	@private
	 */
	function _clear(key) {
		if (!key) {
			_params = {};
		} else {
			_params[key] = null;
		}
		return true;
	}

    return {
		/**
	     *	@see _track
		 *	@public
		 */
        track: _track,
		/**
	     *	@see _set()
		 *	@public
		 */
        set: _set, 
		/**
	     *	@see _setFn
		 *	@public
		 */
		setFn: _setFn, 
		/**
	     *	@see _get
		 *	@public
		 */
		get: _get,
		/**
	     *	@see _getLastTrackingUri
		 *	@public
		 */
		getLastTrackingUri: _getLastTrackingUri,
		/**
	     *	@see _copy
		 *	@public
		 */
		copy: _copy,
		/**
	     *	@see _clone
		 *	@public
		 */
		clone: _clone,
		/**
	     *	@see _clear
		 *	@public
		 */
		clearAll: _clear, 
		/**
	     *	Register custom events on specific DOM elements
	     *
		 *	@param 	object		obj		Object that contains the class of the DOM element as the key and the function to run on click as the value
		 *								[{withClass: 'class1', events: 'click', tags: ['a', 'input'], fn: function(target) {.....}}, 
		 *								{withClass: ['class2', 'class4'], events: ['click', 'hover'], tags: 'a', fn: function(target) {.....}, 
		 *								{withClass: 'class3', events: 'change', tags: 'select', fn: function(target) {.....}]
		 *								
		 *	@return void
		 *	@requires		YUI DOM 2.7.0
		 *	@public
		 */
		registerCustomEvents: function(data) {
			var $evt = YAHOO.util.Event;
			var $dom = YAHOO.util.Dom;
			if (!$evt || !$dom) {
				throw new Error('Edmunds UI: ETRACK\'s method "registerCustomEvents" requires YUI Utilities');
			}
			var cnt = data.length;
			for(var i=0; i<cnt; i++) {
				// Cache the class variable in object i
				var cls = data[i].withClass;
				// Continue to the next object if cls is not a string or an array
				if (!((cls && typeof cls === 'string') || (typeof cls === 'object' && cls.length))) {
					continue;
				}
				
				// Cache the events variable in object i
				var events = data[i].events || 'click';
				// Continue to the next object if events is not a string or an array
				if (!((events && typeof events === 'string') || (typeof events === 'object' && events.length))) {
					continue;
				}
				
				// Cache the tags variable in object i
				var tags = data[i].tags || 'a';
				// Continue to the next object if tags is not a string or an array
				if (!((tags && typeof tags === 'string') || (typeof tags === 'object' && tags.length))) {
					continue;
				}
				
				// Cache the function to be executed when the event(s) occur
				var fn = data[i].fn;
				// Continue to the next object if fn is not a function 
				if (!(fn.constructor === Function)) {
					continue;
				}
				
				// Get the elements with the specified class(es)
				var elems;
				if (typeof cls === 'string') {
					elems = $dom.getElementsByClassName(cls);
				} else {
					elems = $dom.getElementsByClassName(cls[0]);
					var clscnt = cls.length;
					for (var q=1; q<clscnt; i++) {
						elems.concat($dom.getElementsByClassName(cls[q]));
					}
				}
				function _fn(e, data) {
					var target = $evt.getTarget(e);
	                target.normalize();
					var tags;
					var fn = data.fn;

					if (typeof data.tags === 'string') {
						tags = data.tags;
					} else {
						tags = data.tags.join('|');
					}

	                var tag = target.tagName.toLowerCase();
	                if (tags.indexOf(tag) !== -1) {
						fn(target);
					}
				}
				
				if (typeof events === 'string') {
					$evt.addListener(elems, events, _fn, {fn: fn, tags: tags});
				} else {
					var eventscnt = events.length;
					for (var k=0; k<eventscnt; k++) {
						$evt.addListener(elems, events[k], _fn, {fn: fn, tags: tags});
					}
				}
			}
		}, 
		/**
	     *	Go through the DOM and process all elements that are defined in tid_tags and have a tif attribute.
		 * 	Attach an event listener on the element for the event defined in tid_tags.
	     *
		 *	@param 	string		root	The root DOM element under which the search for elements with "tid" class should run
		 *  @param 	function	fn		The function that handles TID once they are evaluated
		 *	@return void
		 *	@requires		YUI DOM 2.7.0
		 *	@public
		 */
		processStaticTids: function(root, fn) {
			var $evt = YAHOO.util.Event;
			var $dom = YAHOO.util.Dom;
			function _processLink(elem, parent) {
                elem.normalize();
				var getXY = $dom.getXY;
				var module = parent.id;
                var txt = '';
                var type = '';
                var root_xy = getXY(root);
                var xy = getXY(elem);

                // If the element is a button or input fields
                if (elem.tagName.toLowerCase() != 'a') {
                    txt = elem.value || elem.name;
                    type = elem.tagName.toLowerCase();
                } else {
                    var nodes = elem.childNodes;
                    var len = nodes.length;
                    var thisNode = null;
                    if (len == 1) {
                        thisNode = nodes[0];
                    } else {
                        for (var i=0; i<len; i++) {
                            if (nodes[i].nodeType == 3 || nodes[i].nodeType == 1) {
                                thisNode = nodes[i];
                                break;
                            }
                        }
                    }
                    // If for some reason there are no viable children to be processed, quit.
                    if (thisNode === null) {
                        return;
                    }
                    
                    switch(thisNode.nodeType) {
                        // Text node
                        case 3:
                            txt = thisNode.nodeValue;
                            type = 'text';
                            break;
                        // Element node
                        case 1:
                            thisNode.normalize();
                            var tag = thisNode.tagName.toLowerCase();
                            if (tag == 'img') {
                                txt = thisNode.getAttribute('alt') || thisNode.getAttribute('title');
                                type = 'image';
                            } else {
                                txt = (thisNode.childNodes.length) ? thisNode.childNodes[0].nodeValue : thisNode.nodeValue;
                                type = 'text';
                            }
                    }
                }
				txt = txt.replace(/([^\x20-\x7E]+)/ig, '-');
				//var tid = module+'.'+type+'.'+escape(txt)+'.'+parseInt(xy[0] - root_xy[0])+','+parseInt(xy[1] - root_xy[1]);
				// Per Reporting request, removing coord
				var tid = module+'.'+type+'.'+escape(txt);
                fn(tid);
            }

			// Get all DIV elements with class "tid" under the root element
			var elem = $dom.getElementsByClassName('tid', 'div', root);
			// Add an event listener to those nodes to register all bubbled up onclick events
            $evt.addListener(elem, 'click', function(e) {
				// Get the sub-element on which the click actually occured
                var target = $evt.getTarget(e);
				var ancestor = $dom.getAncestorByClassName(target, 'tid');
				if (ancestor != this) {
					return;
				}
				// Remove empty child nodes and merge text nodes to simplify the tree
                target.normalize();
				// Get the tag of this node
                var tag = target.tagName.toLowerCase();
                // If this node is in fact a link
                if (tag == 'a') {
					// Start processing it
                    _processLink(target, this);
                } else { // Otherwise
                    // Check immediate parent node to see if it is a link (i.e. <a href=""><span>xxx</span></a>) or a button/input
                    var thisNode = target.parentNode.tagName.toLowerCase();
					// It if it is
                    if (thisNode == 'a' || thisNode == 'button' || thisNode == 'input') {
						// Start processing it
                        _processLink(target.parentNode, this);
                    } else if (target.childNodes.length > 0 && target.childNodes[0].tagName) { // Otherwise, if the node has children ...
						// Check immediate child to see if it is a link (i.e. <li><a href="">xxxxx</a></li>) or a button/input
	                    thisNode = target.childNodes[0].tagName.toLowerCase();
	                    if (thisNode == 'a' || thisNode == 'button' || thisNode == 'input') {
							// Start processing it
	                        _processLink(target.childNodes[0], this);
	                    }
					}
				}
            });
		}
    };
};

// Cookie module 2.0.1
EDMUNDS = window.EDMUNDS || {};
EDMUNDS.Cookie = EDMUNDS.Cookie || {
	VERSION: '2.0.1', 
    
    //-------------------------------------------------------------------------
    // Private Methods
    //-------------------------------------------------------------------------
    
    /**
     * Creates a cookie string that can be assigned into document.cookie.
     * @param {String} name The name of the cookie.
     * @param {String} value The value of the cookie.
     * @param {encodeValue} encodeValue True to encode the value, false to leave as-is.
     * @param {Object} options (Optional) Options for the cookie.
     * @return {String} The formatted cookie string.
     * @method _createCookieString
     * @private
     * @static
     */
    _createCookieString : function (name /*:String*/, value /*:Variant*/, encodeValue /*:Boolean*/, options /*:Object*/) /*:String*/ {
	    
        var text /*:String*/ = encodeURIComponent(name) + "=" + (encodeValue ? encodeURIComponent(value) : value);
           
        if (options && typeof options === 'object') {
            //expiration date
            if (options.expires instanceof Date){
                text += "; expires=" + options.expires.toGMTString();
            }
        
            //path
            if (options.path && typeof options.path === 'string'){
                text += "; path=" + options.path;
            }
    
            //domain
            if (options.domain && typeof options.domain === 'string'){
                text += "; domain=" + options.domain;
            }
            
            //secure
            if (options.secure === true){
                text += "; secure";
            }
        }
        
        return text;
    },
    
    /**
     * Formats a cookie value for an object containing multiple values.
     * @param {Object} hash An object of key-value pairs to create a string for.
     * @return {String} A string suitable for use as a cookie value.
     * @method _createCookieHash
     * @private
     * @static
     */
    _createCookieHashString : function (hash /*:Object*/) /*:String*/ {
        
        if (!(typeof hash === 'object')){
            throw new TypeError("Cookie._createCookieHashString(): Argument must be an object.");
        }
        
        var text /*:Array*/ = new Array();
        
        for (var key in hash){
            if (hash.hasOwnProperty(key) && key && typeof key === 'string'){
                text.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(hash[key])));
            }
        }
        
        return text.join("&");
    },
    
    /**
     * Parses a cookie hash string into an object.
     * @param {String} text The cookie hash string to parse. The string should already be URL-decoded.
     * @return {Object} An object containing entries for each cookie value.
     * @method _parseCookieHash
     * @private
     * @static
     */
    _parseCookieHash : function (text /*:String*/) /*:Object*/ {
    
        var hashParts /*:Array*/ = text.split("&"),
            hashPart /*:Array*/ = null,
            hash /*:Object*/ = new Object();
        
        if (text.length > 0){
            for (var i=0, len=hashParts.length; i < len; i++){
                hashPart = hashParts[i].split("=");
                hash[decodeURIComponent(hashPart[0])] = decodeURIComponent(hashPart[1]);
            }
        }
        
        return hash;
    },    
    
    /**
     * Parses a cookie string into an object representing all accessible cookies.
     * @param {String} text The cookie string to parse.
     * @param {Boolean} decode (Optional) Indicates if the cookie values should be decoded or not. Default is true.
     * @return {Object} An object containing entries for each accessible cookie.
     * @method _parseCookieString
     * @private
     * @static
     */
    _parseCookieString : function (text /*:String*/, decode /*:Boolean*/) /*:Object*/ {
    
        var cookies /*:Object*/ = new Object();        
        
        if (typeof text === 'string' && text.length > 0) {
        
            var decodeValue = (decode === false ? function(s){return s;} : decodeURIComponent);
        
            if (/[^=]+=[^=;]?(?:; [^=]+=[^=]?)?/.test(text)){            
                var cookieParts /*:Array*/ = text.split(/;\s/g),
                    cookieName /*:String*/ = null,
                    cookieValue /*:String*/ = null,
                    cookieNameValue /*:Array*/ = null;
                
                for (var i=0, len=cookieParts.length; i < len; i++){
                
                    //check for normally-formatted cookie (name-value)
                    cookieNameValue = cookieParts[i].match(/([^=]+)=/i);
                    if (cookieNameValue instanceof Array){
                        try {
                            cookieName = decodeURIComponent(cookieNameValue[1]);
                            cookieValue = decodeValue(cookieParts[i].substring(cookieNameValue[1].length+1));
                        } catch (ex){
                            //ignore the entire cookie - encoding is likely invalid
                        }
                    } else {
                        //means the cookie does not have an "=", so treat it as a boolean flag
                        cookieName = decodeURIComponent(cookieParts[i]);
                        cookieValue = cookieName;
                    }
                    cookies[cookieName] = cookieValue;
                }
            }
        }
        
        return cookies;
    },    
    
    //-------------------------------------------------------------------------
    // Public Methods
    //-------------------------------------------------------------------------

    /**
     * Returns the cookie value for the given name.
     * @param {String} name The name of the cookie to retrieve.
     * @param {Function} converter (Optional) A function to run on the value before returning
     *      it. The function is not used if the cookie doesn't exist.
     * @return {Variant} If no converter is specified, returns a string or null if
     *      the cookie doesn't exist. If the converter is specified, returns the value
     *      returned from the converter or null if the cookie doesn't exist.
     * @method get
     * @static
     */
    get : function (name /*:String*/, converter /*:Function*/) /*:Variant*/{

        var cookies /*:Object*/ = this._parseCookieString(document.cookie);        
        
        if (!(typeof name === 'string') || name === ""){
            throw new TypeError("Cookie.get(): Cookie name must be a non-empty string.");
        }
        
        if (typeof cookies[name] === 'undefined') {
            return null;
        }
        
        if (!(converter && converter.constructor &&  converter.constructor == Function)) {
            return cookies[name];
        } else {
            return converter(cookies[name]);
        }
    },
    
    /**
     * Returns the value of a subcookie.
     * @param {String} name The name of the cookie to retrieve.
     * @param {String} subName The name of the subcookie to retrieve.
     * @param {Function} converter (Optional) A function to run on the value before returning
     *      it. The function is not used if the cookie doesn't exist.
     * @return {Variant} If the cookie doesn't exist, null is returned. If the subcookie
     *      doesn't exist, null if also returned. If no converter is specified and the
     *      subcookie exists, a string is returned. If a converter is specified and the
     *      subcookie exists, the value returned from the converter is returned.
     * @method getSub
     * @static
     */
    getSub : function (name /*:String*/, subName /*:String*/, converter /*:Function*/) /*:Variant*/ {

        var hash /*:Variant*/ = this.getSubs(name);  

        if (hash !== null) {
            
            if (!(typeof subName === 'string') || subName === ""){
                throw new TypeError("Cookie.getSub(): Subcookie name must be a non-empty string.");
            }
            
            if (typeof hash[subName] === 'undefined'){
                return null;
            }            
        
            if (!(converter && converter.constructor && converter.constructor == Function)){
                return hash[subName];
            } else {
                return converter(hash[subName]);
            }
        } else {
            return null;
        }
    
    },
    
    /**
     * Returns an object containing name-value pairs stored in the cookie with the given name.
     * @param {String} name The name of the cookie to retrieve.
     * @return {Object} An object of name-value pairs if the cookie with the given name
     *      exists, null if it does not.
     * @method getHash
     * @static
     */
    getSubs : function (name /*:String*/) /*:Object*/ {
        
        //check cookie name
        if (!(typeof name === 'string') || name === ""){
            throw new TypeError("Cookie.getSubs(): Cookie name must be a non-empty string.");
        }
        
        var cookies = this._parseCookieString(document.cookie, false);
        if (typeof cookies[name] === 'string'){
            return this._parseCookieHash(cookies[name]);
        }
        return null;
    },
    
    /**
     * Removes a cookie from the machine by setting its expiration date to
     * sometime in the past.
     * @param {String} name The name of the cookie to remove.
     * @param {Object} options (Optional) An object containing one or more
     *      cookie options: path (a string), domain (a string), 
     *      and secure (true/false). The expires option will be overwritten
     *      by the method.
     * @return {String} The created cookie string.
     * @method remove
     * @static
     */
    remove : function (name /*:String*/, options /*:Object*/) /*:String*/ {
        
        //check cookie name
        if (!(typeof name === 'string') || name === ""){
            throw new TypeError("Cookie.remove(): Cookie name must be a non-empty string.");
        }
        
        //set options
        options = options || {};
        options.expires = new Date(0);
        
        //set cookie
        return this.set(name, "", options);
    },

    /**
     * Removes a sub cookie with a given name.
     * @param {String} name The name of the cookie in which the subcookie exists.
     * @param {String} subName The name of the subcookie to remove.
     * @param {Object} options (Optional) An object containing one or more
     *      cookie options: path (a string), domain (a string), expires (a Date object),
     *      and secure (true/false). This must be the same settings as the original
     *      subcookie.
     * @return {String} The created cookie string.
     * @method removeSub
     * @static
     */
    removeSub : function(name /*:String*/, subName /*:String*/, options /*:Object*/) /*:String*/ {
    
        //check cookie name
        if (!(typeof name === 'string') || name === ""){
            throw new TypeError("Cookie.removeSub(): Cookie name must be a non-empty string.");
        }
        
        //check subcookie name
        if (!(typeof subName === 'string') || subName === ""){
            throw new TypeError("Cookie.removeSub(): Subcookie name must be a non-empty string.");
        }
        
        //get all subcookies for this cookie
        var subs = this.getSubs(name);
        
        //delete the indicated subcookie
        if (subs && typeof subs === 'object' && subs.hasOwnProperty(subName)){
            delete subs[subName];
            
            //reset the cookie
            return this.setSubs(name, subs, options);
        } else {
            return "";
        }
        
    },

    /**
     * Sets a cookie with a given name and value.
     * @param {String} name The name of the cookie to set.
     * @param {Variant} value The value to set for the cookie.
     * @param {Object} options (Optional) An object containing one or more
     *      cookie options: path (a string), domain (a string), expires (a Date object),
     *      and secure (true/false).
     * @return {String} The created cookie string.
     * @method set
     * @static
     */
    set : function (name /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {

        if (!(name && typeof name === 'string')){
            throw new TypeError("Cookie.set(): Cookie name must be a string.");
        }
        
        if (typeof value === 'undefined'){
            throw new TypeError("Cookie.set(): Value cannot be undefined.");
        }
        
    
        var text /*:String*/ = this._createCookieString(name, value, true, options);
        document.cookie = text;
        return text;
    },
        
    /**
     * Sets a sub cookie with a given name to a particular value.
     * @param {String} name The name of the cookie to set.
     * @param {String} subName The name of the subcookie to set.
     * @param {Variant} value The value to set.
     * @param {Object} options (Optional) An object containing one or more
     *      cookie options: path (a string), domain (a string), expires (a Date object),
     *      and secure (true/false).
     * @return {String} The created cookie string.
     * @method setSub
     * @static
     */
    setSub : function (name /*:String*/, subName /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {

        if (!(typeof name === 'string') || name === ""){
            throw new TypeError("Cookie.setSub(): Cookie name must be a non-empty string.");
        }

        if (!(typeof subName === 'string') || subName === ""){
            throw new TypeError("Cookie.setSub(): Subcookie name must be a non-empty string.");
        }
        
        if (typeof value === 'undefined'){
            throw new TypeError("Cookie.setSub(): Subcookie value cannot be undefined.");
        }

        var hash /*:Object*/ = this.getSubs(name);
        
        if (!(hash && typeof hash === 'object')){
            hash = new Object();
        }
        
        hash[subName] = value;        
        
        return this.setSubs(name, hash, options);
        
    },
    
    /**
     * Sets a cookie with a given name to contain a hash of name-value pairs.
     * @param {String} name The name of the cookie to set.
     * @param {Object} value An object containing name-value pairs.
     * @param {Object} options (Optional) An object containing one or more
     *      cookie options: path (a string), domain (a string), expires (a Date object),
     *      and secure (true/false).
     * @return {String} The created cookie string.
     * @method setSubs
     * @static
     */
    setSubs : function (name /*:String*/, value /*:Object*/, options /*:Object*/) /*:String*/ {
        
        if (!(name && typeof name === 'string')){
            throw new TypeError("Cookie.setSubs(): Cookie name must be a string.");
        }
        
        if (!(value && typeof value === 'object')){
            throw new TypeError("Cookie.setSubs(): Cookie value must be an object.");
        }
    
        var text /*:String*/ = this._createCookieString(name, this._createCookieHashString(value), false, options);
        document.cookie = text;
        return text;        
    }    

};

// User module 2.0.6
if (!window.EDMUNDS) throw new Error('Edmunds UI: User module requires Edmunds Core library');
if (!EDMUNDS.Cookie) throw new Error('Edmunds UI: User module requires Edmunds Cookie module');
EDMUNDS.User = EDMUNDS.User || {
	VERSION: "2.0.6", 
	// Session cookie lasts for 30 minutes *according to WebTrends recommendation!*
	setSessionCookie: function(val) {
		EDMUNDS.Cookie.set('edw', val, {expires: new Date(new Date().getTime() + 60*1000*30), path: '/'});
	}, 
	getSessionCookie: function() {
		return EDMUNDS.Cookie.get('edw');
	},
	// User cookie lasts for one year
	setUserCookie: function(val) {
		EDMUNDS.Cookie.set('edmunds', val, {expires: new Date(new Date().getTime() + 60*1000*60*24*365*1), path: '/'});
	}, 
	getUserCookie: function() {
		return EDMUNDS.Cookie.get('edmunds');
	}, 
	// Zipcode cookie is part of the EdmundsYear cookie
	setZipcode: function(val) {
		EDMUNDS.Cookie.setSub('EdmundsYear', 'zip', val, {expires: new Date(new Date().getTime() + 60*1000*60*24*365*1), path: '/'});
	}, 
	getZipcode: function() {
		return EDMUNDS.Cookie.getSub('EdmundsYear', 'zip');
	}, 
	// State cookie is part of the EdmundsYear cookie
	setState: function(val) {
		EDMUNDS.Cookie.setSub('EdmundsYear', 'state', val, {expires: new Date(new Date().getTime() + 60*1000*60*24*365*1), path: '/'});
	}, 
	getState: function() {
		return EDMUNDS.Cookie.getSub('EdmundsYear', 'state');
	}, 
	// DMA cookie is part of the EdmundsYear cookie
	setDMA: function(val) {
		EDMUNDS.Cookie.setSub('EdmundsYear', 'dma', val, {expires: new Date(new Date().getTime() + 60*1000*60*24*365*1), path: '/'});
	}, 
	getDMA: function() {
		var dma = EDMUNDS.Cookie.getSub('EdmundsYear', 'dma');
		if (dma) {
			return dma.replace(/:([\w!-.]+)/g, '');
		}
	}, 
	// Countyfips cookie is part of the EdmundsYear cookie
	setCountyfips: function(val) {
		EDMUNDS.Cookie.setSub('EdmundsYear', 'countyfips', val, {expires: new Date(new Date().getTime() + 60*1000*60*24*365*1), path: '/'});
	}, 
	getCountyfips: function() {
		return EDMUNDS.Cookie.getSub('EdmundsYear', 'countyfips');
	}, 
	// Authentication
	isLoggedIn: function() {
		var email = EDMUNDS.Cookie.getSub('EDMID', 'EMAIL');
		if (email){
			return true;
		}
		return false;
	}, 
	getFirstName: function() {
		return EDMUNDS.Cookie.getSub('EDMID', 'FN');
	}, 
	getLastName: function() {
		return EDMUNDS.Cookie.getSub('EDMID', 'LN');
	}, 
	getRole: function() {
		var role = EDMUNDS.Cookie.getSub('EDMID', 'ROLE');
		if (role && role.indexOf('MEMBER') !== -1) {
			return 'member';
		}
		return 'guest';
	}
};

// Adunit module 2.0.4
/**
 * @fileOverview Handles ad units throughout the site. Displays them in either
 * JavaScript or iFrame formats.
 *
 *	@requires   YAHOO
 *  @requires   EDMUNDS
 *  @requires   YAHOO.util.Event
 *  @requires   YAHOO.lang
 *  @class      Ad unit manager
 */

if (!window.EDMUNDS) throw new Error('Edmunds UI: AdUnit module requires Edmunds Core library');
if (!EDMUNDS.User) throw new Error('Edmunds UI: AdUnit module requires Edmunds User module');

if (!EDMUNDS.AdUnit) {
	// Initialize the object
	EDMUNDS.AdUnit = {};
	
	/**
	 * @property Class version
	 * @type string
	 * @static
	 */
	EDMUNDS.AdUnit.VERSION = "2.0.4";
	
	/**
	 *	AdUnit class can be used as a helper to create and output IFrame or Javascrip Ads.
	 *
	 *  @param      string    	id                    	The HTMLElement ID of the ad
	 *  @param      string    	targetParams        	The URL querystring
	 *  @param      string    	format                	"iframe" or "javascript"
	 *  @param      int        	width                	Width of the HTMLElement of ad
	 *  @param      int        	height                	Height of the HTMLElement of ad
	 *  @param      string    	siteName            	Name of the site calling the ad (???????)
	 *  @return     void
	 *  @constructor Initialize the instance variables
	 *	@version 2.0.0
	 */
	EDMUNDS.AdUnit = function(id, siteName, keyValues, targetParams, format, width, height) {
	    this.id = id;
		this.siteName = siteName;
		this.keyValues = keyValues;
		this.uValueKeyValue = keyValues.replace(/=/g, '_').replace(/;/g, ':');
		this.url = '';
	    this.targetParams = targetParams;
	    this.format = format;
	    this.width = width;
	    this.height = height;
	    this.randomNumParam = new Date().getTime();

	    // If targetParams doesn't specify the u-value, generate one automatically.
	    if (this.targetParams !== '' && this.targetParams.match(/;u=[^;]+;/) === null) {
	        var userValue = this.generateUserValue(this.targetParams);
	        this.targetParams += ';' + userValue;
	    }
	    // Map name=!PARAMs to their PARAM values.
	    this.targetParamsOnce = this.replaceAdVariablesOnce(this.targetParams);
	    this.targetParams = this.replaceAdVariablesRefresh(this.targetParamsOnce);
	
		if (format == 'iframe') {
			EDMUNDS.AdUnit.ad_units.push(this);
		}
	};
	
	// Extend the class
	EDMUNDS.AdUnit.prototype = {
	    /**
	     *    Refresh the ad after an AJAX call is made
	     *
	     *    @memberOf EDMUNDS.AdUnit
	     *    @param    void
	     *    @return    void
	     */
	    refresh: function() {
			if (this.format == "iframe") {
				this.targetParams = this.replaceAdVariablesRefresh(this.targetParamsOnce);
		        this.outputAd();
			}
	    },
	    /**
	     *    Return the URL of this object
	     *
	     *    @memberOf EDMUNDS.AdUnit
	     *    @param    void
	     *    @return    string        The ad URL
	     */
	    getAdUrl: function () {
	        var randNum = this.randomNumParam;
	        var adTargetParams = this.targetParams;
			var type = (this.format == "iframe") ? "adi/":"adj/";
			var siteName = this.siteName + "/;"+this.keyValues;
			this.url = "http://ad.doubleclick.net/" + type + siteName + adTargetParams + 'ord=123456789';
	        return this.url;
	    },
	    /**
	     *    Writes out the Ad in one of two ways, depending on the format
	     *
	     *    @memberOf EDMUNDS.AdUnit
	     *    @param    void
	     *    @return    void
	     */
	    outputAd: function() {
			var src = this.getAdUrl();
			if (this.format == 'iframe') {
	       		var adFrame = document.getElementById(this.id);
             	if(adFrame && adFrame.contentWindow) {
             		adFrame.contentWindow.location.replace(src);
              	} else if(adFrame.contentDocument) {
               		adFrame.contentDocument.location.replace(src);
             	}
	        } else {
	            var content = '<script type="text/javascript" src="'+src+'"></script>';
		        document.write(content);
	        }
	    },
	    /**
	     *    Create a Javascript configured Ad and echo it into the DOM
	     *
	     *    @memberOf EDMUNDS.AdUnit
	     *    @param    string    targetParams    The URL querystring
	     *    @param    string    requestUrl        The URL in which the ad container lives
	     *    @return    string    The updated URL querystring
	     */
	    replaceAdVariablesOnce: function(targetParams) {
	        //regular expressions for replacement variable names to be used
	        var pageurlKey = /(!PAGEURL)/g;
	        var zipKey = /(!ZIP)/g;
	        var dmaKey = /(!DMA)/g;
	        var edmundsKey = /(!EDMUNDSID)/g;
	        var resisegKey = /(!RSISEG)/g;
	        var edwKey = /(!EDW)/g;
	        var stateKey = /(!STATE)/g;
	        var countyFipKey = /(!COUNTYFIP)/g;

	        //if target parameters has content then check for replacement variables
	        if(targetParams){
	            targetParams = targetParams.replace(pageurlKey, window.location.href);
	            targetParams = targetParams.replace(zipKey, EDMUNDS.User.getZipcode());
				var stateValue = EDMUNDS.User.getState();
				targetParams = targetParams.replace(stateKey, stateValue);
	            targetParams = targetParams.replace(countyFipKey, stateValue + EDMUNDS.User.getCountyfips());
				targetParams = targetParams.replace(edmundsKey, EDMUNDS.User.getUserCookie());
				var resisegValue = EDMUNDS.Cookie.get('rsi_segs');
				if (resisegValue) {
					resisegValue = resisegValue.replace(/B05501_(\d+)\|?/g, "rs=$1;");
		            resisegValue = resisegValue.replace(/((rs=\d+;){10}).*/, "$1");
				}
				targetParams = targetParams.replace(resisegKey, resisegValue);
	           	targetParams = targetParams.replace(edwKey, EDMUNDS.User.getSessionCookie());
				targetParams = targetParams.replace(dmaKey, EDMUNDS.User.getDMA());
	        }
	        return targetParams;
	    },

	    /**
	     *      Create a Javascript configured Ad and echo it into the DOM.  This method is called every time the ad is refreshed
	     *      @method replaceAdVariablesRefresh
	     *      @param  string  targetParams    The URL querystring
	     *      @return string  The updated targetParams querystring
	     */
	    replaceAdVariablesRefresh: function(targetParams) {
	        //regular expressions for replacement variable names to be used
	        var edwTimestampKey = /(!TIMESTAMP)/g;
			targetParams = targetParams.replace(edwTimestampKey, PAGESETUP.scope.timestamp  || new Date().getTime());
	        return targetParams;
	    },

	    /**
	     * @memberOf EDMUNDS.AdUnit
	     * @method generateUserValue
	     * @param object paramHash    key/value hash of ad parameters
	     * @return string    the u-value string
	     *
	     * The form of the u-value is:
	     *     u=[KEY_VALUE:...]|ED_COOKIE|EDW_COOKIE|TIMESTAMP|URL|ZIP;
	     * The valid key_value components are:
	     *     [dma, cty] - from cookie values
	     *     ["make", "mdl", "page", "pos", "sect",  "seg", "theme", "type"] - from page parameters
	     */        
	    generateUserValue: function(targetParams) {
	        var uvalStr = "make_X:mdl_X:page_X:pos_X:sect_X:seg_X:theme_X:type_X:cty_!COUNTYFIP:dma_!DMA|!EDMUNDSID|!EDW|!TIMESTAMP|!PAGEURL|!ZIP;";

	        // Decompose targetParams into key/value pairs and build a map.
	        var paramHash = {};
	        var paramRe = /([^=]+)=([^;]+);/g;
	        while (paramRe.test(targetParams)) {
	            paramHash[paramRe.$1] = paramRe.$2;
	        }

	        // RE function to replace name_PARAMs from paramHash or remove.
	        function replaceUval(matchstr, p1) {
	            if (paramHash[p1]) {
	                return(p1 + "_" + paramHash[p1] + ":");
	            } else {
	                return("");
	            }
	        }

	        // Map all name_X: strings to their name_value: or delete.
	        uvalStr = uvalStr.replace(/([a-z]+)_X:/g, replaceUval);
	        return('u='+this.uValueKeyValue+uvalStr);
	    }

	};

	/**
	 * @property Array of ad units
	 * @type array
	 * @static
	 */
	EDMUNDS.AdUnit.ad_units = [];
	
	/**
	 *    Refresh all ads
	 *
	 *    @memberOf EDMUNDS.AdUnit
	 *    @param    void
	 *    @return    void
	 *    @static
	 */
	EDMUNDS.AdUnit.refreshAll = function() {
	    var cnt = EDMUNDS.AdUnit.ad_units.length;
		for (var i=0; i<cnt; i++) {
			EDMUNDS.AdUnit.ad_units[i].refresh();
		}
	};
}

//
// Copyright (c) 2008 Paul Duncan (paul@pablotron.org)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

/**
 * Persist - top-level namespace for Persist library.
 */
STORAGE = (function() {
  var VERSION = '0.1.2', P, B, esc, init, empty, ec, C;

  // easycookie 0.2.1 (pre-minified)
  // (see http://pablotron.org/software/easy_cookie/)
  ec = (function(){var EPOCH='Thu, 01-Jan-1970 00:00:01 GMT',RATIO=1000*60*60*24,KEYS=['expires','path','domain'],esc=escape,un=unescape,doc=document,me;var get_now=function(){var r=new Date();r.setTime(r.getTime());return r;}
var cookify=function(c_key,c_val){var i,key,val,r=[],opt=(arguments.length>2)?arguments[2]:{};r.push(esc(c_key)+'='+esc(c_val));for(i=0;i<KEYS.length;i++){key=KEYS[i];if(val=opt[key])
r.push(key+'='+val);}
if(opt.secure)
r.push('secure');return r.join('; ');}
var alive=function(){var k='__EC_TEST__',v=new Date();v=v.toGMTString();this.set(k,v);this.enabled=(this.remove(k)==v);return this.enabled;}
me={set:function(key,val){var opt=(arguments.length>2)?arguments[2]:{},now=get_now(),expire_at,cfg={};if(opt.expires){opt.expires*=RATIO;cfg.expires=new Date(now.getTime()+opt.expires);cfg.expires=cfg.expires.toGMTString();}
var keys=['path','domain','secure'];for(i=0;i<keys.length;i++)
if(opt[keys[i]])
cfg[keys[i]]=opt[keys[i]];var r=cookify(key,val,cfg);doc.cookie=r;return val;},has:function(key){key=esc(key);var c=doc.cookie,ofs=c.indexOf(key+'='),len=ofs+key.length+1,sub=c.substring(0,key.length);return((!ofs&&key!=sub)||ofs<0)?false:true;},get:function(key){key=esc(key);var c=doc.cookie,ofs=c.indexOf(key+'='),len=ofs+key.length+1,sub=c.substring(0,key.length),end;if((!ofs&&key!=sub)||ofs<0)
return null;end=c.indexOf(';',len);if(end<0)
end=c.length;return un(c.substring(len,end));},remove:function(k){var r=me.get(k),opt={expires:EPOCH};doc.cookie=cookify(k,'',opt);return r;},keys:function(){var c=doc.cookie,ps=c.split('; '),i,p,r=[];for(i=0;i<ps.length;i++){p=ps[i].split('=');r.push(un(p[0]));}
return r;},all:function(){var c=doc.cookie,ps=c.split('; '),i,p,r=[];for(i=0;i<ps.length;i++){p=ps[i].split('=');r.push([un(p[0]),un(p[1])]);}
return r;},version:'0.2.1',enabled:false};me.enabled=alive.call(me);return me;}());

  // empty function
  empty = function() { };

  // escape spaces in name
  esc = function(str) {
    return 'PS' + str.replace(/_/g, '__').replace(/ /g, '_s');
  };



  C = {
    /*
     * Backend search order.
     *
     * Note that the search order is significant; the backends are
     * listed in order of capacity, and many browsers
     * support multiple backends, so changing the search order could
     * result in a browser choosing a less capable backend.
     *
     */
    search_order: [
      // TODO: air
      'gears',
      'localstorage',
      'whatwg_db',
      'globalstorage',
      'flash',
      'ie',
      'cookie'
    ],

    // valid name regular expression
    name_re: /^[a-z][a-z0-9_ -]+$/i,

    // list of backend methods
    methods: [
      'init',
      'get',
      'set',
      'remove',
      'load',
      'save'
      // TODO: clear method?
    ],

    // sql for db backends (gears and db)
    sql: {
      version:  '1', // db schema version

      create:   "CREATE TABLE IF NOT EXISTS persist_data (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)",
      get:      "SELECT v FROM persist_data WHERE k = ?",
      set:      "INSERT INTO persist_data(k, v) VALUES (?, ?)",
      remove:   "DELETE FROM persist_data WHERE k = ?"
    },

    // default flash configuration
    flash: {
      // ID of wrapper element
      div_id:   '_persist_flash_wrap',

      // id of flash object/embed
      id:       '_persist_flash',

      // default path to flash object
      path: 'persist.swf',
      size: { w:1, h:1 },

      // arguments passed to flash object
      args: {
        autostart: true
      }
    }
  };

  // built-in backends
  B = {
    // gears db backend (webkit, Safari 3.1+)
    // (src: http://code.google.com/apis/gears/api_database.html)
    gears: {
      // no known limit
      size:   -1,

      test: function() {
        // test for gears
        return (window.google && window.google.gears) ? true : false;
      },

      methods: {
        transaction: function(fn) {
          var db = this.db;

          // begin transaction
          db.execute('BEGIN').close();

          // call callback fn
          fn.call(this, db);

          // commit changes
          db.execute('COMMIT').close();
        },

        init: function() {
          var db;

          // create database handle (TODO: add schema version?)
          db = this.db = google.gears.factory.create('beta.database');

          // open database
          // from gears ref:
          //
          // Currently the name, if supplied and of length greater than
          // zero, must consist only of visible ASCII characters
          // excluding the following characters:
          //
          //   / \ : * ? " < > | ; ,
          db.open(esc(this.name));

          // create table
          db.execute(C.sql.create).close();
        },

        get: function(key, fn, scope) {
          var r, sql = C.sql.get;

          // if callback isn't defined, then return
          if (!fn)
            return;

          // begin transaction
          this.transaction(function (t) {
            // exec query
            r = t.execute(sql, [key]);

            // call callback
            if (r.isValidRow())
              fn.call(scope || this, true, r.field(0));
            else
              fn.call(scope || this, false, null);

            // close result set
            r.close();
          });
        },

        set: function(key, val, fn, scope) {
          var rm_sql = C.sql.remove,
              sql    = C.sql.set, r;

          // begin set transaction
          this.transaction(function(t) {
            // exec remove query
            t.execute(rm_sql, [key]).close();

            // exec set query
            t.execute(sql, [key, val]).close();

            // run callback
            if (fn)
              fn.call(scope || this, true, val);
          });
        },

        // begin remove transaction
        remove: function(key, fn, scope) {
          var get_sql = C.sql.get;
              sql = C.sql.remove,
              r, val;

          this.transaction(function(t) {
            // if a callback was defined, then get the old
            // value before removing it
            if (fn) {
              // exec get query
              r = t.execute(get_sql, [key]);

              if (r.isValidRow()) {
                // key exists, get value
                val = r.field(0);

                // exec remove query
                t.execute(sql, [key]).close();

                // exec callback
                fn.call(scope || this, true, val);
              } else {
                // key does not exist, exec callback
                fn.call(scope || this, false, null);
              }

              // close result set
              r.close();
            } else {
              // no callback was defined, so just remove the
              // data without checking the old value

              // exec remove query
              t.execute(sql, [key]).close();
            }
          });
        }
      }
    },

    // whatwg db backend (webkit, Safari 3.1+)
    // (src: whatwg and http://webkit.org/misc/DatabaseExample.html)
    whatwg_db: {
      size:   200 * 1024,

      test: function() {
        var name = 'PersistJS Test',
            desc = 'Persistent database test.';

        // test for openDatabase
        if (!window.openDatabase)
          return false;

        // make sure openDatabase works
        // XXX: will this leak a db handle and/or waste space?
        if (!window.openDatabase(name, C.sql.version, desc, B.whatwg_db.size))
          return false;

        return true;
      },

      methods: {
        transaction: function(fn) {
          if (!this.db_created) {
            var sql = C.sql.create;

            this.db.transaction(function(t) {
              // create table
              t.executeSql(sql, [], function() {
                this.db_created = true;
              });
            }, empty); // trap exception
          }

          this.db.transaction(fn);
        },

        init: function() {
          var desc, size;

          // init description and size
          desc = this.o.about || "Persistent storage for " + this.name;
          size = this.o.size || B.whatwg_db.size;

          // create database handle
          this.db = openDatabase(this.name, C.sql.version, desc, size);
        },

        get: function(key, fn, scope) {
          var sql = C.sql.get;

          // if callback isn't defined, then return
          if (!fn)
            return;

          // get callback scope
          scope = scope || this;

          // begin transaction
          this.transaction(function (t) {
            t.executeSql(sql, [key], function(t, r) {
              if (r.rows.length > 0)
                fn.call(scope, true, r.rows.item(0)['v']);
              else
                fn.call(scope, false, null);
            });
          });
        },

        set: function(key, val, fn, scope) {
          var rm_sql = C.sql.remove,
              sql    = C.sql.set;

          // begin set transaction
          this.transaction(function(t) {
            // exec remove query
            t.executeSql(rm_sql, [key], function() {
              // exec set query
              t.executeSql(sql, [key, val], function(t, r) {
                // run callback
                if (fn)
                  fn.call(scope || this, true, val);
              });
            });
          });

          return val;
        },

        // begin remove transaction
        remove: function(key, fn, scope) {
          var get_sql = C.sql.get;
              sql = C.sql.remove;

          this.transaction(function(t) {
            // if a callback was defined, then get the old
            // value before removing it
            if (fn) {
              // exec get query
              t.executeSql(get_sql, [key], function(t, r) {
                if (r.rows.length > 0) {
                  // key exists, get value
                  var val = r.rows.item(0)['v'];

                  // exec remove query
                  t.executeSql(sql, [key], function(t, r) {
                    // exec callback
                    fn.call(scope || this, true, val);
                  });
                } else {
                  // key does not exist, exec callback
                  fn.call(scope || this, false, null);
                }
              });
            } else {
              // no callback was defined, so just remove the
              // data without checking the old value

              // exec remove query
              t.executeSql(sql, [key]);
            }
          });
        }
      }
    },

    // globalstorage backend (globalStorage, FF2+, IE8+)
    // (src: http://developer.mozilla.org/en/docs/DOM:Storage#globalStorage)
    //
    // TODO: test to see if IE8 uses object literal semantics or
    // getItem/setItem/removeItem semantics
    globalstorage: {
      // (5 meg limit, src: http://ejohn.org/blog/dom-storage-answers/)
      size: 5 * 1024 * 1024,

      test: function() {
        return window.globalStorage ? true : false;
      },

      methods: {
        key: function(key) {
          return esc(this.name) + esc(key);
        },

        init: function() {
          this.store = globalStorage[this.o.domain];
        },

        get: function(key, fn, scope) {
          // expand key
          key = this.key(key);

          if (fn)
            fn.call(scope || this, true, this.store.getItem(key));
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // set value
          this.store.setItem(key, val);

          if (fn)
            fn.call(scope || this, true, val);
        },

        remove: function(key, fn, scope) {
          var val;

          // expand key
          key = this.key(key);

          // get value
          val = this.store[key];

          // delete value
          this.store.removeItem(key);

          if (fn)
            fn.call(scope || this, (val !== null), val);
        }
      }
    },

    // localstorage backend (globalStorage, FF2+, IE8+)
    // (src: http://www.whatwg.org/specs/web-apps/current-work/#the-localstorage)
    localstorage: {
      // (unknown?)
      size: -1,

      test: function() {
        return window.localStorage ? true : false;
      },

      methods: {
        key: function(key) {
          return esc(this.name) + esc(key);
        },

        init: function() {
          this.store = localStorage;
        },

        get: function(key, fn, scope) {
          // expand key
          key = this.key(key);

          if (fn)
            fn.call(scope || this, true, this.store.getItem(key));
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // set value
          this.store.setItem(key, val);

          if (fn)
            fn.call(scope || this, true, val);
        },

        remove: function(key, fn, scope) {
          var val;

          // expand key
          key = this.key(key);

          // get value
          val = this.getItem(key);

          // delete value
          this.store.removeItem(key);

          if (fn)
            fn.call(scope || this, (val !== null), val);
        }
      }
    },

    // IE backend
    ie: {
      prefix:   '_persist_data-',
      // style:    'display:none; behavior:url(#default#userdata);',

      // 64k limit
      size:     64 * 1024,

      test: function() {
        // make sure we're dealing with IE
        // (src: http://javariet.dk/shared/browser_dom.htm)
        return window.ActiveXObject ? true : false;
      },

      make_userdata: function(id) {
        var el = document.createElement('div');

        // set element properties
        el.id = id;
        el.style.display = 'none';
        el.addBehavior('#default#userData');

        // append element to body
        document.body.appendChild(el);

        // return element
        return el;
      },

      methods: {
        init: function() {
          var id = B.ie.prefix + esc(this.name);

          // save element
          this.el = B.ie.make_userdata(id);

          // load data
          if (this.o.defer)
            this.load();
        },

        get: function(key, fn, scope) {
          var val;

          // expand key
          key = esc(key);

          // load data
          if (!this.o.defer)
            this.load();

          // get value
          val = this.el.getAttribute(key);

          // call fn
          if (fn)
            fn.call(scope || this, val ? true : false, val);
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = esc(key);

          // set attribute
          this.el.setAttribute(key, val);

          // save data
          if (!this.o.defer)
            this.save();

          // call fn
          if (fn)
            fn.call(scope || this, true, val);
        },

        load: function() {
          this.el.load(esc(this.name));
        },

        save: function() {
          this.el.save(esc(this.name));
        }
      }
    },

    // cookie backend
    // uses easycookie: http://pablotron.org/software/easy_cookie/
    cookie: {
      delim: ':',

      // 4k limit (low-ball this limit to handle browser weirdness, and
      // so we don't hose session cookies)
      size: 4000,

      test: function() {
        // XXX: use easycookie to test if cookies are enabled
        return P.Cookie.enabled ? true : false;
      },

      methods: {
        key: function(key) {
          return this.name + B.cookie.delim + key;
        },

        get: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // get value
          val = ec.get(key);

          // call fn
          if (fn)
            fn.call(scope || this, val != null, val);
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // save value
          ec.set(key, val, this.o);

          // call fn
          if (fn)
            fn.call(scope || this, true, val);
        },

        remove: function(key, val, fn, scope) {
          var val;

          // expand key
          key = this.key(key);

          // remove cookie
          val = ec.remove(key)

          // call fn
          if (fn)
            fn.call(scope || this, val != null, val);
        }
      }
    },

    // flash backend (requires flash 8 or newer)
    // http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_16194&sliceId=1
    // http://livedocs.adobe.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002200.html
    flash: {
      test: function() {
        // TODO: better flash detection
        if (!window.SWFObject || !deconcept || !deconcept.SWFObjectUtil)
          return false;

        // get the major version
        var major = deconcept.SWFObjectUtil.getPlayerVersion().major;

        // check flash version (require 8.0 or newer)
        return (major >= 8) ? true : false;
      },

      methods: {
        init: function() {
          if (!B.flash.el) {
            var o, key, el, cfg = C.flash;

            // create wrapper element
            el = document.createElement('div');
            el.id = cfg.div_id;

            // FIXME: hide flash element
            // el.style.display = 'none';

            // append element to body
            document.body.appendChild(el);

            // create new swf object
            o = new SWFObject(this.o.swf_path || cfg.path, cfg.id, cfg.size.w, cfg.size.h, '8');

            // set parameters
            for (key in cfg.args)
              o.addVariable(key, cfg.args[key]);

            // write flash object
            o.write(el);

            // save flash element
            B.flash.el = document.getElementById(cfg.id);
          }

          // use singleton flash element
          this.el = B.flash.el;
        },

        get: function(key, fn, scope) {
          var val;

          // escape key
          key = esc(key);

          // get value
          val = this.el.get(this.name, key);

          // call handler
          if (fn)
            fn.call(scope || this, val !== null, val);
        },

        set: function(key, val, fn, scope) {
          var old_val;

          // escape key
          key = esc(key);

          // set value
          old_val = this.el.set(this.name, key, val);

          // call handler
          if (fn)
            fn.call(scope || this, true, val);
        },

        remove: function(key, fn, scope) {
          var val;

          // get key
          key = esc(key);

          // remove old value
          val = this.el.remove(this.name, key);

          // call handler
          if (fn)
            fn.call(scope || this, true, val);
        }
      }
    }
  };

  // init function
  var init = function() {
    var i, l, b, key, fns = C.methods, keys = C.search_order;

    // set all functions to the empty function
    for (i = 0, l = fns.length; i < l; i++)
      P.Store.prototype[fns[i]] = empty;

    // clear type and size
    P.type = null;
    P.size = -1;

    // loop over all backends and test for each one
    for (i = 0, l = keys.length; !P.type && i < l; i++) {
      b = B[keys[i]];

      // test for backend
      if (b.test()) {
        // found backend, save type and size
        P.type = keys[i];
        P.size = b.size;

        // extend store prototype with backend methods
        for (key in b.methods)
          P.Store.prototype[key] = b.methods[key];
      }
    }

    // mark library as initialized
    P._init = true;
  };

  // create top-level namespace
  P = {
    // version of persist library
    VERSION: VERSION,

    // backend type and size limit
    type: null,
    size: 0,

    // expose init function
    // init: init,

    add: function(o) {
      // add to backend hash
      B[o.id] = o;

      // add backend to front of search order
      C.search_order = [o.id].concat(C.search_order);

      // re-initialize library
      init();
    },

    remove: function(id) {
      var ofs = C.search_order.indexOf(id);
      if (ofs < 0)
        return;

      // remove from search order
      C.search_order.splice(ofs, 1);

      // delete from lut
      delete B[id];

      // re-initialize library
      init();
    },

    // expose easycookie API
    Cookie: ec,

    // store API
    Store: function(name, o) {
      // verify name
      if (!C.name_re.exec(name))
        throw new Error("Invalid name");

      // XXX: should we lazy-load type?
      // if (!P._init)
      //   init();

      if (!P.type)
        throw new Error("No suitable storage found");

      o = o || {};
      this.name = name;

      // get domain (XXX: does this localdomain fix work?)
      o.domain = o.domain || location.hostname || 'localhost.localdomain';

      this.o = o;

      // expires in 2 years
      o.expires = o.expires || 365 * 2;

      // set path to root
      o.path = o.path || '/';

      // call init function
      this.init();
    }
  };

  // init persist
  init();

  // return top-level namespace
  return P;
})();