var t = (new Date).getTime();
var jhs = 'http://hem.bredband.net/ecmanaut/';

function debug( e, f, friendly )
{
  if( location.search.indexOf( 'debug' ) + 1 )
  {
    if( typeof f == 'function' )
      f = /^function ([^)]*.)/.exec( f.toString() )[1];
    if( friendly )
      prompt( 'Error in '+ f, e );
    else
      alert( 'Error in '+ f +':\n'+ e );
  }
}

if( location.hostname != 'www.blogger.com' &&
    location.pathname.substr(1).indexOf( '/' ) == -1 )
  document.write( '<script type="text/javascript" src="'+ jhs +
		  'template/blogmap.js"></' + 'script>' );
else
  var cook_maps = function(){};

if( typeof Delicious == 'object' )
  harvestDelicious();
else
  Delicious = { posts:[], tags:[] };

function init()
{
  if( init.fired ) return; else init.fired = true;
  t = (new Date).getTime() - t; // hideLater removed from callbacks below:
  var f = [ cook_maps, noComment, fixPlural, addBlogAd, harvester, renderCal,
	    createNew, babelfish, clustrMap, preload, initiateLoadingBacklinks,
	    showFaces, fakeScript ];
  for( var i=0; i<f.length; i++ )
    try { f[i](); } catch(e){ debug( e, f[i] ); }
  if( typeof smoothScrollInpageLinks == 'function' )
    smoothScrollInpageLinks();
}

function fakeScript()
{
  var c = document.getElementsByTagName( 'code' ), s, i;
  var junk = /^\s*\46lt;\133\133CDATA\133|<br[^/>]*>$|]]\46gt;\s*$/g;
  for( i=0; i<c.length; i++ )
  {
    s = c[i].getAttribute('style') || '';
    if( s.match( /display[\s:]+none/i ) )
      if( !/^http/.test( c[i].innerHTML ) )
	eval( c[i].innerHTML.replace( junk, '' ) );
      else if( c[i].getAttribute('rel') == 'stylesheet' )
	addStyles( c[i].innerHTML );
      else
	addScript( c[i].innerHTML );
  }
}

var images = { 'donate-img':
		 [jhs+'gfx/paypal-donate.gif',
		  'http://www.paypal.com/en_US/i/btn/x-click-but04.gif'] };
function preload()
{
  var url, id, i, loaded = {};
  for( id in images )
    for( var i=0; i<images[id].length; i++ )
    {
      url = images[id][i];
      if( loaded[url] ) continue;
      images[id][i] = new Image;
      images[id][i].src = url;
      loaded[url] = true;
    }
  images['donate-link'] = images['donate-img'];
}

function img( id, n )
{
  var image = $id( id ); if( image )
  image.src = images[id][n].src;
}

function removeNode( node )
{
  node.parentNode.removeChild( node );
}

function loved()
{
  var x, i, f = document.forms, love = f.love, e = f.dx.elements;
  for( i=0, x=''; i<e.length; i++ )
    if( (n = e[i]) && (n.type.toLowerCase() == 'checkbox') && n.checked )
      x += n.name;
  love.item_number.value = x;
}

function donate()
{
  var form = $id( 'donator' ), what = $id( 'melike' ), title; try {
  if( typeof article == 'string' )
    if( article.length > 10 ) // a post id
      title = ' "<b>'+ $id( 'title-'+ article ).innerHTML +'</b>".';
    else // a date
      title = 's on <b>'+ article +'</b>.';
  if( typeof article == 'undefined' && what )
    removeNode( what.parentNode.parentNode );
  if( title )
  {
    what.name = article;
    what.parentNode.innerHTML += title;
    article = null;
  }
  } catch( e ) { debug( e.message || e ); }
  if( form.style.display == 'block' )
    return form.style.display = 'none';
  form.style.display = 'block';
}


var del_user = "ohayou", anchor = "ecmanaut"; // Fetch tag set and archive posts from Del.icio.us:
writeScript( 'http://del.icio.us/feeds/json/tags/'+ del_user +'/'+ anchor +'?sort=freq&count=100' );
//writeScript( 'http://del.icio.us/feeds/json/'+ del_user +'/'+ anchor +'?count=100' );

function writeScript( src )
{
  document.write( '<script type="text/javascript" src="'+ src +'"><\/script>' );
}

