// SCHILLMANIA! | 2004 edition - Now with more funk!
// -------------------------------------------------
// Code by Scott Schiller | Version 2.0.20041024
// default.js: core site functionality and objects
// -------------------------------------------------
// Core objects:
// 
// ConfigManager()  - tracks configuration changes
// ContentManager() - loads and displays article/entry content (if supported)
// FilterManager()  - sets or resets filters applying to entries under the navigation
// BannerManager()  - handles banner requests and provides banner display functions
// ScrollManager()  - handles mouse events and banner scrolling (child of BannerManager)
// SoundManager()   - Provides sound effects which can be hooked into scripted events

// A side note: Legibility and ease-of-maintenance vs. file size trade-off in effect. ;)

var columns = [];
var columnWidths = [200,256,256]; // for IE min-width fix
var shadows = [];
var content = [];
var timeStart = new Date();
var tabs = [];
var tabContent = [];
var tabLast = 0;
var tabCurrent = 0;
var soundManager = null;
var nodes = []; // getElementsByClassName: default nodes to check
var configManager = null;
var bannerManager = null;
var scrollManager = null;
var contentManager = null;
var filterManager = null;

// debug mode (to enable, add ?debug=true to URL)
var DEBUG = (window.location.toString().indexOf('debug=')+1);

// basic ie:mac detection (originally referenced by WebPad)
var naV = navigator.appVersion.toLowerCase();
var isIE = naV.indexOf('msie')+1;
var isMac = naV.indexOf('mac')+1;

function extremeCounter() {
/*
  var an = navigator.appName;
  var s = screen;
  var d = document;
  var srw = s.width;
  var srb = (an!='Netscape')?s.colorDepth:s.pixelDepth;
  var src = 'http://v0.extreme-dm.com/0.gif?tag=scott01&j=y&srw='+srw+'&srb='+srb+'&rs=41&l='+escape(d.referrer);
  var img = new Image();
  img.src = src;
  writeDebug('extremeCounter(): '+src);
*/
}

function getColumns() {
  writeDebug('getColumns()');
  columns = getElementsByClassName('column','div');
  shadows = getElementsByClassName('shadowRight ie','div');
  for (var i=0; i<columns.length; i++) {
    columns[i].alt = i; // ALT reference for shadowExpression index
    if (pngHandler.isIE) columns[i].style.overflow = 'hidden'; // workaround for IE overflow/shadow width hang problem
    content[i] = getElementsByClassName('content','div',columns[i])[0];
  }
}

function shadowExpression(o) {
  // One of the weirdest IE-only implementations I've ever seen.
  // Inspiration source: svendtofte.com/code/max_width_in_ie/
/*
  o = o.parentElement.parentElement;
  s = o.getElementsByTagName('div')[2];
  if (this.scrollHeight>1600) return false;
  return ((s.scrollWidth >= columnWidths[o.alt]?s.scrollHeight:this.scrollHeight)+'px');
*/
}

function setTab(tab) {
  writeDebug('setTab('+tab+')');
  if (tabContent[tabLast]) {
    tabContent[tabLast].style.display = 'none';
    tabs[tabLast].className = (tabLast==0?'first ':(tab==tabs.length-1?'last ':''))+'tab';
  }
  tabLast = tab;
  if (tabContent[tab]) {
    tabContent[tab].style.display = 'block';
    tabs[tab].className = (tab==0?'first ':(tab==tabs.length-1?'last ':''))+'tab active';
  }
  tabCurrent = tab;
  soundManager.play(null,'mouseover1');
  // soundManager.play('rc','background'+parseInt(Math.random()*3));
}

function getTabs() {
  writeDebug('getTabs()');
  var oTabs = document.getElementById('tabs');
  var oNav = document.getElementById('col0');
  tabs = oTabs.getElementsByTagName('a');
  for (var i=0; i<tabs.length; i++) {
    tabContent[i] = getElementsByClassName('container '+tabs[i].href.toString().substr(tabs[i].href.toString().indexOf('#')+1),'div',oNav)[0];
  }
}

function setDefaultTab() {
  // setTab(0);
  // Determine current tab based on URL (eg. content/react)
  writeDebug('setDefaultTab()');
  var loc = window.location.toString().toLowerCase();
  var match = false;
  var path = null;
  for (var i=0; i<tabs.length; i++) {
    path = tabs[i].href.substr(tabs[i].href.indexOf('#')+1);
    if (path == 'news') path = 'entries';
    if (loc.indexOf('content/'+path)+1) {
      match = true;
      setTab(i);
      return true;
    }
  }
  if (!match) {
    // assume default
    setTab(0);
    setEntryHighlight(0);
    return false;
  }
}

