var parseSelector = (function() {
	var SEPERATOR       = /\s*,\s*/
	var WHITESPACE      = /\s*([\s>+~(),]|^|$)\s*/g;
	var IMPLIED_ALL     = /([\s>+~,]|[^(]\+|^)([#.:@])/g;
	var STANDARD_SELECT = /^[^\s>+~]/;
	var STREAM          = /[\s#.:>+~()@]|[^\s#.:>+~()@]+/g;
	
	function parseSelector(selector, node) {
		node = node || document.documentElement;
		var argSelectors = selector.split(SEPERATOR), result = [];

		for(var i = 0; i < argSelectors.length; i++) {
			var nodes = [node], stream = toStream(argSelectors[i]);
			for(var j = 0; j < stream.length;) {
				var token = stream[j++], filter = stream[j++], 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;
	}

	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++) a.push(enumerable[i]);
			return a;
		}
	};
	
	var dom = {
		isTag: function(node, tag) {
			return (tag == '*') || (tag.toLowerCase() == node.nodeName.toLowerCase());
		},
	
		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) {
			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)) result.push(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)) result.push(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;
})();

var sIFR = new function() {
  //=:private Constant reference to the Singleton instance
  var SIFR = this;

  var CSS_ACTIVE        = 'sIFR-active';
  var CSS_REPLACED      = 'sIFR-replaced';
  var CSS_REPLACING     = 'sIFR-replacing';
  var CSS_FLASH         = 'sIFR-flash';
  var CSS_IGNORE        = 'sIFR-ignore';
  var CSS_ALTERNATE     = 'sIFR-alternate';
  var CSS_CLASS         = 'sIFR-class';
  var CSS_LAYOUT        = 'sIFR-layout';
  var XHTML_NS          = 'http://www.w3.org/1999/xhtml';
  var MIN_FONT_SIZE     = 6;
  var MAX_FONT_SIZE     = 126;
  var MIN_FLASH_VERSION = 8;
  var PREFETCH_COOKIE   = 'SIFR-PREFETCHED';
  //= Whitespace string each whitespace character is replaced with, as per `preserveSingleWhitespace`.
  var SINGLE_WHITESPACE = ' ';
  var DEFAULT_RATIOS    = [10, 1.55, 19, 1.45, 32, 1.35, 71, 1.30, 1.25]; //= Ratio list for Verdana
  var FLASH_PADDING_BOTTOM = 5;

  this.isActive                 = false;
  this.isEnabled                = true;
  this.hideElements             = true;
  this.replaceNonDisplayed      = false;
  this.preserveSingleWhitespace = false;
  this.fixWrap                  = true;
  this.fixHover                 = true;
  this.registerEvents           = true;
  this.setPrefetchCookie        = true;
  this.cookiePath               = '/';
  this.domains                  = [];
  this.fromLocal                = true;
  this.forceClear               = false;
  this.forceWidth               = false;
  this.fitExactly               = false;
  this.forceTextTransform       = true;
  this.useDomContentLoaded      = true;
  this.debugMode                = false;
  this.hasFlashClassSet         = false;
  this.delayCss                 = false;
  
  var elementCount = 0; // The number of replaced elements.
  var hasPrefetched = false, isInitialized = false;
  var callbacks = [];

  var dom = new function() {
    this.getBody = function() {
      var nodes = document.getElementsByTagName('body');
      if(nodes.length == 1) return nodes[0];
      return null;
    };

    this.addClass = function(name, node) {
      if(node) node.className = ((node.className || '') == '' ? '' : node.className + ' ') + name;
    };
    
    this.removeClass = function(name, node) {
      if(node) node.className = node.className.replace(new RegExp('(^|\\s)' + name + '(\\s|$)'), '').replace(/^\s+|(\s)\s+/g, '$1');
    }

    this.hasClass = function(name, node) {
      return new RegExp('(^|\\s)' + name + '(\\s|$)').test(node.className);
    };
    
    this.hasOneOfClassses = function(names, node) {
      for(var i = 0; i < names.length; i++) {
        if(this.hasClass(names[i], node)) return true;
      }
      return false;
    };

    this.create = function(name) {
      if(document.createElementNS) return document.createElementNS(XHTML_NS, name);
      return document.createElement(name);
    }
    
    this.setInnerHtml = function(node, html) {
      if(ua.innerHtmlSupport) node.innerHTML = html;
      else if(ua.xhtmlSupport){
        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.nodeFromHtml = function(html) {
      var temp = this.create('div');
      temp.innerHTML = html;
      return temp.firstChild;
    };
    
    this.getComputedStyle = function(node, property) {
      var result;
      if(document.defaultView && document.defaultView.getComputedStyle) {
        result = document.defaultView.getComputedStyle(node, null)[property];
      } else if(node.currentStyle) result = node.currentStyle[property];
      return result || ''; // Ensuring a string.
    }

    this.getStyleAsInt = function(node, property, requirePx) {
      var value = this.getComputedStyle(node, property);
      if(requirePx && !/px$/.test(value)) return 0;
      
      value = parseInt(value);
      return isNaN(value) ? 0 : value;
    }

    this.getZoom = function() {
      return hacks.zoom.getLatest();
    }
  };
  this.dom = dom;

  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.quicktime        = false;

    this.opera            = ua.indexOf('opera') > -1;
    this.konqueror        = product.indexOf('konqueror') > -1;
    this.ie               = false/*@cc_on || true @*/;
    this.ieSupported      = this.ie && !/ppc|smartphone|iemobile|msie\s5\.5/.test(ua)/*@cc_on && @_jscript_version >= 5.5 @*/
    this.ieWin            = this.ie && this.windows/*@cc_on && @_jscript_version >= 5.1 @*/;
    this.windows          = this.windows && (!this.ie || this.ieWin);
    this.ieMac            = this.ie && this.macintosh/*@cc_on && @_jscript_version < 5.1 @*/;
    this.macintosh        = this.macintosh && (!this.ie || this.ieMac);
    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';

    this.operaVersion     = this.opera     && /.*opera(\s|\/)(\d+\.\d+)/.exec(ua) ? parseInt(RegExp.$2) : 0;
    this.webkitVersion    = this.webkit    && /.*applewebkit\/(\d+).*/.exec(ua)   ? parseInt(RegExp.$1) : 0;
    this.geckoBuildDate   = this.gecko     && /.*gecko\/(\d{8}).*/.exec(ua)       ? parseInt(RegExp.$1) : 0;
    this.konquerorVersion = this.konqueror && /.*konqueror\/(\d\.\d).*/.exec(ua)  ? parseInt(RegExp.$1) : 0;

    this.flashVersion     = 0;
    if(this.ieWin) {
      var axo;
      var stop = false;
      try {
	      axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7');
      } catch(e) {
        // In case the Flash 7 registry key does not exist, we need to test for specific 
        // Flash 6 installs before we can use the general key. 
        // See also <http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/>.
        // Many thanks to Geoff Sterns and Bobby van der Sluis for clarifying problem and providing
        // examples of non-crashing code.
    		try {
    		  axo                   = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6');
      		this.flashVersion     = 6;
          axo.AllowScriptAccess = 'always';
        } catch(e) { stop = this.flashVersion == 6; }

				if(!stop) try { axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');	} catch(e) {}}

      if(!stop && axo) this.flashVersion = parseFloat(/([\d,?]+)/.exec(axo.GetVariable('$version'))[1].replace(/,/g, '.'));
    } 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' && mime.enabledPlugin.description.toLowerCase().indexOf('quicktime') > -1) {
          this.flashVersion = 0;
          this.quicktime = true;
        }
        i++;
      }
    }

    this.flash = this.flashVersion >= MIN_FLASH_VERSION;

    // There are other conditions, but these are ruled out in `computedStyledSupport` or `supported`.
    this.transparencySupport  = this.macintosh || this.windows;

    this.computedStyleSupport = this.ie || document.defaultView && document.defaultView.getComputedStyle 
      && (!this.gecko || this.geckoBuildDate >= 20030624); // Older Geckos have trouble with `getComputedStyle()`

    this.css = true;
    // Verify CSS support. We'll be changing the color of the head element, as it won't affect
    // page rendering and no other elements (other than HTML) can be relied up-on at this point.
    if(this.computedStyleSupport) {
      try { // Not sure if there are user agents which will disallow this
        var node = document.getElementsByTagName('head')[0];
        node.style.backgroundColor = '#FF0000';
        var color = dom.getComputedStyle(node, 'backgroundColor'); // Safari will return null, Konqueror an empty string
        this.css = !color || /\#F{2}0{4}|rgb\(255,\s?0,\s?0\)/i.test(color);
        node = null;
      } catch(e) {}}
    
    this.xhtmlSupport = !!window.DOMParser && !!document.importNode;
    try { 
      var n = dom.create('span');
      if(!this.ieMac) n.innerHTML = 'x'; // Need to test for IE/Mac, since the code will still be executed in IE/Mac.
      this.innerHtmlSupport = n.innerHTML == 'x';
    } catch (e) { this.innerHtmlSupport = false; }
    
    this.zoomSupport       = !!(this.opera && document.documentElement);
    this.geckoXml          = this.gecko && (document.contentType || '').indexOf('xml') > -1;

    this.requiresPrefetch  = this.ieWin || this.khtml;
    this.verifiedKonqueror = false;

    this.supported         = this.flash && this.css && (!this.ie || this.ieSupported) 
      && (!this.opera || this.operaVersion >= 8) && (!this.webkit || this.webkitVersion >= 412)  
      && (!this.konqueror || this.konquerorVersion > 3.5) && this.computedStyleSupport
      && (this.innerHtmlSupport || !this.khtml && this.xhtmlSupport);
  };
  this.ua = ua;

  var util = new function() {
    function capitalize($) {
      return $.toUpperCase();
    }
    
    this.normalize = function(str) {
      if(SIFR.preserveSingleWhitespace) return str.replace(/\s/g, SINGLE_WHITESPACE);
      // Normalize to the first whitespace, and then make sure no nbsp characters are preserved as Flash doesn't seem to support these.
      return str.replace(/(\s)\s+/g, '$1').replace(/\xA0/, SINGLE_WHITESPACE);
    };
    
    this.textTransform = function(type, str) {
      switch(type) {
        case 'uppercase':
          str = str.toUpperCase();
          break;
        
        case 'lowercase':
          str = str.toLowerCase();
          break;
          
        case 'capitalize':
          var strCopy = str;
          str = str.replace(/^\w|\s\w/g, capitalize);
          if(str.indexOf('function capitalize') != -1) {
            var substrs = strCopy.replace(/(^|\s)(\w)/g, '$1$1$2$2').split(/^\w|\s\w/g);
            str = '';
            for(var i = 0; i < substrs.length; i++) str += substrs[i].charAt(0).toUpperCase() + substrs[i].substring(1);
          }
          break;
      }
      
      return str;
    };
    
    this.toHexString = function(str) {
      if(typeof(str) != 'string' || !str.charAt(0) == '#' || str.length != 4 && str.length != 7) return str;
      
      str = str.replace(/#/, '');
      if(str.length == 3) str = str.replace(/(.)(.)(.)/, '$1$1$2$2$3$3');

      return '0x' + str;
    };

    this.toJson = function(obj) {
      var json = '';

      switch(typeof(obj)) {
        case 'string':
          json = '"' + obj + '"';
          break;
        case 'number':
        case 'boolean':
          json = obj.toString();
          break;
        case 'object':
          json = [];
          for(var prop in obj) {
            if(obj[prop] == Object.prototype[prop]) continue;
            json.push('"' + prop + '":' + util.toJson(obj[prop]));
          }
          json = '{' + json.join(',') + '}';
          break;
      }

      return json;
    };
    
    this.convertCssArg = function(arg) {
      if(!arg) return {};
      if(typeof(arg) == 'object') {
        if(arg.constructor == Array) arg = arg.join('');
        else return arg;
      }

      var obj = {};
      var rules = arg.split('}');

      for(var i = 0; i < rules.length; i++) {
        var $ = rules[i].match(/([^\s{]+)\s*\{(.+)\s*;?\s*/);
        if(!$ || $.length != 3) continue;

        if(!obj[$[1]]) obj[$[1]] = {};

        var properties = $[2].split(';');
        for(var j = 0; j < properties.length; j++) {
          var $2 = properties[j].match(/\s*([^:\s]+)\s*\:\s*([^\s;]+)/);
          if(!$2 || $2.length != 3) continue;
          obj[$[1]][$2[1]] = $2[2];
        }
      }

      return obj;
    };

    this.extractFromCss = function(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.cssToString = function(arg) {
      var css = [];
      for(var selector in arg) {
        var rule = arg[selector];
        if(rule == Object.prototype[selector]) continue;

        css.push(selector, '{');
        for(var property in rule) {
          if(rule[property] == Object.prototype[property]) continue;
          css.push(property, ':', rule[property], ';');
        }
        css.push('}');
      }

      return escape(css.join(''));
    };

    this.bind = function(scope, method) {
      return function() {
        scope[method].apply(scope, arguments);
      };
    };
  };
  this.util = util;

  var hacks = {};
  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
  };

  // The goal here is not to prevent usage of the Flash movie, but running sIFR on possibly translated pages
  function isValidDomain() {
    if(SIFR.domains.length == 0) return true;

    var domain = '';
    try { // When trying to access document.domain on a Google-translated page with Firebug, I got an exception.
      domain = document.domain;
    } catch(e) {};

    if(SIFR.fromLocal && sIFR.domains[0] != 'localhost') sIFR.domains.unshift('localhost');

    for(var i = 0; i < SIFR.domains.length; i++) {
      var match = SIFR.domains[i];
      if(match == '*' || match == domain) return true;

      var wildcard = match.lastIndexOf('*');
      if(wildcard > -1) {
        match = match.substr(wildcard + 1);
        var matchPosition = domain.lastIndexOf(match);
        if(matchPosition > -1 && (matchPosition + match.length) == domain.length) return true;
      }
    }

    return false;
  }

  this.activate = function(/* … */) {
    if(!ua.supported || !this.isEnabled || this.isActive || !isValidDomain()) return;
    
    if(arguments.length > 0) this.prefetch.apply(this, arguments);

    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();

      // Remove handlers to prevent memory leak in Firefox 1.5, but only after onload.
      if(evt && evt.type == 'load') {
        if(document.removeEventListener) document.removeEventListener('DOMContentLoaded', handler, false);
        if(window.removeEventListener) window.removeEventListener('load', handler, false);
      }
    }
    
    if(window.addEventListener) {
      // Opera and also Safari load JavaScript and CSS synchrously, so we can't rely on 
      // correct information to do the replacements. Hence, we only use DOMContentLoaded
      // for Gecko-based browsers. In other cases, add a normal load event to the document.
      //:note The load event might be redundant, needs testing!
      if(SIFR.useDomContentLoaded && ua.gecko) document.addEventListener('DOMContentLoaded', handler, false);
      window.addEventListener('load', handler, false);
    } else if(ua.ieWin) {
      if(SIFR.useDomContentLoaded) { // Replacing before onload breaks prefetching
        document.write('<scr' + 'ipt id=__sifr_ie_onload defer src=//:><\/script>');
        document.getElementById('__sifr_ie_onload').onreadystatechange = function() {
          if(this.readyState == 'complete') {
            handler();
            this.removeNode();
          }
        };
      }
      window.attachEvent('onload', handler);
    }
  };

  this.setFlashClass = function() {
    if(this.hasFlashClassSet) return;

    dom.addClass(CSS_ACTIVE, dom.getBody() || document.documentElement);
    this.hasFlashClassSet = true;
  };

  this.removeFlashClass = function() {
    if(!this.hasFlashClassSet) return;

    dom.removeClass(CSS_ACTIVE, dom.getBody());
    dom.removeClass(CSS_ACTIVE, document.documentElement);
    this.hasFlashClassSet = false;
  }

  this.initialize = function() {
    if(isInitialized || !this.isActive || !this.isEnabled) return;

    isInitialized = true;
    replaceKwargsStore.replaceAll();
    clearPrefetch();
  };

  function getSource(src) {
    if(typeof(src) != 'string') {
      // This is a niciety to allow you to create general configuration objects
      // for the prefetch as well as the replacement. You could create constructs
      // like `{src: {src: {/*....*/}}}`, but that's not really a problem.
      if(src.src) src = src.src;

      // It might be a string now...
      if(typeof(src) != 'string') {
        var versions = [];
        for(var version in src) if(src[version] != Object.prototype[version]) versions.push(version);
        versions.sort().reverse();

        var result = '';
        var i = -1;
        while(!result && ++i < versions.length) {
          if(parseFloat(versions[i]) <= ua.flashVersion) result = src[versions[i]];
        }
        
        src = result;
      }
    }
    
    if(!src && SIFR.debugMode) throw new Error("sIFR: Could not determine appropriate source");
    
    // 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. This turns `/foo.swf` in `http://example.com/foo.swf`.
    if(ua.ie && src.charAt(0) == '/') {
      src = window.location.toString().replace(/([^:]+)(:\/?\/?)([^\/]+).*/, '$1$2$3') + src;
    }
    
    return src;
  }

  this.prefetch = function(/* … */) {
    if(!ua.requiresPrefetch || !ua.supported || !this.isEnabled || !isValidDomain()) return;
    if(this.setPrefetchCookie && 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.setPrefetchCookie) document.cookie = PREFETCH_COOKIE + '=true;path=' + this.cookiePath;
    } catch(e) { if(SIFR.debugMode) throw e; }
  };

  function prefetchIexplore(args) {
    for(var i = 0; i < args.length; i++) {
      document.write('<script defer type="sifr/prefetch" src="' + getSource(args[i]) + '"></script>');
    }
  }

  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 nodes = document.getElementsByTagName('script');
      for(var i = nodes.length - 1; i >= 0; i--) {
        var node = nodes[i];
        if(node.type == 'sifr/prefetch') node.parentNode.removeChild(node);
      }
    } catch(e) {}}

  // Gives a font-size to required vertical space ratio
  function getRatio(size, ratios) {
    for(var i = 0; i < ratios.length; i += 2) {
      if(size <= ratios[i]) return ratios[i + 1];
    }
    return ratios[ratios.length - 1];
  }

  function getFilters(obj) {
    var filters = [];
    for(var filter in obj) {
      if(obj[filter] == Object.prototype[filter]) continue;

      var properties = obj[filter];
      filter = [filter.replace(/filter/i, '') + 'Filter'];

      for(var property in properties) {
        if(properties[property] == Object.prototype[property]) continue;
        filter.push(property + ':' + escape(util.toJson(util.toHexString(properties[property]))));
      }

      filters.push(filter.join(','));
    }

    return filters.join(';');
  }
  
  function calculate(node) {
    var lineHeight, lines;
    if(!ua.ie) { //:=todo Only do once for each selector?
      lineHeight = dom.getStyleAsInt(node, 'lineHeight');
      lines = Math.floor(dom.getStyleAsInt(node, 'height') / lineHeight);
    } else if(ua.ie) { // IE returs computed style in the original units, which is quite useless.
      var html = node.innerHTML;

      // Without these settings, we won't be able to get the rects properly. getClientRects()
      // won't work on elements having layout or that are hidden.
      node.style.visibility  = 'visible';
      node.style.overflow    = 'visible';
      node.style.position    = 'static';
      node.style.zoom        = 'normal';
      node.style.writingMode = 'lr-tb';
      node.style.width       = node.style.height = 'auto';
      node.style.maxWidth    = node.style.maxHeight = node.style.styleFloat  = 'none';
              
      var rectNode = node;
      var hasLayout = node.currentStyle.hasLayout;
      if(hasLayout) {
        dom.setInnerHtml(node, '<div class="' + CSS_LAYOUT + '">X<br />X<br />X</div>');
        rectNode = node.firstChild;
      } else dom.setInnerHtml(node, 'X<br />X<br />X');

      var rects = rectNode.getClientRects();
      lineHeight = rects[1].bottom - rects[1].top;

      // In IE, the lineHeight is about 1.25 times the height in other browsers.
      lineHeight = Math.ceil(lineHeight * 0.8);

      if(hasLayout) {
        dom.setInnerHtml(node, '<div class="' + CSS_LAYOUT + '">' + html + '</div>');
        rectNode = node.firstChild;
      } else dom.setInnerHtml(node, html);
      rects = rectNode.getClientRects();
      lines = rects.length;
      
      if(hasLayout) dom.setInnerHtml(node, html);

      // 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.visibility = node.style.width = node.style.height = node.style.maxWidth 
      = node.style.maxHeight = node.style.overflow = node.style.styleFloat
      = node.style.position = node.style.zoom = node.style.writingMode = '';
    }
    
    return {lineHeight: lineHeight, lines: lines};
  }

  this.replace = function(kwargs, mergeKwargs) {
    if(!ua.supported) return;
    
    // This lets you specify to kwarg objects so you don't have to repeat common settings.
    // The first object will be merged with the second, while properties in the second 
    // object have priority over those in the first. The first object is unmodified
    // for further use, the resulting second object will be used in the replacement.
    if(mergeKwargs) {
      for(var property in kwargs) {
        if(typeof(mergeKwargs[property]) == 'undefined') mergeKwargs[property] = kwargs[property];
      }
      kwargs = mergeKwargs;
    }
    
    if(!isInitialized) return replaceKwargsStore.kwargs.push(kwargs);
    if(hacks.synchronizer.isBlocked) return blockedReplaceKwargsStore.kwargs.push(kwargs);

    var nodes = kwargs.elements;
    if(!nodes && parseSelector) nodes = parseSelector(kwargs.selector);
    if(nodes.length == 0) return;

    this.setFlashClass();

    var src = getSource(kwargs.src);
    var css = util.convertCssArg(kwargs.css);
    var filters = getFilters(kwargs.filters);
    
    var forceClear = (kwargs.forceClear == null) ? SIFR.forceClear : kwargs.forceClear;
    var fitExactly = (kwargs.fitExactly == null) ? SIFR.fitExactly : kwargs.fitExactly;
    var forceWidth = fitExactly || (kwargs.forceWidth == null ? SIFR.forceWidth : kwargs.forceWidth);

    var leading         = parseInt(util.extractFromCss(css, '.sIFR-root', 'leading')) || 0;
    var fontSize        = util.extractFromCss(css, '.sIFR-root', 'font-size', true) || 0;
    var backgroundColor = util.extractFromCss(css, '.sIFR-root', 'background-color', true) || '#FFFFFF';
    var kerning         = util.extractFromCss(css, '.sIFR-root', 'kerning', true) || '';
    var gridFitType     = kwargs.gridFitType || util.extractFromCss(css, '.sIFR-root', 'text-align') == 'right' ? 'subpixel' : 'pixel';
    var textTransform   = SIFR.forceTextTransform ? util.extractFromCss(css, '.sIFR-root', 'text-transform', true) || 'none' : 'none';
    var opacity         = util.extractFromCss(css, '.sIFR-root', 'opacity', true) || '100';
    var pixelFont       = kwargs.pixelFont || false;
    var ratios          = kwargs.ratios || DEFAULT_RATIOS;

    if(parseInt(fontSize).toString() != fontSize && fontSize.indexOf('px') == -1) fontSize = 0; // We only support pixel sizes
    else fontSize = parseInt(fontSize);
    if(parseFloat(opacity) < 1) opacity = 100 * parseFloat(opacity); // Make sure to support percentages and decimals

    var fixHover = null;
    var cssText = '';
    // Alignment is handled by the browser in this case.
    if(fitExactly) util.extractFromCss(css, '.sIFR-root', 'text-align', true);
    if(!kwargs.modifyCss) {
      cssText = util.cssToString(css);
      fixHover = SIFR.fixHover && cssText.indexOf('\%3Ahover') > -1;
    }
    

    var wmode = kwargs.wmode || '';
    if(wmode == 'transparent') {
			if(!ua.transparencySupport)	wmode = 'opaque';
			else backgroundColor = 'transparent';
		}

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

      if(!ua.verifiedKonqueror) {
        if(dom.getComputedStyle(node, 'lineHeight').match(/e\+08px/)) {
          ua.supported = SIFR.isEnabled = false;
          this.removeFlashClass();
          return;
        }
        ua.verifiedKonqueror = true;
      }

      if(dom.hasOneOfClassses([CSS_REPLACED, CSS_REPLACING, CSS_IGNORE, CSS_ALTERNATE], node)) continue;

      // Elements with no height (`0` in IE, `undefined` in Safari) are usually `display: none`.
      // Let's attempt to display them and replace them anyway.
      var resetDisplay = false;

      // Without a height or width, we can't function. Tring again with display:block…
      // See also <http://www.snook.ca/archives/javascript/safari2_display-none_getcomputedstyle/>.
      if(!node.offsetHeight ||!node.offsetWidth) {
        if(!SIFR.replaceNonDisplayed) continue;

        node.style.display = 'block';
        if(!node.offsetHeight || !node.offsetWidth) { // If they are still without height or width, don't replace them.
          node.style.display = '';
          continue;
        }
        resetDisplay = true;
      }

      if(forceClear && ua.gecko) node.style.clear = 'both';

      // If the text doesn't wrap nicely, the width becomes too large and Flash
      // can't adjust for it. By setting the text to just "X" we can be sure
      // we get the correct width.
      var html = null;
      if(SIFR.fixWrap && ua.ie && dom.getComputedStyle(node, 'display') == 'block') {
        html = node.innerHTML;
        dom.setInnerHtml(node, 'X');
      }

      // Get the width (again to approximate the final size). The computed width
      // may not be a pixel unit in IE, in which case we try to calculate using
      // padding and borders and the offsetWidth.
      var width = dom.getStyleAsInt(node, 'width', ua.ie);
      if(width == 0) {
        var paddingRight  = dom.getStyleAsInt(node, 'paddingRight', true);
        var paddingLeft   = dom.getStyleAsInt(node, 'paddingLeft', true);
        var borderRight   = dom.getStyleAsInt(node, 'borderRightWidth', true);
        var borderLeft    = dom.getStyleAsInt(node, 'borderLeftWidth', true);
        width = node.offsetWidth - paddingLeft - paddingRight - borderLeft - borderRight;
      }

      if(html && SIFR.fixWrap && ua.ie) dom.setInnerHtml(node, html);

      var lineHeight, lines;
      if(!fontSize) {
        var calculation = calculate(node);
        lineHeight      = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, calculation.lineHeight));
        if(pixelFont) lineHeight = Math.max(8, 8 * Math.round(lineHeight / 8));

        lines = calculation.lines;
        if(isNaN(lines) || !isFinite(lines) || lines == 0) lines = 1;

        if(lines > 1 && leading) height += Math.round((lines - 1) * leading);
      } else {
        lineHeight = fontSize;
        lines      = 1;
      }

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

      // We have all the info we need, reset the display setting now.
      if(resetDisplay) node.style.display = '';
      if(forceClear && ua.gecko) node.style.clear = '';

      // I wanted to use `noembed` here, but unfortunately FlashBlock only works with `span.sIFR-alternate`
      var alternate = dom.create('span');
      alternate.className = CSS_ALTERNATE;

      // Clone the original content to the alternate element.
      var contentNode = node.cloneNode(true);
      for(var j = 0, l = contentNode.childNodes.length; j < l; j++) {alternate.appendChild(contentNode.childNodes[j].cloneNode(true));}

      // Allow the sIFR content to be modified
      if(kwargs.modifyContent) kwargs.modifyContent(contentNode, kwargs.selector);
      if(kwargs.modifyCss) cssText = kwargs.modifyCss(css, contentNode, kwargs.selector);
      if(fixHover == null) fixHover = SIFR.fixHover && cssText.indexOf('\%3Ahover') > -1;

      var content = handleContent(contentNode, textTransform);
      if(kwargs.modifyContentString) content = kwargs.modifyContentString(content, kwargs.selector);

      if(content == '') continue;
      var vars = ['content=' + content, // Don't touch this line!
                  'width=' + width, 'height=' + height, 'fitexactly=' + (fitExactly ? 'true' : ''),
                  'tunewidth=' + (kwargs.tuneWidth || ''), 'tuneheight=' + (kwargs.tuneHeight || ''),
                  'offsetleft=' + (kwargs.offsetLeft || ''), 'offsettop=' + (kwargs.offsetTop || ''),
                  'thickness=' + (kwargs.thickness || ''), 'sharpness=' + (kwargs.sharpness || ''), 
                  'kerning=' + kerning, 'gridfittype=' + gridFitType, 'zoomsupport=' + ua.zoomSupport, 
                  'flashfilters=' + filters, 'opacity=' + opacity, 'blendmode=' + (kwargs.blendMode || ''), 
                  'size=' + lineHeight, 'zoom=' + dom.getZoom(), 'css=' + cssText, 
                  'selectable=' + (kwargs.selectable == null ? 'true' : kwargs.selectable),
                  // Not strictly needed, but helps for the findRatios debug method
                  'lines=' + lines];
      var encodedVars = encodeURI(vars.join('&amp;'));

      var callbackName = 'sIFR_callback_' + elementCount++;
      var callbackInfo = new CallbackInfo(callbackName, vars, kwargs.onReplacement, fixHover);
      window[callbackName + '_DoFSCommand'] = (function(callbackInfo) {
        return function(info, arg) {
          callbackInfo.handle(info, arg);
        }
      })(callbackInfo);

      // Approach the final height to avoid annoying movements of the page
      height = Math.round(lines * getRatio(lineHeight, ratios) * lineHeight) + FLASH_PADDING_BOTTOM;

      var forcedWidth = forceWidth ? width : '100%';

      var flash;
      if(ua.ie) {
        flash = [
          '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="', callbackName,
           '" sifr="true" width="', forcedWidth, '" height="', height, '" class="', CSS_FLASH, '">',
            '<param name="movie" value="', src, '"></param>',
            '<param name="flashvars" value="', encodedVars, '"></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>',
            '<param name="name" value="', callbackName, '"></param>',
          '</object>',
          // Load in the callback code. Keep the <script> line exactly the same!!! (Yes, IE is that crappy)
          // Thanks to Tom Lee for the tip: <http://tom-lee.blogspot.com/2006/04/dynamically-inserting-fscommand.html>
          '<scr', 'ipt event=FSCommand(info,args) for=', callbackName, '>', 
            callbackName, '_DoFSCommand(info, args);',
          '</', 'script>' // End like this to prevent syntax error in IE/Mac.
        ].join('');
      } else {
        flash = [
          '<embed type="application/x-shockwave-flash"', (ua.opera || !SIFR.delayCss ? ' class="' + CSS_FLASH + '"' : ''),
          ' src="', src,'" quality="best" flashvars="', encodedVars, '" width="', forcedWidth, '" height="', height,
          '" wmode="', wmode, '" bgcolor="', backgroundColor, '" name="', callbackName, '" id="', callbackName,
          '" allowScriptAccess="always" sifr="true"></embed>'
        ].join('');
      }

      dom.setInnerHtml(node, flash);
      callbackInfo.flashNode = node.firstChild;
      callbackInfo.html = flash;
      callbacks.push(callbackInfo);
      node.appendChild(alternate);
      dom.addClass(SIFR.delayCss ? CSS_REPLACING : CSS_REPLACED, node);

      callbackInfo.setupFixHover();
    }

    hacks.fragmentIdentifier.restore();
  };

  /*=:private
    Walks through the childNodes of `source`. 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, textTransform) {
    var stack = [], content = [];
    var nodes = source.childNodes;

    var i = 0;
    while(i < nodes.length) {
      var node = nodes[i];

      if(node.nodeType == 3) {
        var text = util.normalize(node.nodeValue);
        text = util.textTransform(textTransform, text);
        // Escape reserved characters
        content.push(text.replace(/\%/g, '%25').replace(/\&/g, '%26').replace(/\,/g, '%2C').replace(/\+/g, '%2B'));
      }

      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) > -1) className = className.match('(\\s|^)' + CSS_CLASS + '-([^\\s$]*)(\\s|$)')[2];
          // or use the first class
          else className = className.match(/^([^\s]+)/)[1];
        }
        if(className != '') attributes.push('class="' + className + '"');

        if(nodeName == 'a') {
          var href = node.getAttribute('href') || '';
          var target = node.getAttribute('target') || '';
          attributes.push('href="' + href + '"', 'target="' + target + '"');
        }

        content.push('<' + nodeName + (attributes.length > 0 ? ' ' : '') + escape(attributes.join(' ')) + '>');

        if(node.hasChildNodes()) {
          // Push the current index to the stack and prepare to iterate
          // over the childNodes.
          stack.push(i);
          i = 0;
          nodes = node.childNodes;
          continue;
        } else if(!/^(br|img)$/i.test(node.nodeName)) content.push('</', node.nodeName.toLowerCase(), '>');
      }

      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) content.push('</', node.nodeName.toLowerCase(), '>');
        } while(i < nodes.length && stack.length > 0);
      }

      i++;
    }
  
    return content.join('').replace(/\n|\r/g, '');
  }

  function CallbackInfo(id, vars, replacementHandler, fixHover) {
    this.id         = id;
    this.vars       = vars;

    this._replacementHandler = replacementHandler;
    this._firedReplacementEvent = !(this._replacementHandler != null);
    this._fixHover = fixHover;
    this._setClasses = !SIFR.delayCss;
    this.html       = '';
    this._pings = 0;
  }
  
  CallbackInfo.prototype.getFlashNode = function() {
    return document.getElementById(this.id);
  };
  
  CallbackInfo.prototype.handle = function(info, arg) {
    if(/(FSCommand\:)?resize/.test(info)) {
      var flashNode = this.getFlashNode();

      var $ = arg.split(':');
      flashNode.setAttribute($[0], $[1]);

      if(!this._setClasses && $[0] == 'height') {
        // IE needs the Flash movie to be visible for the callbacks to work.
        if(!ua.ie && !ua.opera) dom.addClass(CSS_FLASH, flashNode);
        dom.removeClass(CSS_REPLACING, flashNode.parentNode);
        dom.addClass(CSS_REPLACED, flashNode.parentNode);
        this._setClasses = true;
      }
      
      // Here comes another story!
      //
      // Good old Safari (saw this in 2.0.3) does not see the resizing
      // of the Flash movie as requiring a repaint of the document. Because
      // of this, the movie won't be resized unless Safari is forced to.
      // This is done by requesting the `offsetHeight` on the Flash node.
      //
      // Just to be sure this hack is applied to all browsers which
      // implement the KHTML engine.
      if(ua.khtml) var repaint = flashNode.offsetHeight;
      
      if(!this._firedReplacementEvent) {
        this._replacementHandler(this);
        this._firedReplacementEvent = true;
      }
    } else if(/(FSCommand\:)?resetmovie/.test(info)) {this.resetMovie();} else if(/(FSCommand\:)?ping/.test(info)) {
      if(this._pings > 0) this.setupFixHover();
      this._pings++;
    } else if(this.debugHandler && /(FSCommand\:)?debug/.test(info)) {
      this.debugHandler(info, arg);
    }
  };
  
  CallbackInfo.prototype.call = function(type, value) {
    var flashNode = this.getFlashNode();
    if(!flashNode) return;

    flashNode.SetVariable('callbackType', type);
    flashNode.SetVariable('callbackValue', value);
    flashNode.SetVariable('callbackTrigger', true);
  };
  
  CallbackInfo.prototype.write = function(content) {
    this.call('write', content);
    this.vars[0] = 'content=' + content;
    this.html = this.html.replace(/(flashvars(=|\"\svalue=)\")[^\"]+/, '$1' + encodeURI(this.vars.join('&amp;')));
  };
  
  CallbackInfo.prototype.resetMovie = function() {
    var flashNode = this.getFlashNode();
    var node = flashNode.parentNode;
    node.replaceChild(dom.nodeFromHtml(this.html), flashNode);
    this.setupFixHover();
  };
  
  CallbackInfo.prototype.setupFixHover = function() {
    var flashNode = this.getFlashNode();
    if(!this._fixHover || !flashNode) return;
    
    var node = flashNode.parentNode;
    if(node.addEventListener) node.addEventListener('mouseout', util.bind(this, 'fixHover'), false);
    else if(node.attachEvent) node.attachEvent('onmouseout', util.bind(this, 'fixHover'));
  };
  
  CallbackInfo.prototype.fixHover = function() {this.call('resettext');};
};

/* ========== START SIFR_CONFIG_JS  ==============*/
var jvm_text = {
  src: 'http://next.jvm.de/movies/sifr/sifr_jvm_text.swf'};