var loggen=[];
function sump(x)
{
  loggen.push(x);
}

// Kicks it all away, eventually pulling in all backlinks listed on the page:
function initiateLoadingBacklinks()
{
  sump( 'initiateLoadingBacklinks() called.' );
  BL_Backlinks.watch( window.maxBacklinkCount = 0, interceptBacklink );
  for( var i=1, tmp; i<postIDs.length; i++ ) // populate backlinks
    if( (BL_BacklinkCount[postIDs[i]] = tmp = $id( postIDs[i] )) )
      tmp.watch( 'innerHTML', gotBacklinkCount );
    else
      debug( i+': '+postIDs[i], 'initiateLoadingBacklinks (no value)', 1 );
  addScript( getCountURL );
}

// Callback fired when any of the #postid nodes gets changed (getLinks script)
function gotBacklinkCount( property, was, got )
{
  sump( 'gotBacklinkCount('+property+','+was+','+got+') called.' );
  backCount[loadCNT.shift()] = got;
  if( !loadCNT.length )
    setTimeout( fetchKnownBacklinks, 100 ); // (just to return quicker)
  return got;
}

// Callback fired when the backCount mapping is fully populated
function fetchKnownBacklinks()
{
  sump( 'fetchKnownBacklinks() called.' );
  for( var postID in backCount )
    if( backCount[postID] > 0 )
      backlinkQueue.push( postID );
  loadBacklinks();
}

// Callback that fires on BL_Backlinks updates. Assumes that it is populated
// fully when it has not been fired within 100 ms its from last execution.
var backlinksTimeout = null;
function interceptBacklink( n, was, got )
{
  sump( 'interceptBacklink('+n+','+was+','+got+') called.' );
  if( backlinksTimeout ) clearTimeout( backlinksTimeout );
  if( n == window.maxBacklinkCount ) // grow our spying glass reach slightly
    BL_Backlinks.watch( window.maxBacklinkCount = n+1, interceptBacklink );
  var postID = backlinkQueue[0];
  if( !n ) backlinks[postID] = [];
  backlinks[postID][n] = got;
  backlinksTimeout = setTimeout( loadBacklinks, 100, postID );
  return got;
}

// Callback that fires when one post's backlinks have been fully populated,
// and when it's time to start fetching the first batch. completedID, when
// passed, is the id of the post we just loaded.
function loadBacklinks( completedID )
{
  sump( 'loadBacklinks('+completedID+') called.' );
  if( completedID )
  {
    renderBacklinks( completedID );
    backlinkQueue.shift();
  }
  if( !backlinkQueue.length ) return; // Done! (-- fire some callback?)
  addScript( getLinks + '&postID=' + backlinkQueue[0] );
}

function addStyles( src )
{
  var link = document.createElement( 'style' );
  link.setAttribute( 'type', 'text/css' );
  link.innerHTML = '@import url("'+ src +'");';
  document.getElementsByTagName( 'head' )[0].appendChild( link );
}

// whenLoaded( function( u ){ return function(){ addScript( u ); }; }( url ) );
function addScript( src )
{
  sump( 'addScript('+src+') called.' );
  var script = document.createElement( 'script' );
  script.setAttribute( 'type', 'text/javascript' );
  script.defer = 'true';
  script.src = src;
  document.getElementsByTagName( 'head' )[0].appendChild( script );
}

var bblPre = 'BlogBacklink', c, lt = '(<|%3C|&lt;)\\$', gt = '\\$(>|%3E|&gt;)';
var bblTerms = 'URL|Title|Snippet|Author|DateTime'.split('|'), bblRe = [];
for( c=0; c<bblTerms.length; c++ )
  bblRe[c] = new RegExp( lt + bblPre + bblTerms[c] + gt, 'mg' );
bblRe.push( new RegExp( lt + bblPre + 'URLEscaped' + gt, 'mg' ) );
function processBacklinkTemplate( template, ecmanautTemplate, links )
{
  sump( 'processBacklinkTemplate(<html>,[size: '+links.length+']) called.' );
  var html = "", tmp, i, j;
  for( i=0; i<links.length; i++ )
  {
    var me = '^http://(ecmanaut|some-assembly-required|intill).blogspot.com/';
    if( links[i].BlogBacklinkURL.match( me ) )
      tmp = ecmanautTemplate;
    else
      tmp = template;
    for( j=0; j<bblTerms.length; j++ )
      tmp = tmp.replace( bblRe[j], links[i][ bblPre + bblTerms[j] ] );
    html += tmp.replace( bblRe[j], escape( links[i][ bblPre+'URL' ] ) );
  }
  return html;
}