function findEntryHighlight() {
  // match entry item within current tab section based on URL
  writeDebug('findEntryHighlight()');
  var loc = window.location.toString().toLowerCase();
  var items = tabContent[tabCurrent].getElementsByTagName('a');
  var match = false;
  for (var i=0; i<items.length; i++) {
    if (loc.indexOf(items[i].href)+1) {
      setEntryHighlight(i);
      match = true;
    }
  }
}

function setEntryHighlight(i) {
  writeDebug('setEntryHighlight('+i+')');
  var item = tabContent[tabCurrent].getElementsByTagName('a')[i];
  if (contentManager.oLast) {
    // reset last active object
    // need a better (single) regExp here.
    contentManager.oLast.className = contentManager.oLast.className.replace(/ selected/,'');
    contentManager.oLast.className = contentManager.oLast.className.replace(/selected/,'');
  }
  item.className = (item.className?item.className+' ':'')+'selected';
  contentManager.oLast = item; // reference for future clicks
}

var cssPaths = [];
cssPaths['Washed-Out'] = 'washed-out';
cssPaths['Inverted'] = 'inverted';
cssPaths['Paper Bag'] = 'paper_bag';
cssPaths['Tech V1'] = 'tech';
cssPaths['Tech V2'] = 'techv2';
cssPaths['Hallowe\'en'] = 'pumpkin';
cssPaths['Steel'] = 'steel';

function whatIs(something) {
  var result = '';
  for (var stuff in something) {
    result += stuff+' | ';
  }
  alert(result);
}

function setTheme(title) {
  if (title == 'Tech') title = 'Tech V2'; // legacy update
  writeDebug('setTheme('+title+')');

  var cssTmp = getStylesheet(); // document.getElementsByTagName('head')[0].getElementsByTagName('link') returns empty array under Opera. ?
  var css = [];
  var match = false;
  var tOld = null;
  var tNew = null;

  if (!cssTmp.length) return false; // opera can't find CSS?

  for (var i=0; i<cssTmp.length; i++) {
    // cssTmp[i].alternate=='yes' // apparently "alternate" as defined in XML stylesheet isn't accessible from Javascript?
    if (APP_XHTML?i>1:((cssTmp[i].rel||cssTmp[i].owningElement.rel) == 'alternate stylesheet')) {
      css[css.length] = cssTmp[i];
    }
  }

  if (!css.length) {
    writeDebug('setTheme(): Warning - could not find alternate stylesheets');
    return false;
  }

  for (var i=0; i<css.length; i++) {
    if (css[i].title == title) {
      css[i].disabled = false;
      tNew = cssPaths[css[i].title];
      match = true;
    } else {
      if (!css[i].disabled) {
        tOld = cssPaths[css[i].title];
      }
      css[i].disabled = true;
    }
  }

  if (title == 'none') {
    for (var i=0; i<cssTmp.length; i++) {
      cssTmp[i].disabled = true;
    }
    return false;
  } else if (!match) {
    // no theme found - set default
    setTheme('Tech V2');
    // setTheme('Hallowe\'en');
  }

  // hack: swap out PNG images (some shadows vary by theme.)
  if (tOld) {
    // apply only after initial set
    pngHandler.themeSwitch(tOld,tNew);
  }

  configManager.setPreference('theme',title);

}

function setStyle(s) {
  writeDebug('setStyle('+s+')');
  getStylesheet(2).disabled = (s=='Relative'?false:true);
  if (!pngHandler.isIE) {
    try {
      // Mozilla redraw fix
      var tmp = getElementsByClassName('content','div',document.getElementById('col1'))[0];
      tmp.style.display = 'none';
      tmp.style.display = 'block';
    } catch(e) {
      // oh well
    }
  }
  configManager.setPreference('style',s);

}

function getStylesheet(n) {
  var o = document.styleSheets?document.styleSheets:document.getElementsByTagName('link');
  return (typeof(n)=='undefined')?o:o[n];
}

function disableAllThemes(exception) {
  // exception: index to exclude from disabling
  writeDebug('disableAllThemes('+(exception||'')+')');
  if (typeof(exception)=='undefined') exception = -1;
  var css = getStylesheet();
  for (var i=0; i<css.length; i++) {
    if (css[i].href.indexOf('theme')+1 && i != exception) {
      css[i].disabled = true;
    }
  }
}

function setDefaultTheme() {
  if (configManager.data['theme'] == 'Hallowe\'en') configManager.data['theme'] = 'Tech V1'; // wacky override
  setTheme(configManager.data['theme']);
}

function setDefaultStyle() {
  setStyle(configManager.data['style']);
}