var jvm_link = {
  src: 'http://next.jvm.de/movies/sifr/sifr_jvm_link.swf'};

sIFR.fitExactly = true;

sIFR.fixHover = true;

sIFR.prefetch(jvm_text, jvm_link);

sIFR.activate();

sIFR.replace(jvm_link, {
    selector: 'div.headline'
    ,css: [
      '.sIFR-root {text-align: left; font-weight: bold; text-transform: uppercase; background-color: #001400; font-size: 35px; leading: -8; color: #FFFFFF;}'
     ,'a{color: #FFFFFF; text-decoration: none;}'
     ,'a:hover{color: #001C00; text-decoration: none;}'
    ]
    ,tuneHeight: (-13)
    ,offsetTop: (-5)
    ,offsetLeft: (-1)
    ,wmode: "transparent"
  });

sIFR.replace(jvm_link, {
    selector: 'div.hero_headline'
    ,css: [
      '.sIFR-root {text-align: left; font-weight: bold; text-transform: uppercase; background-color: #001400; font-size: 45px; leading: -10; color: #FFFFFF;}'
     ,'a{color: #FFFFFF; text-decoration: none;}'
     ,'a:hover{color: #001C00; text-decoration: none;}'
]
    ,tuneHeight: (-15)
    ,offsetTop: (-5)
    ,offsetLeft: (-1)
    ,wmode: "transparent"
  });

sIFR.replace(jvm_text, {
    selector: 'h1'
    ,css: [
      '.sIFR-root {text-align: left; font-weight: bold; text-transform: uppercase; background-color: #001400; font-size: 35px; leading: -9; color: #FFFFFF;}'
     ,'a{text-decoration: none;}'
    ]
    ,tuneHeight: (-11)
    ,offsetTop: (-4)
    ,offsetLeft: (-1)
    ,wmode: "transparent"
  });

sIFR.replace(jvm_text, {
    selector: 'h2'
    ,css: ['.sIFR-root {text-align: left; font-weight: bold; text-transform: uppercase; background-color: #001400; font-size: 20px; leading: -5; color: #FFFFFF}']
    ,tuneHeight: (-8)
    ,offsetTop: (-3)
    ,wmode: "transparent"
  });
