/*=:project

		parseSelector 2.0

		

	=:description

		Provides an extensible way of parsing CSS selectors against a DOM in 

		JavaScript.



  =:file

  	Copyright: 2006 Mark Wubben.

  	Author: Mark Wubben, <http://novemberborn.net/>

   		

	=:license

		* This software is licensed and provided under the CC-GNU LGPL

		* See <http://creativecommons.org/licenses/LGPL/2.1/>

	

	=:notes

		* The parsing of CSS selectors as streams has been based on Dean Edwards

			excellent work with cssQuery. See <http://dean.edwards.name/my/cssQuery/>

			for more info.

*/



var parseSelector = (function() {

	var SEPERATOR = /\s*,\s*/

	

	function parseSelector(selector, node) {

		node = node || document.documentElement;

		var selectors = selector.split(SEPERATOR);

		var result = [];

		

		for(var i = 0; i < selectors.length; i++) {

			var nodes = [node];

			var stream = toStream(selectors[i]);

			for(var j = 0;j < stream.length;) {

				var token = stream[j++];

				var filter = stream[j++];

				var args = '';

				if(stream[j] == '(') {

					while(stream[j++] != ')' && j < stream.length) args += stream[j];

					args = args.slice(0, -1);

				}

				nodes = select(nodes, token, filter, args);

			}

			result = result.concat(nodes);

		}

		

		return result;

	}



	var WHITESPACE = /\s*([\s>+~(),]|^|$)\s*/g;

	var IMPLIED_ALL = /([\s>+~,]|[^(]\+|^)([#.:@])/g;

	var STANDARD_SELECT = /^[^\s>+~]/;

	var STREAM = /[\s#.:>+~()@]|[^\s#.:>+~()@]+/g;

		

	function toStream(selector) {

		var stream = selector.replace(WHITESPACE, '$1')

												 .replace(IMPLIED_ALL, '$1*$2');

		if(STANDARD_SELECT.test(stream)) stream = ' ' + stream;

    return stream.match(STREAM) || [];

	}

	

	function select(nodes, token, filter, args) {

		return (selectors[token]) ? selectors[token](nodes, filter, args) : [];

	}

	

	var util = {

		toArray: function(enumerable) {

			var a = [];

			for(var i = 0; i < enumerable.length; i++) util.push(a, enumerable[i]);

			return a;

		},

		

		push: function(arr) {

      for(var i = 1; i < arguments.length; i++) arr[arr.length] = arguments[i];

      return arr.length;

    }

	};

	

	var dom = {

		isTag: function(node, tag) {

			return (tag == '*') || (

				tag.toLowerCase() == node.nodeName.toLowerCase().replace(':html', '')

			);

		},

	

		previousSiblingElement: function(node) {

			do node = node.previousSibling; while(node && node.nodeType != 1);

			return node;

		},

	

		nextSiblingElement: function(node) {

			do node = node.nextSibling; while(node && node.nodeType != 1);

			return node;

		},

	

		hasClass: function(name, node) {

			return (node.className || '').match('(^|\\s)'+name+'(\\s|$)');

		},

	

		getByTag: function(tag, node) {

			/*	IE5.x does not support document.getElementsByTagName("*")

				therefore we're falling back to element.all */

			if(tag == '*' && node.all != null) return node.all;

			return node.getElementsByTagName(tag);

		}

	};

	

	var selectors = {

		'#': function(nodes, filter) {

			for(var i = 0; i < nodes.length; i++) {

				if(nodes[i].getAttribute('id') == filter)	return [nodes[i]];

			}

			return [];

		},



		' ': function(nodes, filter) {

			var result = [];

			for(var i = 0; i < nodes.length; i++) {

				result = result.concat(util.toArray(dom.getByTag(filter, nodes[i])));

			}

			return result;

		},

		

		'>': function(nodes, filter) {

			var result = [];

			for(var i = 0, node; i < nodes.length; i++) {

				node = nodes[i];

				for(var j = 0, child; j < node.childNodes.length; j++) {

					child = node.childNodes[j];

					if(child.nodeType == 1 && dom.isTag(child, filter)) {

						util.push(result, child);

					}

				}

			}

			return result;

		},



		'.': function(nodes, filter) {

			var result = [];

			for(var i = 0, node; i < nodes.length; i++) {

				node = nodes[i];

				if(dom.hasClass([filter], node)) util.push(result, node);

			}

			return result;

		}, 

				

		':': function(nodes, filter, args) {

			return (pseudoClasses[filter]) ? pseudoClasses[filter](nodes, args) : [];

		}

		

	};

	

	parseSelector.selectors			= selectors;

	parseSelector.pseudoClasses = {};

	parseSelector.util 				  = util;

	parseSelector.dom 				  = dom;

		

	return parseSelector;

})();



/*=:project

    scalable Inman Flash Replacement (sIFR) version 3.0, Alpha.



  =:file

    Copyright: 2006 Mark Wubben.

    Author: Mark Wubben, <http://novemberborn.net/>

   

  =:history

    * IFR: Shaun Inman

    * sIFR 1: Mike Davidson, Shaun Inman and Tomas Jogin

    * sIFR 2: Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben

    

  =:license

    * This software is licensed and provided under the CC-GNU LGPL

    * See <http://creativecommons.org/licenses/LGPL/2.1/>    

*/



var sIFR = new function() {

  //=:private Constant reference to the Singleton instance

  var SIFR = this;

  

  var CSS_HASFLASH = 'sIFR-hasFlash';

  var CSS_REPLACED = 'sIFR-replaced';

  var CSS_FLASH = 'sIFR-flash';

  var CSS_IGNORE = 'sIFR-ignore';

  var CSS_ALTERNATE = 'sIFR-alternate';

  var CSS_CLASS = 'sIFR-class';

  var XHTML_NS = 'http://www.w3.org/1999/xhtml';

  var MIN_FONT_SIZE = 6;

  var MAX_FONT_SIZE = 126;

  var MIN_FLASH_VERSION = 7;

  var ADVANCED_FLASH_VERSION = 8;

  var PREFETCH_COOKIE = 'SIFR-PREFETCHED';

  

  this.compatMode = false;

  this.isActive = false;

  this.isEnabled = true;

  this.hideElements = true;

  this.registerEvents = true;

  this.waitForPrefetch = true;

  this.cookiePath = '/';

  

  var elementCount = 0;

  var hasPrefetched = false;

  var advancedModeOnly = false; // Advanced mode requires Flash 8 or above.

  

  var ua = new function() {

    var ua = navigator.userAgent.toLowerCase();

    var product = (navigator.product || '').toLowerCase();



    this.macintosh  = ua.indexOf('mac') > -1;

    this.windows    = ua.indexOf('windows') > -1;



    this.opera      = ua.indexOf('opera') > -1;

    this.konqueror  = product.indexOf('konqueror') > -1;

    this.ie         = ua.indexOf('ie') > -1 && !this.opera;

    this.ieMac      = this.ie && this.mac;

    this.ieWin      = this.ie && this.windows;

    this.safari     = ua.indexOf('safari') > -1;

    this.webkit     = ua.indexOf('applewebkit') > -1 && !this.konqueror;

    this.khtml      = this.webkit || this.konqueror;

    this.gecko      = !this.webkit && product == 'gecko';



    var $;

    this.operaVersion = this.webkitVersion  

                      = this.geckoBuildDate 

                      = this.konquerorVersion = 0;

                      

    if(this.opera) {

      $ = ua.match(/.*opera(\s|\/)(\d+\.\d+)/);

      this.operaVersion = $.length > 1 ? parseInt($[2]) : 0;

    }



    if(this.webkit) {

      $ = ua.match(/.*applewebkit\/(\d+).*/);

      this.webkitVersion = $.length > 0 ? parseInt($[1]) : 0;

    }

    

    if(this.gecko) {

      $ = ua.match(/.*gecko\/(\d{8}).*/);

      this.geckoBuildDate = $.length > 0 ? parseInt($[1]) : 0;

    }



    if(this.konqueror) {

      $ = ua.match(/.*konqueror\/(\d\.\d).*/);

      this.konquerorVersion = $.length > 0 ? parseInt($[1]) : 0;

    }

    

    this.flashVersion = 0;



    if(this.ieWin) {

      try {

        this.flashVersion = parseFloat(

          /([\d,?]+)/.exec(

            new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7').GetVariable('$version')

          )[1].replace(/,/g, '.')

        );

      } catch(e) {}

    } else if(navigator.plugins && navigator.plugins['Shockwave Flash']) {

      var flashPlugin = navigator.plugins['Shockwave Flash'];

      this.flashVersion = parseFloat(/(\d+\.?\d*)/.exec(flashPlugin.description)[1]);



      // Watch out for QuickTime, which could be stealing the Flash handling!

      var i = 0;

      while(this.flashVersion >= MIN_FLASH_VERSION && i < navigator.mimeTypes.length) {

        var mime = navigator.mimeTypes[i];

        if(mime.type == 'application/x-shockwave-flash' 

        && !new RegExp('.*Shockwave\\sFlash\\s' + this.flashVersion + '.*').exec(mime.enabledPlugin.description)) {

          this.flashVersion = 0;

        }

        i++;

      }

    }



    this.flash = this.flashVersion >= MIN_FLASH_VERSION;



    this.transparencySupport = true;

    if(!this.macintosh && !this.windows || this.ieMac

    || this.opera && this.operaVersion < 7.6 

    || this.webkit && this.webkitVersion < 312

    || this.gecko && this.geckoBuildDate < 20020523) {

      this.transparencySupport = false;

    }



    this.computedStyleSupport = true;

    if(!this.ie && !this.gecko 

    && (!document.defaultView || !document.defaultView.getComputedStyle)

    //: Older Geckos have trouble with `getComputedStyle()`

    || this.gecko && this.geckoBuildDate < 20030624) {

      this.computedStyleSupport = false;  

    }

    

    this.zoomSupport = this.opera && document.documentElement;

    this.geckoXml = this.gecko && (document.contentType || '').indexOf("xml") > -1;

                    

    this.innerHtmlHack =  this.konqueror || (this.webkit && this.webkitVersion < 312) || this.ie;

    this.requiresPrefetch = this.ieWin || this.safari;

    

    this.vbScript = this.ieWin;

    if(this.vbScript) {

      try {

        document.write([

          '<script type="text/vbscript">',

            'sIFR_ua_vbScript = true',

          '</script>'

        ].join('\n'));

      } catch(e) {};

      this.vbScript = !!sIFR_ua_vbScript;

    }

    

    this.supported = (!this.ie || this.ieWin && this.vbScript) && (!this.macintosh || !this.opera || this.operaVersion >= 8);

  };

  this.ua = ua;



  var dom = new function() {

    this.getBody = function() {

      var nodes = document.getElementsByTagName('body');

      if(nodes.length == 1) return nodes[0];

    };

    

    this.addClass = function(name, node) {

      node.className = ((node.className || '') == '' ? '' : node.className + ' ') + name;

    };

    

    this.hasClass = function(name, node) {

      return new RegExp('(^|\\s)'+name+'(\\s|$)').test(node.className);

    };

    

    this.create = function(name) {

      if(document.createElementNS) return document.createElementNS(XHTML_NS, name);

      return document.createElement(name);

    }

    

    var useInnerHtml = true;

    try { this.create('span').innerHTML = ''; }

    catch (e) { useInnerHtml = false; }



    this.setInnerHtml = function(node, html) {

      if(useInnerHtml) node.innerHTML = html;

      else {

        html = ['<root xmlns="', XHTML_NS, '">', html, '</root>'].join('');

        var xml = (new DOMParser()).parseFromString(html, 'text/xml');

        xml = document.importNode(xml.documentElement, true);

        while(node.firstChild) node.removeChild(node.firstChild);

        while(xml.firstChild)  node.appendChild(xml.firstChild);

      }

    }

    

    this.appendNode = function(to, node) {

      to.appendChild(node);

      if(this.innerHtmlHack) to.innerHTML += '';

    }

    

    this.getStyleAsInt = function(node, property) {

      if(!ua.computedStyleSupport && !SIFR.compatMode) return null;

      

      var value;

      if(document.defaultView && document.defaultView.getComputedStyle)

        value = document.defaultView.getComputedStyle(node, null)[property];

      else if(node.currentStyle) value = node.currentStyle[property];



      value = parseInt(value);

      return isNaN(value) ? 0 : value;

    }



    this.getZoom = function() {

      return hacks.zoom.getLatest();

    }

  };

  this.dom = dom;

  

  var util = {

    normalize: function(str) {

      return str.replace(/(\s)\s+/g, '$1');

    },

    

    push: function(arr) {

      for(var i = 1; i < arguments.length; i++) arr[arr.length] = arguments[i];

      return arr.length;

    }

  };

  this.util = util;

  

  var hacks = {};

  hacks.fscommand = new function() {

    if(!ua.vbScript) return;

    

    // `fscommand()` in IE will only talk to a VB Script function. The code below

    // allows us to create such a function. Inspired by <http://www.moock.org/webdesign/flash/fscommand/>

    // and <http://osflash.org/flashjs>.

    document.write([

      '<script type="text/vbscript">',

        'Sub sIFR_setup_fscommand(callbackName)',

        	'ExecuteGlobal _',

        	  '"Sub " & callbackName & "_FSCommand(ByVal command, ByVal args)" & _',

         		  'callbackName & "_DoFSCommand command, args" & vbcrlf & _',

         		'"End Sub"',

        'End Sub',

      '</script>'

    ].join('\n'));

  };

  

  hacks.fscommand.setup = function(callbackName) {

    if(ua.vbScript) sIFR_setup_fscommand(callbackName);

  };

  

  hacks.fragmentIdentifier = new function() {

    this.fix = true;

    

    var cachedTitle;

    this.cache = function() {

      cachedTitle = document.title;

    };

    

    function doFix() {

      document.title = cachedTitle;

    }

    

    this.restore = function() {

      if(this.fix) setTimeout(doFix, 0);

    };

  };

    

  // The zoom hack needs to be run before replace(). The synchronizer can be

  // used to ensure this.

  hacks.synchronizer = new function() {

    this.isBlocked = false;

    

    this.block = function() {

      this.isBlocked = true;

    };

    

    this.unblock = function() {

      this.isBlocked = false;

      blockedReplaceKwargsStore.replaceAll();

    };

  };

  

  // Detect the page zoom in Opera. Adapted from <http://virtuelvis.com/archives/2005/05/opera-measure-zoom>.

  hacks.zoom = new function() {

    // Latest zoom, assume 100

    var latestZoom = 100;

    

    this.getLatest = function() {

      return latestZoom;

    }



    if(ua.zoomSupport && ua.opera) {

      // Create the DOM element used to calculate the zoom.

      var node = document.createElement('div');

      node.style.position = 'fixed';

      node.style.left = '-65536px';

      node.style.top = '0';

      node.style.height = '100%';

      node.style.width = '1px';

      node.style.zIndex = '-32';

      document.documentElement.appendChild(node);

    

      function updateZoom() {

        if(!node) return;

      

        var zoom = window.innerHeight / node.offsetHeight;



        var correction = Math.round(zoom * 100) % 10;

        if(correction > 5) zoom = Math.round(zoom * 100) + 10 - correction;

        else zoom = Math.round(zoom * 100) - correction;



        latestZoom = isNaN(zoom) ? 100 : zoom;

        hacks.synchronizer.unblock();



        document.documentElement.removeChild(node);

        node = null;

      }



      hacks.synchronizer.block();



      // We need to wait a few ms before Opera the offsetHeight of the node

      // becomes available.

      setTimeout(updateZoom, 54);

    }

  };

  this.hacks = hacks;

  

  var replaceKwargsStore = {

    kwargs: [],

    replaceAll:  function() {

      for(var i = 0; i < this.kwargs.length; i++) SIFR.replace(this.kwargs[i]);

      this.kwargs = [];

    }

  };

  

  var blockedReplaceKwargsStore = {

    kwargs: [],

    replaceAll: replaceKwargsStore.replaceAll

  };



  this.useAdvancedModeOnly = function() {

    advancedModeOnly = true;

    ua.flash = ua.flashVersion >= ADVANCED_FLASH_VERSION;

  }



  this.activate = function() {

    if(!ua.flash || !this.isEnabled || this.isActive || !ua.supported || !this.compatMode && !ua.computedStyleSupport) {

      return;

    }



    this.isActive = true;

    

    if(this.hideElements) this.setFlashClass();

    

    if(ua.ieWin && hacks.fragmentIdentifier.fix && window.location.hash != '') {

      hacks.fragmentIdentifier.cache();

    } else hacks.fragmentIdentifier.fix = false;

    

    if(!this.registerEvents) return;

    

    function handler(evt) {

      SIFR.initialize(true);

      

      // Remove handlers to prevent memory leak in Firefox 1.5

      if(document.removeEventListener) document.removeEventListener('load', handler, false);

      if(window.removeEventListener) window.removeEventListener('load', handler, false);

    }

    

    if(window.attachEvent) {

      window.attachEvent('onload', handler);

    } else if((!ua.konqueror || ua.konquerorVersion < 3.5) 

    && (document.addEventListener || window.addEventListener)) {

      if(document.addEventListener)  document.addEventListener('load', handler, false);  

      if(window.addEventListener) window.addEventListener('load', handler, false);  

    } else {

      if(typeof window.onload == 'function'){

        var fOld = window.onload;

        window.onload = function(evt){ fOld(evt); handler(evt); };

      } else window.onload = handler;

    }

  };

  

  this.hasFlashClass = false;

  this.setFlashClass = function() {

    if(this.hasFlashClass) return;



    dom.addClass(CSS_HASFLASH, dom.getBody() || document.documentElement);

    this.hasFlashClass = true;

  };

  

  this.isInitialized = false;

  this.initialize = function(evt) {

    if(this.isInitialized || !this.isActive || !this.isEnabled 

    || !evt && (ua.requiresPrefetch && hasPrefetched && this.waitForPrefetch || !this.uaCompletedRendering())) {

      return;

    }



    this.isInitialized = true;

    replaceKwargsStore.replaceAll();

    clearPrefetch();

  };

  

  this.uaCompletedRendering = function() {

    return (this.isInitialized || !ua.khtml && !ua.geckoXml && !!dom.getBody());

  };



  function getSource(kwargs) {

    if(typeof(kwargs) == 'string') return kwargs;

    if(ua.flashVersion < ADVANCED_FLASH_VERSION) return kwargs.src;

    return kwargs.highsrc || kwargs.src;

  }



  this.prefetch = function() {

    if(!ua.requiresPrefetch || !ua.flash || !this.isEnabled) return;

    if(this.waitForPrefetch && new RegExp(';?' + PREFETCH_COOKIE + '=true;?').test(document.cookie)) return;



    try { // We don't know which DOM actions the user agent will allow

      hasPrefetched = true;

      

      if(ua.ieWin) prefetchIexplore(arguments);

      else prefetchLight(arguments);



      if(this.waitForPrefetch) document.cookie = PREFETCH_COOKIE + '=true;path=' + this.cookiePath;

    } catch(e) {}

  };

  

  function prefetchIexplore(args) {

    var head = document.getElementsByTagName('head')[0];

    for(var i = 0; i < args.length; i++) {

      var node = dom.create('embed');

      head.appendChild(node);

      node.setAttribute('src', getSource(args[i]));

      node.setAttribute('sIFR-prefetch', 'true');

    }

  }

  

  // Lightweight prefetch method

  function prefetchLight(args) {

    for(var i = 0; i < args.length; i++) new Image().src = getSource(args[i]);

  }

  

  function clearPrefetch() {

    if(!ua.ieWin || !hasPrefetched) return;



    try {

      var head = document.getElementsByTagName('head')[0];

      var nodes = head.getElementsByTagName('embed');

      for(var i = nodes.length - 1; i >= 0; i--) {

        if(node.getAttribute('sIFR-prefetch') == 'true') head.removeChild(node);

      }

    } catch(e) {}

  }



  // Gives a font-size to required vertical space ratio

  // Tested with Verdana

  // Mirrored in sIFR.as

  function getRatio(size) {

    if(size <= 10) return 1.55;

    if(size <= 19) return 1.45;

    if(size <= 32) return 1.35;

    if(size <= 71) return 1.30;

    return 1.25;

  }

  

  function convertCssArg(arg) {

    if(!arg) return '';

    

    var css = [];

    for(var selector in arg) {

      var rule = arg[selector];

      if(rule == Object.prototype[selector]) continue;

      

      util.push(css, selector, '{');

      for(var property in rule) {

        if(rule[property] == Object.prototype[property]) continue;

        util.push(css, property, ':', rule[property], ';');

      }

      util.push(css, '}');

    }

    

    return css.join('');

  }

  

  function extractFromCss(css, selector, property, remove) {

    var value = null;

    

    if(css && css[selector] && css[selector][property]) {

      value = css[selector][property];

      if(remove) delete css[selector][property];

    }

    

    return value;

  }

  

  this.replace = function(kwargs) {

    if(!this.isInitialized) return util.push(replaceKwargsStore.kwargs, kwargs);

    if(hacks.synchronizer.isBlocked) return util.push(blockedReplaceKwargsStore.kwargs, kwargs);



    nodes = parseSelector(kwargs.selector);

    if(nodes.length == 0) return;

    

    this.setFlashClass();

    

    var src = getSource(kwargs);

    var css = convertCssArg(kwargs.css);

    var leading = parseInt(extractFromCss(kwargs.css, '.sIFR-root', 'leading')) || 0;

    var backgroundColor = extractFromCss(kwargs.css, '.sIFR-root', 'background-color', true) || '#FFFFFF';

    

    var wmode = kwargs.wmode || '';

    if(wmode == 'transparent') {

			if(!ua.transparencySupport)	wmode = 'opaque';

			else backgroundColor = 'transparent';

		}



    // Some IE installs refuse to show the Flash unless it gets the really absolute

    // URI of the file. I haven't been able to reproduce this behavior but let's

    // ensure a full URI none the less.

    if(ua.ie && src.charAt(0) == '/') {

      src = location.toString().replace(/([^:]+)(:\/?\/?)([^\/]+).*/, '$1$2$3') + src;

    }



    for(var i = 0; i < nodes.length; i++) {

      var node = nodes[i];

      

      if(dom.hasClass(CSS_REPLACED, node) || dom.hasClass(CSS_IGNORE, node)) {

        continue;

      }

      

      // Ignore elemnts which have no height. Usually these are `display: none;`

      if(node.offsetHeight == 0) continue;

      

      // Determine lineHeight (the font-size used in the Flash) and the number 

      // of lines. We also need the dimensions in order to calculate word 

      // wrapping.

      var paddingTop = dom.getStyleAsInt(node, 'paddingTop');

      if(paddingTop == null) paddingTop = kwargs.paddingTop;

      var paddingRight = dom.getStyleAsInt(node, 'paddingRight');

      if(paddingRight == null) paddingRight = kwargs.paddingRight;

      var paddingBottom = dom.getStyleAsInt(node, 'paddingBottom');

      if(paddingBottom == null) paddingBottom = kwargs.paddingBottom;

      var paddingLeft = dom.getStyleAsInt(node, 'paddingLeft');

      if(paddingLeft == null) paddingLeft = kwargs.paddingLeft;

      var borderTop = dom.getStyleAsInt(node, 'borderTopWidth');

      if(borderTop == null) borderTop = kwargs.borderTop;

      var borderRight = dom.getStyleAsInt(node, 'borderRightWidth');

      if(borderRight == null) borderRight = kwargs.borderRight;

      var borderBottom = dom.getStyleAsInt(node, 'borderBottomWidth');

      if(borderBottom == null) borderBottom = kwargs.borderBottom;

      var borderLeft = dom.getStyleAsInt(node, 'borderLeftWidth');

      if(borderLeft == null) borderLeft = kwargs.borderLeft;



      var height =  node.offsetHeight - paddingTop - paddingBottom - borderTop - borderBottom;

      var width = node.offsetWidth - paddingLeft - paddingRight - borderLeft - borderRight;



      // Take the zoom level into account

      var dZoom = dom.getZoom() / 100;

      height = Math.round(height / dZoom);

      width = Math.round(width * dZoom);



      var lineHeight, lines;

      if(!ua.ie) { //:=todo Only do once for each selector?

        if(!ua.computedStyleSupport && SIFR.compatMode) {

          var html = node.innerHTML;

          dom.setInnerHtml(node, 'X');

          lineHeight   = node.offsetHeight - paddingTop - paddingBottom - borderTop - borderBottom;

          dom.setInnerHtml(node, html);

        } else lineHeight = dom.getStyleAsInt(node, 'lineHeight');

        lines = Math.ceil(height / lineHeight);

      } else if(ua.ie) { // IE returs computed style in the original units, which is quite useless.

        var html = node.innerHTML;

        dom.setInnerHtml(node, 'X<br />X<br />X');

        

        // Without these settings, we won't be able to get the rects properly.

        node.style.visibility = 'visible';

        node.style.width = 'auto';

        node.style.styleFloat = 'none';

        var rects = node.getClientRects();



        lineHeight = rects[1].bottom - rects[1].top;

        dom.setInnerHtml(node, html);

        rects = node.getClientRects();

        lines = rects.length;



        // By setting an empty string, the values will fall back to those in the (non-inline) CSS.

        // When that CSS changes, the changes are reflected here. Setting explicit values would break

        // that behaviour.

        node.style.styleFloat = '';

        node.style.width = '';

        node.style.visibility = '';

      }



      lineHeight = Math.max(MIN_FONT_SIZE, lineHeight);

      lineHeight = Math.min(MAX_FONT_SIZE, lineHeight);



      if(isNaN(lines) || !isFinite(lines)) lines = 1;

      height = Math.round(lines * getRatio(lineHeight) * lineHeight);



      if(lines > 1 && leading) height += Math.round((lines - 1) * leading);



      var alternate = dom.create((ua.gecko || ua.opera) ? 'noembed' : 'span');

      alternate.className = CSS_ALTERNATE;

      

      var contentObj = handleContent(node, alternate);

      var $ = contentObj.content.match(/<br>/g);

      var extraLines = $ ? $.length : 0;



      var vars = ['content=' + contentObj.content, 'chars=' + contentObj.chars,

                  'links=' + contentObj.links, 'targets=' + contentObj.targets,

                  'w=' + width, 'h=' + height, 

                  'lines=' + lines, 'extralines=' + extraLines,

                  'size=' + lineHeight, 'zoom=' + dom.getZoom(), 'css=' + css];



      var callbackName = 'sIFR_callback_' + elementCount++;

      window[callbackName + '_DoFSCommand'] = (function(node) {

        return function(info, arg) {

          if(/(FSCommand\:)?resize/.test(info)) {

            var $ = arg.split(':');

            // The Flash is 7px too high

/*            if($[0] == 'height') $[1] = parseInt($[1]) - 7;*/

            node.firstChild.setAttribute($[0], $[1]);

            // Here comes another story!

            //

            // Good old Safari (saw this in 2.0.3) will *not* repaint the 

            // Flash movie with the new dimensions *until* the document

            // receives a UIEvent. I haven't tested this throroughly, so it

            // might respond to other events as well.

            //

            // The solution is to trick Safari into thinking that the `embed`

            // element has changed, this is done by adding an empty string

            // to it's `innerHTML`. Be aware though that adding this string

            // to `node` (the parent of the `embed` element) will immediately

            // crash Safari.

            //

            // Just to be sure this hack is applied to all browsers which

            // implement the KHTML engine.

            //

            /*:=todo Test this bug in older browsers to see if it occurs there

                      as well.

            */

            if(ua.khtml) node.firstChild.innerHTML += '';

          }

        }

      })(node);

      hacks.fscommand.setup(callbackName);



      var flash;

      if(ua.isIE) {

        flash = [

          '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" sifr="true" width="',

          ua.zoomSupport ? width : '100%', '" height="', height, '" class="', CSS_FLASH, '">',

          '<param name="movie" value="', src, '?', encodeURI(vars.join('&amp;')), '"></param>',

          '<param name="allowScriptAccess" value="always"></param>',

          '<param name="quality" value="best"></param>',

          '<param name="wmode" value="', wmode, '"></param>',

          '<param name="bgcolor" value="', backgroundColor, '"></param>',

          '</object>'

        ].join('');

      } else {

        flash = [

          '<embed class="', CSS_FLASH, '" type="application/x-shockwave-flash" src="', 

          src,'" quality="best" flashvars="', 

          encodeURI(vars.join('&amp;')), '" width="', ua.zoomSupport ? width : '100%', 

          '" height="', height, '" wmode="', wmode, '" bgcolor="', backgroundColor, '" name="',

          callbackName, '" allowScriptAccess="always" sifr="true"></embed>'

        ].join('');

      }



      dom.setInnerHtml(node, flash);

      dom.appendNode(node, alternate);

      dom.addClass(CSS_REPLACED, node);

    }



    hacks.fragmentIdentifier.restore();

  };

  

  /*=:private

    Walks through the childNodes of `source` and copies to `alternate`. Also

    generates a text representation of these childNodes.

    

    Returns:

    * string: the text representation.

      

    Notes:

    * A number of items are still to do. See the individual comments for that.

    * This method does not recursion because it'll be necessary to keep a 

      count of all links and their URIs. This is easier without recursion.

  */

  function handleContent(source, alternate) {

    //:=todo add filters

    var stack = [];

    var nodes = source.childNodes;

    //:=todo dojo string builder for speed?

    // We start the arrays with default values. This stops us from having to

    // check if the array is empty when parsing the content. Don't mess with

    // this, it works.

    var content = [''];

    var chars = [','];

    var links = [];

    var targets = [];



    var i = 0;

    while(i < nodes.length) {

      var node = nodes[i];



      if(stack.length == 0) alternate.appendChild(node.cloneNode(true));



      if(node.nodeType == 3) {

        var text = util.normalize(node.nodeValue);



        if(chars[chars.length - 1] != ',' && /^\s/.test(text)) {

          util.push(content, ',');

          util.push(chars, ',');

        }



        var words = text.replace(/\s$/, '').split(/\s/);

        var j = 0;



        while(j < words.length) {

          if(words[j].length == 0) words.splice(j, 1);

          else {

            if(chars[chars.length - 1] != ',') chars[chars.length - 1] += words[j].length;

            else util.push(chars, words[j].length);

            

            util.push(content, words[j].replace(/\,/g, '%2C')); // Comma is used as a delimiter



            if(j < words.length - 1) {

              util.push(content, ',');

              util.push(chars, ',');

            }



            j++;

          }

        }



        if(/\s$/.test(text) && chars[chars.length - 1] != ',') {

          util.push(content, ',');

          util.push(chars, ',');

        }

      }



      if(node.nodeType == 1) {

        var attributes = [];

        var nodeName = node.nodeName.toLowerCase();

        

        var className = node.className || '';

        // If there are multiple classes, look for the specified sIFR class

        if(/\s+/.test(className)) {

          if(className.indexOf(CSS_CLASS)) className = className.match('(\\s|^)' + CSS_CLASS + '-([^\\s$]*)(\\s|$)')[2];

          // or use the first class

          else className = className.match(/^([^\s]+)/)[1];

        }

        if(className != '') util.push(attributes, 'class="' + className + '"');



        switch(nodeName) {

          case 'a':

            var href = node.getAttribute('href') || '';

            var target = node.getAttribute('target') || '';

            if(href != '') {

              util.push(links, escape(href));

              util.push(targets, escape(target));

              util.push(attributes, 'href="asfunction:sIFR.followLink,' + (links.length - 1) +  '"');

            }

            break;

          case 'br':

            if(/^<br>\s*$/.test(content[content.length - 1])) util.push(chars, 0);



            if(chars[chars.length - 1] != ',') {

              util.push(content, ',');

              util.push(chars, ',');

            }

            break;

        }



        // The tag needs to be one string in order to detect sequenced <br> tags

        util.push(content,   

          '<' + nodeName 

          + (attributes.length > 0 ? ' ' : '') 

          + escape(attributes.join(' ')) + '>'

        );



        //:=todo pass to tag filters to get attributes (this.filters.filterNode)

        if(node.hasChildNodes()) {

          // Push the current index to the stack and prepare to iterate

          // over the childNodes.

          util.push(stack, i);

          i = 0;

          nodes = node.childNodes;

          continue;

        } 

      }



      if(stack.length > 0 && !node.nextSibling) {

        // Iterating the childNodes has been completed. Go back to the position

        // before we started the iteration. If that position was the last child,

        // go back even further.

        do {

          i = stack.pop();

          nodes = node.parentNode.parentNode.childNodes;

          node = nodes[i];

          if(node) util.push(content, '</', node.nodeName.toLowerCase(), '>');

        } while(i < nodes.length && stack.length > 0);

      }

      

      i++;

    }

    

    // Force the `content` and `chars` arrays into a known shape, depending

    // on the DOM the arrays might be different and carry extraneous 

    // information, this will be sliced out when the function returns.

    if(content[content.length - 1] != ',') util.push(content, ',');

    if(content[1] != ',') content.splice(1, 0, ',');

    if(chars[chars.length - 1] != ',') util.push(chars, ',');

    if(chars[1] != ',') chars.splice(1, 0, ',');



    return {

      content: content.slice(2, content.length - 1).join(''), 

      chars: chars.slice(2, chars.length - 1).join(''),

      links: links.join(','),

      targets: targets.join(',')

    };

  }

};





//text pasted in

sIFR.prefetch({

src: 'sifr/sifr.swf',

highsrc: 'sifr/sifr.swf'

});



sIFR.compatMode = true;

sIFR.activate();



sIFR.replace({

selector: 'h1',

src: 'sifr/sifr.swf',

highsrc: 'sifr/sifr.swf',

css: {

'.sIFR-root' : { 'color': '#1a8aca' , 'font-weight': 'bold' },

'a': { 'text-decoration': 'none' },

'a:link': { 'color': '#650500' },

'a:hover': { 'color': '#4F4F4F' }

},

paddingTop: 20

});