function ConfigManager() {
  // handles user preferences via the loathed cookie (not an evil one, mind you) approach.
  writeDebug('ConfigManager()');
  var self = this;
  this.data = [];
  this.dataNames = [];
  this.delimiter = '&'; // used to be '&' - reinvigorate appears to use '; '.
  this.expireOffset = new Date(new Date().getTime()+(365*24*60*60*1000)).toGMTString(); // 1 year's worth of milliseconds
  this.modified = false;

  this.save = function() {
    // update expiry time, build string from object and write
    if (!this.modified) return false; // don't save if nothing was changed
    tmpData = '';
    tmpParams = '';
    for (var i=0; i<self.dataNames.length; i++) {
      tmpData += self.dataNames[i]+'='+escape(self.data[self.dataNames[i]])+(i<self.dataNames.length-1?self.delimiter:'');
    }
    if (window.location.href.indexOf('debugCookies=1')+1) alert('setting cookie:\n'+tmpData+'; domain='+(window.location.toString().toLowerCase().indexOf('schillmania.com')+1?'schillmania.com':'192.168.0.100')+'; path=/; expires='+self.expireOffset);
    document.cookie = (tmpData+'; domain='+(window.location.toString().toLowerCase().indexOf('schillmania.com')+1?'schillmania.com':'192.168.0.100')+'; path=/; expires='+self.expireOffset);
  }

  this.setPreference = function(name,value) {
    if (!self.modified) self.modified = true;
    if (typeof(self.data[name])=='undefined' && name!='u' && name!='mainWindowLeft') { // excluding reinv. data
      self.dataNames[self.dataNames.length] = name;
    }
    self.data[name] = value;
  }

  this.undefined = function(name) {
    return typeof(self.data[name]=='undefined');
  }

  // constructor: set defaults, get any custom data

  tmpString = document.cookie?document.cookie.split(this.delimiter):[];
  if (tmpString.length) {
    for (var i=0; i<tmpString.length; i++) {
      tmpData = tmpString[i].split('=');
      if (typeof(tmpData) == 'object' && tmpData[0]) {
        if (tmpData[0] != 'path' && tmpData[0] != 'domain' && tmpData[0] != 'expires') {
          this.setPreference(tmpData[0],unescape(tmpData[1]),true);
        }
      }
    }
    // window.status = 'configManager(): preferences loaded.';
  } else {
    // no cookie data
    // window.status = 'configManager(): no preferences.';
  }
  // setTimeout("window.status=''",2000);

  if (typeof(this.data['theme'])=='undefined') {
    this.setPreference('theme','Tech V2'); // set default
  }
  if (typeof(this.data['style'])=='undefined') {
    this.setPreference('style','Fluid'); // set default
  }

}