function renderBacklinks( postID )
{
  sump( 'renderBacklinks('+postID+') called.' );
  var footer = $id( postID ), count, links, title, parent;
  count = footer.innerHTML;
  title = ' Backlink' + (count==1?'':'s') + ' | ';
  footer.nextSibling.nodeValue = title;
  links = backlinks[postID];

  parent = $id( 'l'+ postID );
  var h4 = document.createElement( 'h4' );
  h4.appendChild( document.createTextNode( 'Pages linking to this post:' ) );
  parent.parentNode.insertBefore( h4, parent );
  //node = document.createElement( 'a' ); // on/off switch for backlinks?
  //node.onclick = function(){};
  var mine = parent.innerHTML.replace( / rel="backlink"/i, '' );
  mine = mine.replace( / class="closed /, ' class="closed-me ' );
  parent.innerHTML = processBacklinkTemplate( parent.innerHTML, mine, links );
  parent.style.display = 'block';
}

if( document.getElementById )
{
  var toggle_opened = new Image, toggle_closed = new Image;
  var mylink_opened = new Image, mylink_closed = new Image;
  toggle_opened.src = 'http://www.blogger.com/img/triangle_open.gif';
  toggle_closed.src = 'http://www.blogger.com/img/triangle.gif';
  mylink_opened.src = jhs+'gfx/me_open.gif';
  mylink_closed.src = jhs+'gfx/me.gif';
  var old_mousedown = document.onmousedown;
  document.onmousedown = function(e)
  {
    var propagate = true;
    var target = window.event ? window.event.srcElement : e.target;
    if( target.className.match( 'backlink-toggler' ) )
    {
      propagate = false;
      var dt = target.parentNode, dd = dt.nextSibling;
      var by = dt.getAttribute( 'rel' ) == 'backlink' ? '' : '-me';
      while( dd.tagName != 'DD' )
        dd = dd.nextSibling;
      if( target.className.match( 'opened' ) )
	target.className = 'backlink-toggler '+ (dd.className = 'closed' + by);
      else
	target.className = 'backlink-toggler '+ (dd.className = 'opened' + by);
    }
    if( typeof window.old_mousedown == 'function' )
      return window.old_mousedown( e );
    return propagate;
  };
}

function $id( id ){ return document.getElementById( id ); }

if( document.addEventListener ) // nice Mozilla event handler:
  document.addEventListener( 'DOMContentLoaded', init, null );
/*@cc_on @*/
/*@if(@_win32)
//document.write('<script defer src="ie_onload.js"><'+'/script>'); // for Internet Explorer
/*@end @*/

if( document.all && !document.getElementsByTagName )
  document.getElementsByTagName = function( nodeName )
  {
    if( nodeName == '*' ) return document.all;
    var result = [], rightName = new RegExp( nodeName, 'i' ), i;
    for( i=0; i<document.all.length; i++ )
      if( rightName.test( document.all[i].nodeName ) )
	result.push( document.all[i] );
    return result;
  };

document.getElementsByClassName = function( className, nodeName )
{
  var result = [], tag = nodeName||'*', node, seek, i;
  var rightClass = new RegExp( '(^| )'+ className +'( |$)' );
  seek = document.getElementsByTagName( tag );
  for( i=0; i<seek.length; i++ )
    if( rightClass.test( (node = seek[i]).className ) )
      result.push( seek[i] );
  return result;
};

// Appends a "Create New Post" link for every "Edit Post" link, last in the same tag,
// and an "Edit Comment" link after every "Delete Comment" link.
function createNew()
{
  // Each of these is either of these link variants
  // http://www.blogger.com/post-edit.g?blogID=15626356&postID=113019832421252315&quickEdit=true
  // http://www.blogger.com/delete-comment.g?blogID=15626356&postID=113026457691336168
  // and should become the corresponding link from these variants:
  // http://www.blogger.com/post-create.g?blogID=15626356
  // http://www.blogger.com/post-edit.g?blogID=15626356&postID=113026457691336168
  var pens = document.getElementsByClassName( 'item-control' );
  var url, edit, parent, node, link, here, i, newlink, editFirstPost, newPost;
  for( i=pens.length-1; i>=0; i-- )
  {
    parent = pens[i];
    edit = parent.getElementsByTagName( 'a' ).item( 0 );
    here = /^([^:]*:..[^\/]+.)([^.]+)([^&]+)(.*)$/.exec( edit.href );
    if( !here ) continue; // shouldn't happen
    link = edit.cloneNode( false );
    switch( here[2] )
    {
      case 'post-edit':
	newPost = link;
	editFirstPost = edit;
	link.href = here[1] +'post-create'+ here[3];
	link.className = 'create-new';
	link.title = 'Create New Post';
	link.appendChild( document.createTextNode( '\240' ) );
	node = document.createElement( 'span' );
	node.className = parent.className;
	node.appendChild( link );
	parent.parentNode.appendChild( node );
	break;
      case 'delete-comment':
	link.href = here[1] +'post-edit'+ here[3] + here[4];
	link.className = 'edit-comment';
	link.title = 'Edit Comment';
	node = document.createElement( 'span' );
	node.className = 'quick-edit-icon';
	node.appendChild( document.createTextNode( '\240' ) );
	link.appendChild( node );
	parent.appendChild( link );
	break;
      default:
	continue; // shouldn't happen
    }
  }
  if( newPost )
  {
    newPost.accessKey = 'N';
    editFirstPost.accessKey = 'E';
  }
}

function fixpng()
{
  for( var I, i=0; i<document.images.length; i++ )
    if( /png$/.test( (I = document.images[i]).src ) )
    {
      I.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+I.src+'",sizingMethod="scale")';
      I.src = jhs+'gfx/e.gif';
    }
}

function linkLanguage( iso, name, title, url, current )
{
  var nodes, a, img, imgurl = (current ? 'full/' : 'tan/') + iso + '.png';
  var base = 'http://translate.google.com/translate?hl=';
  a = document.createElement( 'a' );
  a.appendChild( document.createTextNode( name ) );
  a.className = (current ? 'current ' : '') + 'lang';
  a.target = '_top';
  a.title = title;
  if( !current )
    if( iso == 'en' )
      a.href = url;
    else
      a.href = base+iso+'&ie=UTF-8&oe=UTF-8&langpair=en|'+iso+'&u='+url;
  nodes = { babel:a };
  a = a.cloneNode( false );
  a.className = 'flag';
  img = document.createElement( 'img' );
  img.height = 13;
  img.width = 20;
  img.alt = name;
  img.src = jhs+'gfx/flags/20/' + imgurl;
  if( current )
    img.id = 'current-lang';
  a.appendChild( img );
  nodes.flag = a;
  return nodes;
}

// Offer machine translation to some other languages
function babelfish()
{
  var langs = { en:'English/English', fr:'French/Français',
		de:'German/Deutsch', it:'Italian/Italiano',
		pt:'Portuguese/Português', es:'Spanish/Español', 
		ja:'Japanese/\u65E5\u672C\u8A9E', ko:'Korean/\uD55C\uAD6D',
		'zh-CN':'Chinese/\u88AB\u7B80\u5316\u7684\u6C49\u8BED' };
  var flagbar = document.createElement( 'ul' );
  var babel = $id( 'babel' );
  var about = $id( 'about' );
  var fish = $id( 'fish' ), i;
  var me = location.href.replace( /^.*u=([^&#]*).*/i, '$1' );
  var current = (/[?&]hl=([^&]+)/.exec( location.search ) || {1:'en'})[1];
  fish.onclick = function( event )
  {
    if( babel.style.display == 'block' )
      babel.style.display = 'none';
    else
      babel.style.display = 'block';
  };
  for( i in langs )
  {
    var lang = langs[i].split('/'), title = lang[0];
    if( i == 'en' )
      title = 'Original ' + title;
    else if( i==current )
      title = 'Translated to ' + title;
    else
      title = 'Translate to ' + title;
    var links = linkLanguage( i, lang[1], title, me, i==current );
    var li = document.createElement( 'li' );
    li.className = 'flag';
    li.appendChild( links.flag );
    flagbar.appendChild( li );
    babel.appendChild( links.babel );
  }
  flagbar.id = 'translate';
  about.parentNode.insertBefore( flagbar, about.nextSibling );
}

// Show post `id', hiding all others. Fix onclick handlers of other posts.
function showPost( id )
{
  return function( event )
  {
    var bodies = document.getElementsByClassName( 'post-body', 'div' ), b = {};
    var titles = document.getElementsByClassName( 'post-title', 'h3' ), t = {};
    var atpost = 'post-'+id, i;
    for( i=0; i<bodies.length; i++ ) b[bodies[i].id.substr(5)] = bodies[i];
    for( i=0; i<titles.length; i++ ) t[titles[i].id.substr(6)] = titles[i];
    for( i=0; i<bodies.length; i++ )
    {
      id = bodies[i].id.substr( 5 ); 
      bodies[i].style.display = bodies[i].id == atpost ? 'block' : 'none';
      if( !t[id] ) continue;
      if( bodies[i].id == atpost )
	t[id].onclick = null;
      else if( !t[id].onclick )
	t[id].onclick = showPost( id );
    }
    return true;
    if( event && event.preventDefault )
      event.preventDefault();
    return false;
  };
}

// If showing multiple posts, fold all but the first one.
function hideLater()
{
  var bodies = document.getElementsByClassName( 'post-body', 'div' ), i;
  for( i=0; i<bodies.length; i++ )
  {
    var id = bodies[i].id.substr( 5 );
    var title = $id( 'post-'+id );
    title.onclick = showPost( id );
  }
  showPost( bodies[0].id.substr( 5 ) )();
}

var faces = {}; // profile URL => face node
// Shows faces of Blogger bloggers who commented things.
function showFaces()
{
  var getProfile = 'http://singpolymaplay.ning.com/bloggerProfile.php';
  var comments = document.getElementsByClassName( 'face', 'a' ), i, node;
  var comlinks = document.getElementsByClassName( 'commented-by', 'p' ), url;
  for( i=0; i<comments.length; i++ )
  {
    var by = comlinks[i].getElementsByTagName( 'a' ), all;
    if( (by = by[0] && by[0].href) &&
	by.match( '^http://www.blogger.com/profile/\\d+' ) )
    {
      all = faces[by] || [];
      all.push( comments[i] );
      faces[by] = all;
    }
  }
  for( url in faces )
    addScript( getProfile +'?callback=showFace&url='+ url );
}

function showFace( json )
{
  var nodes = json && faces[json.url], node, img, face, url, link;
  if( !nodes || !(face = json.photo) ) return;
  img = new Image;
  img.style.width = /*img.style.height =*/ '0px';
  img.height = face.height;
  img.width = face.width;
  img.className = 'face-'+ /\d+$/i.exec( json.url )[0];
  img.src = face.url;
  while( link = (json.contact||[]).shift() )
    if( link.text.toLowerCase() == 'my web page' )
      url = link.href;
  var redirect = 'http://www.blogger.com/r?';
  if( !url.indexOf( redirect ) )
    url = url.substring( redirect.length );
  while( node = nodes.shift() )
  {
    node.appendChild( img.cloneNode( false ) );
    if( url ) node.href = url;
  }
  $( 'img.'+img.className ).load( function()
  {
    var self = this;
    $( self ).showCustom( 'slow' );
    this.parentNode.style.display = 'block';
  } );
}

$.fn.showCustom = function( a, o )
{
  o = $.speed( a, o );
  return this.each( function()
  {
    (new fx.Opacity( this, o )).show();
    (new fx.Width( this, o )).custom( 0, this.getAttribute( 'width' ) );
  });
};

// Don't show comments, unless there are any.
function noComment()
{
  var comments = document.getElementsByClassName( 'comment', 'div' ), i, j;
  var comlinks = document.getElementsByClassName( 'comment-link', 'span' );
  for( i=0; i<comments.length; i++ )
    if( /^0 /.test( comments[i].className ) ) // no comments yet
      comments[i].style.display = 'none'; // hide (non-)comments
    else if( comlinks[i] ) // we have one or more comments below
    {
      comlinks[i].style.display = 'none'; // hide "<N> comments"
      var entries = comments[i].getElementsByTagName( 'li' ), a, p;
      for( j=0; j<entries.length; j++ ) // find and class-mark my comments
	if( (p = entries[j].getElementsByTagName( 'p' )).length &&
	    (a = p[p.length-1].getElementsByTagName( 'a' )).length )
	  {
	    if( a[0].href == blogowner )
	      entries[j].className = 'me';
	    if( a[1].className == 'c-permalink' )
	    { // make these proper comment permalinks, on index and archive pages too:
	      a[1].href = a[1].parentNode.parentNode.parentNode.className + a[1].hash;
	      a[1].title = 'permalink to this comment';
	    }
	  }
    }
}

function trackbacks( id )
{
  var n = hstb[id];
  document.write( n ? n + ' Trackback' + (n==1?'':'s')
		    : 'Trackback URI' );
}

// in case your browser is crummy and doesn't grok CSS 2 selectors:
function fixPlural()
{
  var ua = navigator.userAgent, plurals, i, node;
  // tweak <span class="plural" name="N">N Comment</span> blocks?
  if( /MSIE [0-6]/.test( ua ) && !/opera/i.test( ua ) )
  {
    plurals = document.getElementsByTagName( 'span' );
    for( i=0; i<plurals.length; i++ )
      if( /plural/.test( (node=plurals[i]).className ) && node.name != 1 )
      {
        node.innerHTML += 's';
	// and just in case this browser does support CSS2, toss the class
        node.className = node.className.replace( /plural/, '' );
      }
  }
}

function addBlogAd()
{
  //alert( 'loaded in '+(((new Date).getTime()-t)/1e3) +'s' );
  var f = $id( 'flagButton' );
  if( f ) f.parentNode.removeChild( f ); // make some room
  var a = $id( 'ad2' ); // second ad div
  var b = $id( 'b-this' ); // BlogThis
  while( a.childNodes.length )
  {
    a.removeChild( f = a.firstChild );
    //alert( f.nodeType +':'+ f.nodeName );
    if( f.nodeName == 'IFRAME' )
    {
      var fadeTime = 1400; // ms
      self.fadeDelay = 1e3/80;
      self.fadeSteps = Math.floor( fadeTime/self.fadeDelay );
      f.style.cssText = 'position:absolute; top:5px; left:436px;';
      self.fadeStep = self.fadeSteps;
      fadeIn( f );
    }
    b.appendChild( f );
  }
}

function fadeIn( e )
{
  try {
    var pct = (self.fadeSteps - self.fadeStep) / self.fadeSteps;
    e.style.opacity=e.style['-moz-opacity'] = pct;
    e.style.filter = 'alpha(opacity='+ Math.floor(100 * pct) +')';
    if( --self.fadeStep  )
      setTimeout( function(){ fadeIn( e ); }, self.fadeDelay );
  } catch( e ) {};
}

// Create a date object from an ISO YYYY-MM-DD date present in ${ymd} HTML
function getDate( ymd )
{
  ymd = /(\d{4})-(\d\d)-(\d\d)/.exec( ymd );
  if( ymd )
    return new Date( parseInt( ymd[1], 10 ),
		     parseInt( ymd[2], 10 )-1,
		     parseInt( ymd[3], 10 ) );
}

// Creates prev, next and archive page links where and as appropriate, and
// gathers those few post titles seen in this page (far from all, though).
//no:(a.date-header tags get their /YYYY_MM_DD_${blogName}_archive.html URLs.)
// a.prev-post and a.next-post tags should get prev/next links.
function getTitles( notelist, firstSeen, lastSeen, blogName )
{
  if( typeof firstSeen != 'number' ) return; // (stupid Google translate!!!)
  // For latin derived languages, GT exchanges places for next-post and date

  // "anchor" tags have the attributes name:postid, and href:permalink. On
  // first-post-of-day there is an id:YYYY-MM-DD attribute as well. The id
  // of the post (name attribute) is used to find the post title by nodeid
  var urls = document.getElementsByClassName( 'anchor', 'a' ), post, date;
  var prev = document.getElementsByClassName( 'prev-post', 'a' ), P, N, i;
  var next = document.getElementsByClassName( 'next-post', 'a' ), t, days;

  var titles = {}; // compile a list of all post titles, per archive dates
  next[0].id = 'next-post'; // we will add accessKeys to these two as well
  prev[prev.length-1].id = 'prev-post';
  for( post=0, i=0; post<next.length; i++ )
  {
    if( urls[i].id ) date = urls[i].id; // (on good ISO YYYY-MM-DD format)
    t = getDate( date ).getTime();
    titles[t] = (titles[t] || []).concat( $id('title-'+urls[i].name).innerHTML );
    if( (N = next[post+1]) )
    {
      N.title = date;
      N.href = '#' + (urls[i].id || urls[i].name); // use the nicer anchor
      // N.onclick = showPost( urls[i].name );
    }
    if( post )
    {
      P = prev[post-1];
      P.title = date;
      P.href = '#' + (urls[i].id || urls[i].name); // use the nicer anchor
      // P.onclick = showPost( urls[i].name );
    }
    post++;
  }

  var accessKey = 'Alt+'; // let's find out a helpful usability hint title
  if( navigator.userAgent.match( /opera/i ) )
    accessKey = 'Shift+Esc followed by ';
  else if( navigator.userAgent.match( /macintosh/i ) )
    accessKey = 'Control+';

  if( firstSeen > 0 ) // no back link, if this is the first ever blog post
  {
    node = prev.pop(); // last entry in the prev array; final post on page
    node.href = self.posts[self.notelist[firstSeen-1]];
    date = node.href.replace( /.*(\d{4})_(\d\d)_(\d\d).*/, '$1-$2-$3' );
    node.accessKey = '\074'; // less-than
    node.title = date +' (Hotkey: '+ accessKey +'\074)';
  }
  if( notelist.length > lastSeen+1 ) // were there any later posts at all?
  {
    node = next[0];
    node.href = self.posts[self.notelist[lastSeen+1]];
    date = node.href.replace( /.*(\d{4})_(\d\d)_(\d\d).*/, '$1-$2-$3' );
    node.accessKey = 'Z';
    node.title = date +' (Hotkey: '+ accessKey +'Z)';
  }

/* outdated (when I decided on making them <span> tags instead)
  days = document.getElementsByClassName( 'date-header', 'a' );
  for( i=lastSeen; i<=firstSeen; i++ )
    days[i-lastSeen].href = self.posts[i];
*/
  if( !self.titles )
    self.titles = titles;
}

// Build up a dataset of all posts and their permalinks, anchors and dates
// as best we can, for the Calendar to use. Titles, too, as far as we can.
function harvester()
{
  var archive = $id('archive'); if( !archive ) return debug('archive',harvester);
  var dates = document.getElementsByClassName( 'date-header', 'span' );
  var i, t, date, date1, dateN, node, firstSeen, lastSeen;
  for( i=0; i<dates.length; i++ )
  {
    dateN = dates[i].innerHTML; // pick up the last date shown on the page
    dateN = dateN.substr(0,10); // (and cut away any possible time parts),
    if( !date1 ) date1 = dateN; // and store the first date shown here too
  }

  self.notelist = []; // all archive link timestamps in chronological order
  var links = archive.getElementsByTagName( 'a' ), posts = {}, names = {};
  for( i=0; i<links.length; i++ )
  {
    node = links[i];
    date = node.innerHTML.replace( /[^-0-9]/g, '' ); // translation robustness
    if( date == date1 )
      lastSeen = i; // we are visiting the i:th page (of the archive list)
    if( date == dateN )
      firstSeen = i; // and we see all pages up to (and including) lastSeen
    t = getDate( date ).getTime();
    posts[t] = node.href; // posts[timestamp] => archivelink, and the date
    self.notelist.push(t); // archive page N dates from timestamp T onwards
  }
  i--; // i now points to the last note in self.notelist
  self.posts = posts;
  getTitles( self.notelist, firstSeen, lastSeen, 'ecmanaut' );
  self.thisDate = date1;
  self.dateBounds = [new Date(self.notelist[0]), new Date(self.notelist[i])];
  //harvestDelicious(); // called before init(), to catch first Delicious.posts
}

// extend self.titles and self.posts from Delicious.posts
// archive URL format - /YYYY_MM_DD_ecmanaut_archive.html
function harvestDelicious()
{
  var i, t, u, y, m, d, post, time, times = [], titles = {}; // time:[titles]
  for( i=0; i<Delicious.posts.length; i++ )
  {
    post = Delicious.posts[i];
    time = post.n.substr( 0, 16 );
    times.push( time );
    titles[time] = post.d;
  }
  times = times.sort(); // small to greater
  if( !self.titles ) self.titles = {};
  for( i=0; i<times.length; i++ )
  {
    t = /^(\d{4})-(\d\d)-(\d\d)/.exec( time = times[i] );
/*  u = ['http://ecmanaut.blogspot.com/'+t[1], t[2], t[3],
	 'ecmanaut_archive.html'].join( '_' ); // as below */
    y = parseInt( t[1], 10 );
    m = parseInt( t[2], 10 );
    d = parseInt( t[3], 10 );
    t = (new Date( y, m - 1, d, 0, 0, 0 )).getTime();
    // self.posts[t] = u; // not needed when scraping archive
    titles[t] = (titles[t] || []).concat( titles[time] );
    delete titles[time];
  }
  for( t in titles )
    if( !self.titles[t] || titles[t].length > self.titles[t].length )
      self.titles[t] = titles[t];
}

// Initialize and render the calendar
function renderCal()
{
  if( typeof Calendar == 'undefined' ) return setTimeout( renderCal,2e3 );
  var archive = $id('archive');
  if( archive )
    archive.style.display = 'none';
  else
    return debug( 'no archive!', 'renderCal' );
  try { Calendar.setup(
  {
    step          : 1, // show every year in the year menus
    date          : getDate( self.thisDate ), // selected by default
    flat          : 'calendar-container', // div element
    range         : self.dateBounds,
    showOthers    : true, // show whole first/last week of month
    dateFooter    : showNoteTitle, // a nicer date hover text
    pickPrevNext  : true, // navigation to closest date of unit
    flatCallback  : dateChanged, // what to do on date selection
    dateStatusFunc: disableDateP // which dates to show/hide how
  }); }
  catch(e) {debug(e, arguments.callee);}
}

function showNoteTitle( date )
{
  try{
  var titles = self.titles[date.getTime()];
  return titles && titles.join(',<br />');
  }catch(e){ return debug(e, arguments.callee); }
}

// Returns true for all dates lacking a note, false or a css style for those having one.
// Exception: today does not return true, even if it lacks a note. (improves navigation)
function disableDateP( date, y, m, d )
{
  try{
  var now = new Date;
  if( (y == now.getFullYear()) &&
      (m == now.getMonth()) &&
      (d == now.getDate()) )
    return false;
  return archiveURLFromDate( date ) ? false : true;
}catch(e){debug(e, arguments.callee);}

}

function archiveURLFromDate( date )
{
  try{
  return self.posts[date.getTime()];
}catch(e){debug(e, arguments.callee);}
}

function dateChanged( calendar )
{
  try{
  if( calendar.dateClicked )
  {
    var url = archiveURLFromDate( calendar.date );
    if( url )
      self.location = url;
  }
}catch(e){debug(e, arguments.callee);}
}

function getScreenCoordinates( node )
{
  var x = node.offsetLeft;
  var y = node.offsetTop;
  while( (node = node.offsetParent) )
  {
    x += node.offsetLeft;
    y += node.offsetTop;
  }
  return { x:x, y:y };
}

function clustrMap()
{
  var map = $id( 'clustrmap' );
  var url = 'http://clustrmaps.com/stats/maps-clusters/';
  var get = /url(=|%3D)[^:]*:\/\/([^&]+)*/i;
  var end = '-world.jpg';
  var big = $id( 'bigmap' );
  var dim = { w:800, h:300 };
  map.onmouseover = function( event )
  {
    var link = (event ? event.target : window.event.srcElement).parentNode;
    link = get.exec( link.href )[2].replace( /\//g, '-' );
    var me = url + link + end;
    var at = getScreenCoordinates( map );
    big.style.border = '1px outset #888';
    big.style.top = (at.y - dim.h - 17) + 'px';
    big.style.left =(at.x - dim.w + map.width + 67) + 'px';
    big.style.background = '#1E1E50 url("'+ me +'") no-repeat';
    big.style.display = 'block';
  };
  map.onmouseout = function(){ big.style.display = 'none'; };
}

function list_side_tags() {
  var tags = [], n;
  for( var tag in Delicious.tags ) {
    n = Delicious.tags[tag];
    if( n < 10 ) n = '0'+n;
    if( n < 100) n = '0'+n;
    tags.push( n+':'+tag );
  }
  tags.sort();

  var html = '<select onchange="location=&quot;http://del.icio.us/ohayou/ecmanaut+&quot;+this.value">';
  while( tag = tags.pop() ) {
    tag = tag.split(':');
    n = parseInt( tag[0], 10 );
    tag = tag[1];
    html += '<option value="'+tag+'">'+tag+' ('+n+')</option>\n';
  }
  document.write( html + '</select>' );
}