function ContentManager() {
  writeDebug('ContentManager()');
  var self = this;
  this.xmlhttp = null;
  this.sections = [];
  this.oLast = null;

  try {
    this.xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
  } catch(e) {
    try {
      this.xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
    } catch(E) {
      this.xmlhttp = null;
    }
  }
  if (!this.xmlhttp) {
    try {
      this.xmlhttp = new XMLHttpRequest();
      this.xmlhttp.overrideMimeType('text/xml'); // So mozilla doesn't crash
    } catch(e) {
      this.xmlhttp = null;
    }
  }

  this.readystatechangeHandler = function() {
    if (self.xmlhttp.readyState == 4) {
      if (self.onloadHandler) {
        self.onloadHandler();
      }
    }
  }

  this.load = function(url,className) {
    url = url.toString();
    // writeDebug('ContentManager.load(url+','+className+')');
    if (self.xmlhttp) {
      try {
        target = getElementsByClassName('content','div',document.getElementById('col1'))[0];
        dC = document.createElement('div');
        dC.className = 'top header';
        dCH = document.createElement('h1');
        dCH.appendChild(document.createTextNode('Loading'));
        dC.appendChild(dCH);
        dCD = document.createElement('div');
        dCD.className = 'divider';
        dC.appendChild(dCD);
        dCC = document.createElement('div');
        dCC.className = 'copy';
        dCP = document.createElement('p');
        dCP.appendChild(document.createTextNode('Please wait...'));
        dCC.appendChild(dCP);
        dC.appendChild(dCC);
        removeChildNodes(target);
        target.appendChild(dC);
        // '<div class="top header"><h1>Loading</h1><div class="divider"></div></div><div class="copy"><p>Please wait...</p></div>';
        url = url.toString();
        if (APP_XHTML||(window.location.href.indexOf('test')+1)) {
          file = 'index.php?xml=true&r='+parseInt(Math.random()*1048576);
        } else {
          file = 'content.html';
        }
        // window.status = url+'/'+file;
        self.xmlhttp.open('GET',url+'/'+file,true); // "true" - don't hold up browser
        self.xmlhttp.onreadystatechange = self.readystatechangeHandler;
        self.xmlhttp.setRequestHeader('Content-Type', 'text/xml');
        self.xmlhttp.send(null); // xmlDoc
        bannerManager.setRelatedBanner(className);
      } catch(e) {
        // something blew up - d'oh!
        return true;
      }
    } else {
      // no XMLHTTP support.
      return true;
    }
    return false;
  }

  this.onloadHandler = function() {
    writeDebug('ContentManager.onloadHandler()');
    c = getElementsByClassName('content','div',document.getElementById('col1'))[0];
    removeChildNodes(c);
    if (APP_XHTML) {
      c.appendChild(document.importNode(self.xmlhttp.responseXML.documentElement.getElementsByTagName('div')[0],true));
    } else {
      // Internet explorer etc. get to do it the non-standards way
      c.innerHTML = self.xmlhttp.responseText;
    }
    // evaluate any embedded script calls
    js = c.getElementsByTagName('script');
    for (i=0; i<js.length; i++) {
      eval(js[i].innerHTML);
    }
    // transform PNGs as necessary within content
    if (pngHandler.transform) {
      pngs = getElementsByClassName('png',['div','img'],c);
      for (i=0; i<pngs.length; i++) {
        pngHandler.transform(pngs[i]);
      }
    }
  }

  this.assignHandlersToClass = function(c,tagName) {
    // intercept onClick and load via XMLHTTP where supported
    writeDebug('ContentManager.assignHandlersToClass('+c+','+tagName+')');
    var containers = getElementsByClassName(c,tagName);
    var i,j,links;
    writeDebug('ContentManager.assignHandlersToClass(): found '+containers.length+' containers');
    for (i=0; i<containers.length; i++) {
      // hide container contents by default
      links = containers[i].getElementsByTagName('a');
      for (j=0; j<links.length; j++) {
        links[j].id = 'entryItem'+i+'-'+j; // highlight index reference
        links[j].onmouseover = this.mouseoverHandler; 
        links[j].onclick = this.clickHandler;
      }
    }
  }

  this.clickHandler = function(o) {
    writeDebug('ContentManager.clickHandler('+o+')');
    o = (o&&o.href?o:this); // event firing either direct assignment or .clickHandler(this) - test for .href property as Mozilla transparently passes event argument by default
    soundManager.play(null,'click0');
    setEntryHighlight(o.id);
    o.blur();
    return self.load(o,o.className);
  }

  this.mouseoverHandler = function() {
    soundManager.play(null,'mouseover2');
  }

}

function FilterManager() {
  writeDebug('FilterManager()');
  var self = this;
  this.currentFilter = null;

  containers = getElementsByClassName('container','div');
  tmp = [];
  this.entries = [];
  this.entriesLI = [];
  this.keywordFilter = getElementsByClassName('keywordFilter','a')[0];
  this.keywordFilters = [];
  this.keywordFiltersDefault = [];
  keywordContainers = getElementsByClassName('keywords',['span','div'],this.keywordFilter);

  this.onclickHandler = function() {
    if (self.currentFilter != this.className) {
      self.applyFilter(this.className.replace(' hover','')); // replace "hover" if applicable (may be highlighted as a result of mouseover class name change)
      return false;
    }
  }

  this.onmouseoverHandler = function() {
    this.className = self.keywordFiltersDefault[this.index]+' hover';
  }

  this.onmouseoutHandler = function() {
    this.className = self.keywordFiltersDefault[this.index];
  }

  this.applyFilter = function(fC) {
    writeDebug('FilterManager().applyFilter('+fC+')');
    this.currentFilter = fC;
    this.keywordFilter.className = 'keywordFilter '+this.currentFilter;
    for (i=0; i<this.entries.length; i++) {
      this.entriesLI[i].style.display = (this.entries[i].className.indexOf(fC)+1)||(fC=='default')?'block':'none';
    }
  }

  // get entry items
  for (i=0; i<containers.length; i++) {
    containers[i].style.display = 'none'; // hide container sections by default
    tmp[i] = containers[i].getElementsByTagName('a');
  }
  for (i=0; i<tmp.length; i++) {
    for (j=0; j<tmp[i].length; j++) {
      this.entries[this.entries.length] = tmp[i][j];
      this.entriesLI[this.entriesLI.length] = tmp[i][j].parentNode; // grab LI
      
    }
  }

  // get keyword filter items
  count = 0;
  for (i=0; i<keywordContainers.length; i++) {
    tmp = keywordContainers[i].getElementsByTagName('span');
    for (j=0; j<tmp.length; j++) {
      tmp[j].index = count++;
      tmp[j].onclick = this.onclickHandler;
      tmp[j].onmouseover = this.onmouseoverHandler;
      tmp[j].onmouseout = this.onmouseoutHandler;
      this.keywordFilters[this.keywordFilters.length] = tmp[j];
      this.keywordFiltersDefault[this.keywordFiltersDefault.length] = tmp[j].className;
    }
  }

}

function BannerManager() {
  writeDebug('BannerManager()');
  var self = this;
  this.o = null;
  this.banners = []; // banner objects (instantiated from XML-sourced JS)
  this.currentBannerIndex = null;
  this.currentBanner = null; // current banner object reference
  this.keywords = [];
  this.cover = null;
  this.currentKeywords = null;

  this.Cover = function(o) {
    writeDebug('BannerManager.Cover()');
    var self = this;
    this.o = o;
    this.onComplete = function(){}; // callback function
    this.state = 0; // closed(0)/open(1)
    this.tweenIndex = 0;
    this.tween = [0,1,3,6,10,15,21,28,36,45,55,64,72,79,83,88,94,97,99,100];
    this.tweenX = [];
    this.tweenY = [];

    this.createTween = function() {
      // create tween dynamic to container width/height
      bannerWidth = parseInt(bannerManager.o[1].offsetWidth); // container width
      bannerHeight = 128; // height is always constant
      for (i=0; i<self.tween.length; i++) {
        self.tweenX[i] = parseInt(bannerWidth*self.tween[i]/100)+'px';
        self.tweenY[i] = parseInt(bannerHeight*self.tween[i]/100)+'px';
      }
    }

    this.show = function() {
      self.o.style.display = 'block';
    }

    this.hide = function() {
      self.o.style.display = 'none';
    }

    this.hideImage = function() {
      writeDebug('BannerManager.Cover.hideImage()');
      self.state = 1;
      self.createTween();
      soundManager.play(null,'swish1');
      self.show();
      for (i=0; i<self.tween.length; i++) {
        setTimeout("bannerManager.cover.moveTo("+i+")",(i+1)*20);
      }
      if (self.onComplete) {
        setTimeout("bannerManager.cover.onComplete();bannerManager.cover.onComplete=function(){}",(i+1)*20);
      }
    }

    this.moveTo = function() {
      // reassigned to one of below at random
    }

    this.moveToX = function(i) {
      self.tweenIndex = i;
      self.o.style.width = self.tweenX[i];
    }

    this.moveToY = function(i) {
      self.tweenIndex = i;
      self.o.style.height = self.tweenY[i];
    }

    this.setAnimationStyle = function() {
      // determine animation style (left -> right or top -> bottom)
      var r = (Math.random()>0.5);
      self.o.style.width = r?parseInt(bannerManager.o[1].offsetWidth)+'px':'0px';
      self.o.style.height = r?'0px':'128px';
      self.moveTo = r?self.moveToY:self.moveToX;
    }

    this.showImage = function() {
      writeDebug('BannerManager.Cover.showImage()');
      self.state = 0;
      self.createTween();
      soundManager.play(null,'swish0');
      for (i=0; i<self.tween.length; i++) {
        setTimeout("bannerManager.cover.moveTo("+(self.tween.length-i-1)+")",(i+1)*20);
      }
      setTimeout("bannerManager.cover.hide()",(i+1)*20);
      if (self.onComplete) {
        setTimeout("bannerManager.cover.onComplete();bannerManager.cover.onComplete=function(){}",(i+1)*20);
      }
    }

  }

  this.init = function() {
    writeDebug('BannerManager.init()');
    this.cover = new this.Cover(document.getElementById('banner-cover'));
    this.o = [document.getElementById('banner')]; // grab objects after load
    this.o[1] = this.o[0].getElementsByTagName('div')[0];
    this.oImg = document.createElement('img'); // for script-based event handling
    this.oImgTile = document.createElement('img');
  }

  this.add = function() {
    i = this.banners.length;
    this.banners[i] = new Object();
    this.banners[i].index = i;
    this.banners[i].keywords = ''; // array of keyword array index values
    for (j=0; j<arguments.length; j++) {
      this.banners[i].keywords += this.keywords[arguments[j]]+(j<arguments.length-1?' ':'');
    }
    this.banners[i].properties = [];
  }

  this.addProperties = function(name,value) {
    this.banners[this.banners.length-1].properties[name] = value; // add property/value to current banner
  }

  this.getRandomBanner = function() {
    // update later to exclude screenshots?
    return parseInt(Math.random()*this.banners.length);
  }

  this.setBanner = function(n) {
    // cover image -> load banner image -> set banner image -> uncover image
    // doesn't work in Netscape 6.x (6.0 + 6.2 verified.. image event handlers FUBARed?)
    this.currentBannerIndex = (typeof(n)!='undefined'?n:this.getRandomBanner());
    writeDebug('BannerManager.setBanner('+this.currentBannerIndex+')');
    this.cover.setAnimationStyle();
    this.cover.onComplete = this.bannerCoverComplete;
    this.cover.hideImage();
  }

  this.bannerCoverComplete = function() {
    // banner area now covered - "preload" banner
    writeDebug('BannerManager.bannerCoverComplete()');
    addEventHandler(self.oImg,'load',self.bannerOnloadHandler,false);
    extension = '.'+(self.banners[self.currentBannerIndex].properties['extension']||'jpg');
    self.oImg.src = 'image/banners/banner_'+self.currentBannerIndex+extension;
    self.oImgTile.src = 'image/banners/banner_'+self.currentBannerIndex+'_tile'+extension;
  }

  this.bannerOnloadHandler = function() {
    writeDebug('BannerManager.bannerOnloadHandler()');
    self.setBannerHandler();
    removeEventHandler(self.oImg,'load',self.bannerOnloadHandler);
    setTimeout("bannerManager.cover.showImage()",100);
  }

  this.setBannerHandler = function() {
    // called from bannerOnloadHandler()
    i = self.currentBannerIndex;
    self.currentBanner = self.banners[i];
    extension = '.'+(self.currentBanner.properties['extension']||'jpg');
    self.o[0].style.backgroundImage = 'url(image/banners/banner_'+i+'_tile'+extension+')';
    self.o[1].style.backgroundImage = 'url(image/banners/banner_'+i+extension+')';
    self.o[1].style.backgroundPosition = (self.banners[i].properties['align'] && self.banners[i].properties['align']=='right'?'100% ':'0% ')+'0%';
    scrollManager.reset(); // reposition at 0
    // show scroll if applicable
    if (self.banners[i].properties['width'] && self.banners[i].properties['width']>parseInt(self.o[1].offsetWidth)) {
      scrollManager.show();
    } else {
      scrollManager.hide();
    }
  }

  this.setKeywords = function(keywords) {
    this.keywords = keywords.split(', '); // create array from a string
  }

  this.setRelatedBanner = function(keywords) {
    // set a randomly-selected banner by section according to keywords
    writeDebug('BannerManager.setRelatedBanner()');
    this.currentKeywords = keywords||this.currentKeywords;
    keywords = this.currentKeywords.toLowerCase().split(' ');
    var related = [];
    for (i=0; i<keywords.length; i++) {
      keywords[i] = keywords[i].replace(/\-/g,' ');
       for (j=0; j<this.banners.length; j++) {
        if (this.banners[j].keywords.indexOf(keywords[i])+1) {
          related[related.length] = j;
        }
      }
    }
    if (related.length) {
      this.setBanner(related[parseInt(Math.random()*related.length)]);
    }
  }

}

function ScrollManager() {
  writeDebug('ScrollManager()');
  var self = this;
  this.o = null;
  this.oScroll = null;
  this.banner = null; // banner reference
  this.clickX = 0;
  this.mouse = null;
  this.containerWidth = 0; // width of banner container
  this.xMin = 0;
  this.xMax = 85;
  this.xDefault = parseInt(this.xMax/2);
  this.xLast = this.xMin+5;
  this.drag = function(e) {
    // cancel event bubbling?
    x = (self.mouse.clientX-self.clickX);
    if (x>=(self.xMin+5) && x<=self.xMax) {
      self.setX(x);
    } else if (x>self.xMax && self.xLast<self.xMax) {
      self.setX(self.xMax);
    } else if (x<self.xMax && self.xLast>self.xMin+5 && self.xLast<self.xMax<2) {
      self.setX(self.xMin+5);
    }
    return false;
  }
  this.release = function(e) {
    writeDebug('ScrollManager.release()');
    removeEventHandler(document,'mousemove',self.drag);
    removeEventHandler(document,'mouseup',self.drag);
    addEventHandler(self.o,'mousedown',self.drag);
  }
  this.setX = function(x) {
    // adjust banner offset
    self.o.style.left = x+'px';
    self.xLast = x;
    offX = parseInt((self.xMax-(self.xMax-x))/self.xMax*(Math.abs(self.containerWidth-bannerManager.currentBanner.properties['width']))*-1); // bannerManager.currentBanner.properties['width']
    self.banner.style.backgroundPosition = offX+'px 0px';
  }
  this.start = function(o) {
    if (!bannerManager.currentBanner.properties['width']) {
      return false; // disallow scroll if no width given (will break otherwise.)
    }
    self.containerWidth = parseInt(self.banner.offsetWidth);
    self.clickX = (self.mouse.clientX-self.o.offsetLeft);
    addEventHandler(document,'mousemove',self.drag,false);
    addEventHandler(document,'mouseup',self.release,false);
  }
  this.show = function() {
    writeDebug('ScrollManager.show()');
    this.oScroll.style.visibility = 'visible';
  }
  this.hide = function() {
    writeDebug('ScrollManager.hide()');
    this.oScroll.style.visibility = 'hidden';
  }
  this.reset = function() {
    if (bannerManager.currentBanner.properties['width'] && self.o) { // self.o set once user has grabbed scroller
      self.setX(self.xLast); // re-apply scroller position to current background
    }
  }
  this.getMouse = function(e) {
    // grab mouse from event for future reference
    // 'this' context: element that event was attached to - eg. 'document' object
    e = e||event;
    self.mouse = e;
    // detach event handler
    removeEventHandler(document,'mousemove',self.getMouse,false);
  }
  this.init = function() {
    writeDebug('ScrollManager.init()');
    this.oScroll = document.getElementById('scroller');
    this.o = document.getElementById('scroller-ball');
    this.banner = bannerManager.o[1];
  }
  // set up mouse grab
  addEventHandler(document,'mousemove',self.getMouse,false);

}

function SoundManager(container) {
  // DHTML-controlled sound via Flash
  writeDebug('SoundManager()');
  this.o = []; // movie references
  this.container = container;
  this.unsupported = 0; // assumed to be supported
  this.defaultName = 'default'; // default movie
  
  this.FlashObject = function(url) {
    writeDebug('ScrollManager.FlashObject('+url+')');
    this.movie = null;
    this.movieContainer = document.createElement('div');
    this.movieContainer.className = 'movieContainer';
    var htmlIE = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=5,0,0,0"><param name="movie" value="'+url+'"><param name="quality" value="high"></object>';
    var htmlMoz = '<embed src="'+url+'" width="1" height="1" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed>';
    if (navigator.appName.toLowerCase().indexOf('microsoft')+1) {
      this.movieContainer.innerHTML = htmlIE;
      this.movie = this.movieContainer.getElementsByTagName('object')[0];
    } else {
      oEmbed = document.createElement('embed');
      oEmbed.src = url;
      oEmbed.quality = 'high';
      oEmbed.width = 1;
      oEmbed.height = 1;
      oEmbed.pluginspage = 'http://www.macromedia.com/go/getflashplayer';
      oEmbed.type = 'application/x-shockwave-flash';
      this.movieContainer.appendChild(oEmbed);
      this.movie = this.movieContainer.getElementsByTagName('embed')[0];
    }
    document.documentElement.getElementsByTagName('div')[0].appendChild(this.movieContainer);
  }

  this.addMovie = function(name,url) {
    this.o[name] = new this.FlashObject(url);
  }

  this.play = function(flashName,label,volume) {
    writeDebug('SoundManager.play('+(flashName||this.defaultName)+','+(label||'null')+')');
    if (this.unsupported) return false;
    if (!flashName) flashName = this.defaultName;
    oTmp = this.o[flashName].movie;
    if (!oTmp) {
      writeDebug('SoundManager.play(): could not find movie named '+flashName+'.');
      return false;
    }
    volume = (volume>=0?volume:50);
    try {
      if (oTmp.readyState == 4 || oTmp.PercentLoaded() == 100) {
        oTmp.TGotoLabel("/"+label,"start");
        oTmp.TPlay("/"+label);
        this.playFrame(flashName,label,'v'+volume); // ,'v'+volume);
      } else {
        // movie not fully loaded
      }
    } catch(e) {
      // Something blew up. Not supported?
      writeDebug('SoundManager.play(): Exception. Sound will be disabled.');
      this.unsupported = 1;
      return false;
    }
  }

  this.playFrame = function(flashName,label,volume) {
    this.o[flashName].movie.TGotoLabel('/'+label,volume);
  }

  // constructor - create flash objects

  if (isIE && isMac) {
    this.unsupported = 1;
  }

  if (!this.unsupported) {
    this.addMovie(this.defaultName,'2004-audio.swf');
  }

}

function SoundManagerNull() {
  // Null object for unsupported case
  writeDebug('SoundManagerNull()');
  this.o = []; // movie references
  this.container = null;
  this.unsupported = 1;
  this.FlashObject = function(url) {}
  this.addMovie = function(name,url) {}
  this.play = function(flashName,label,volume) {
    return false;
  }
  this.playFrame = function(flashName,label,volume) {}
  this.defaultName = 'default';
}

function removeChildNodes(o) {
  // remove children from bottom up
  var nodes = o.childNodes;
  if (!nodes || !o) {
    writeDebug('removeChildNodes('+(o||'null')+'): no nodes to remove.');
    return false;
  }
  writeDebug('removeChildNodes('+o.nodeName+'): removing '+nodes.length+' node'+(nodes.length>1?'s':''));
  for (var i=nodes.length-1; i>=0; i--) {
    o.removeChild(nodes[i]);
  }
}

function getElementsByClassName(className,tagNames,oParent) {
  // grab elements by class, restricting search to certain tags or a parent element
  var doc = (oParent||document);
  var matches = [];
  var i,j;
  // nodes - globally declared array (var nodes = [])
  if (!nodes) nodes = []; // required to keep Opera happy
  if (typeof(tagNames)!='undefined' && typeof(tagNames)!='string') {
    for (i=0; i<tagNames.length; i++) {
      if (!nodes || !nodes[tagNames[i]]) {
        nodes[tagNames[i]] = doc.getElementsByTagName(tagNames[i]);
      }
    }
  } else if (tagNames) {
    nodes = doc.getElementsByTagName(tagNames);
  } else {
    nodes = doc.all||doc.getElementsByTagName('*');
  }
  if (typeof(tagNames)!='string') {
    for (i=0; i<tagNames.length; i++) {
      for (j=0; j<nodes[tagNames[i]].length; j++) {
        c = nodes[tagNames[i]][j].className;
        if (c && c.indexOf(className)+1 && (c == className || c.indexOf(className+' ')+1 || c.indexOf(' '+className)+1)) {
          matches[matches.length] = nodes[tagNames[i]][j];
        }
      }
    }
  } else {
    for (i=0; i<nodes.length; i++) {
      c = nodes[i].className;
      if (c && c.indexOf(className)+1 && (c == className || c.indexOf(className+' ')+1 || c.indexOf(' '+className)+1)) {
        matches[matches.length] = nodes[i];
      }
    }
  }
  writeDebug('getEBCN('+className+',['+(tagNames||'null')+'],'+(oParent?oParent.nodeName:'null')+'): '+matches.length+' matches');
  return matches; // kids, don't play with fire. ;)
}

var oDebug = null;
var tStart = new Date();

function writeDebug(s) {
  if (!oDebug) {
    oDebug = document.createElement('div');
    oDebug.id = 'debugContainer';
    oDebug.className = 'png';
  }
  var d = document.createElement('div');
  d.appendChild(document.createTextNode((new Date()-tStart)+': '+s));
  oDebug.appendChild(d);
}

function debugInit() {
  writeDebug('debugInit()');
  document.documentElement.appendChild(oDebug);
  document.getElementById('banner').getElementsByTagName('div')[0].appendChild(oDebug);
  if (pngHandler.transform) pngHandler.transform(oDebug);
}

function doNothing() {
  return false;
}

if (!DEBUG) {
  writeDebug = doNothing;
  debugInit = doNothing;
}


function init() {
  writeDebug('init()');
  // initialize site interface + functionality
  debugInit();
  document.getElementById('txtTheme').appendChild(document.createTextNode('theme { '));
  document.getElementById('txtStyle').appendChild(document.createTextNode(' | style { '));
  document.getElementById('keywordFilterTitle').appendChild(document.createTextNode('filter {'));
  if (navigator.appVersion.indexOf('MSIE 5.01')+1) {
    // IE 5.0 hack
    var x = document.getElementById('col1');
    x.style.position = 'absolute';
  }
  bannerManager.init();
  scrollManager.init();
  if (isIE && isMac) {
    // ie:mac doesn't recognise theme change until after load. (Odd.)
    setDefaultTheme();
    setDefaultStyle();
  }
  getColumns();
  getTabs();
  if (setDefaultTab()) {
    // highlight selected entry, if applicable
    findEntryHighlight();
  }
  if (!(window.location.toString().indexOf('webpad')+1)) {
    // initialize if not under content/react/webpad/
    webPadInit(); // defined in webpad.js
  }
  setTimeout("bannerManager.setRelatedBanner('featured')",20);
  // window.status = 'Total load time: '+(new Date()-timeStart)+'ms';
}

function cleanup() {
  configManager.save();
}

function preInit() {
  soundManager = new SoundManager();
  if (!APP_XHTML) {
    setup();
  } else {
    // addEventHandler(window,'load',setup);
    setTimeout(setup,1500);
  }
}

function setup() {
  // Initialize PNG handler (replace PNG classed-elements with PNG images where supported)
  writeDebug('setup()');
  pngHandler.supported = pngHandler.supportTest();
  pngHandler.init();
  contentManager.assignHandlersToClass('entries','div'); // previously "container" - assign entry load handlers
  filterManager = new FilterManager();
  addEventHandler(window,'load',init);
}

writeDebug('SCHILLMANIA! 2004 edition: debugger output console');
writeDebug('--------------------------------------------------');

// Create controller objects

writeDebug('Creating controller objects...');

extremeCounter();

if (isIE && isMac) SoundManager = SoundManagerNull; // point to "disabled" version of sound manager object where buggy/unsupported (ie:mac)

configManager = new ConfigManager();
bannerManager = new BannerManager();
scrollManager = new ScrollManager();
contentManager = new ContentManager();

disableAllThemes();
setDefaultStyle();
setDefaultTheme();

addEventHandler(window,'unload',cleanup);

