// Event functions written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini
// http://dean.edwards.name/weblog/2005/10/add-event/

function addEvent(element, type, handler) {
	if (element.addEventListener) {
		element.addEventListener(type, handler, false);
	} else {
		// assign each event handler a unique ID
		if (!handler.$$guid) handler.$$guid = addEvent.guid++;
		// create a hash table of event types for the element
		if (!element.events) element.events = {};
		// create a hash table of event handlers for each element/event pair
		var handlers = element.events[type];
		if (!handlers) {
			handlers = element.events[type] = {};
			// store the existing event handler (if there is one)
			if (element["on" + type]) {
				handlers[0] = element["on" + type];
			}
		}
		// store the event handler in the hash table
		handlers[handler.$$guid] = handler;
		// assign a global event handler to do all the work
		element["on" + type] = handleEvent;
	}
};
// a counter used to create unique IDs
addEvent.guid = 1;

function removeEvent(element, type, handler) {
	if (element.removeEventListener) {
		element.removeEventListener(type, handler, false);
	} else {
		// delete the event handler from the hash table
		if (element.events && element.events[type]) {
			delete element.events[type][handler.$$guid];
		}
	}
};

function handleEvent(event) {
	var returnValue = true;
	// grab the event object (IE uses a global event object)
	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
	// get a reference to the hash table of event handlers
	var handlers = this.events[event.type];
	// execute each event handler
	for (var i in handlers) {
		this.$$handleEvent = handlers[i];
		if (this.$$handleEvent(event) === false) {
			returnValue = false;
		}
	}
	return returnValue;
};

function fixEvent(event) {
	// add W3C standard event methods
	event.preventDefault = fixEvent.preventDefault;
	event.stopPropagation = fixEvent.stopPropagation;
	return event;
};
fixEvent.preventDefault = function() {
	this.returnValue = false;
};
fixEvent.stopPropagation = function() {
	this.cancelBubble = true;
};

function $(id) {
	if(typeof id == 'string')
		return document.getElementById(id)
	return id
}

var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0, length = iterable.length; i < length; i++)
      results.push(iterable[i]);
    return results;
  }
}

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}

/*  -- The Animator Library is released under the BSD license:
	Copyright (c) 2006, Bernard Sumption. All rights reserved.
	
	Modified by Kyle Robertson, Feb 2007,
	Changes:
		- Replaced interpolation with a timed-based method
		- Minor trimming-of-the-fat (unused features)
		- Minor optimizations like replacing divide-by-2 with multiple-by-half
			(they probably don't make a huge difference, but I made them anyway)
*/
// Applies a sequence of numbers between 0 and 1 to a number of subjects
// construct - see setOptions for parameters
// Applies a sequence of numbers between 0 and 1 to a number of subjects
// construct - see setOptions for parameters
function Animator(options) {
	this.setOptions(options);
	this.timerDelegate = function(){this.onTimerEvent()}.bind(this);
	this.subjects = [];
	this.target = 0;
	this.state = 0;
};
Animator.prototype = {
	// apply defaults
	setOptions: function(options) {
		this.options = Animator.applyDefaults({
			interval: 20,  // time between animation frames
			duration: 600, // length of animation
			onComplete: function(){},
			onStep: function(){},
			transition: Animator.tx.easeInOut
		}, options);
		this.piByduration = Math.PI / this.options.duration;
	},
	// animate from the current state to provided value
	seekTo: function(to) {
		this.seekFromTo(this.state, to);
	},
	// animate from the current state to provided value
	seekFromTo: function(from, to) {
		this.target = Math.max(0, Math.min(1, to));
		this.state = Math.max(0, Math.min(1, from));
		this.from = this.state;
		this.time = (new Date()).getTime();
		if (!this.intervalId) {
			this.intervalId = window.setInterval(this.timerDelegate, this.options.interval);
		}
	},
	// animate from the current state to provided value
	jumpTo: function(to) {
		this.target = this.state = Math.max(0, Math.min(1, to));
		this.propagate();
	},
	// seek to the opposite of the current target
	toggle: function() {
		this.seekTo(1 - this.target);
	},
	// add a function or an object with a method setState(state) that will be called with a number
	// between 0 and 1 on each frame of the animation
	addSubject: function(subject) {
		this.subjects[this.subjects.length] = subject;
		return this;
	},
	// remove an object that was added with addSubject
	removeSubject: function(subject) {
		var newSubjects = []
		for (var i=0; i<this.subjects.length; i++) {
			if (this.subjects[i] != subject) {
				newSubjects[newSubjects.length] = this.subjects[i];
			}
		}
		this.subjects = newSubjects;
	},
	// remove all subjects
	clearSubjects: function() {
		this.subjects = [];
	},
	// forward the current state to the animation subjects
	propagate: function() {
		var value = this.options.transition(this.state);
		for (var i=0; i<this.subjects.length; i++) {
			if (this.subjects[i].setState) {
				this.subjects[i].setState(value);
			} else {
				this.subjects[i](value);
			}
		}
	},
	// called once per frame to update the current state
	onTimerEvent: function() {
		var eTime = (new Date()).getTime() - this.time;
		if (eTime < this.options.duration) {
			this.state = -(this.state < this.target ? 0.5 : (-0.5)) * (Math.cos(this.piByduration* eTime) - 1) + this.from;
		} else {
			this.state = this.target;
		}
		this.options.onStep.call();
		try {
			this.propagate();
		} finally {
			if (this.target == this.state) {
				window.clearInterval(this.intervalId);
				this.intervalId = null;
				this.options.onComplete.call(this);
			}
		}
	},
	// shortcuts
	play: function() {this.seekFromTo(0, 1)},
	reverse: function() {this.seekFromTo(1, 0)}
}
// merge the properties of two objects
Animator.applyDefaults = function(defaults, prefs) {
	prefs = prefs || {};
	var prop, result = {};
	for (prop in defaults) result[prop] = prefs[prop] || defaults[prop];
	return result;
}
// make an array from any object
Animator.makeArray = function(o) {
	if (o == null) return [];
	if (!o.length) return [o];
	var result = [];
	for (var i=0; i<o.length; i++) result[i] = o[i];
	return result;
}
// convert a dash-delimited-property to a camelCaseProperty (c/o Prototype, thanks Sam!)
Animator.camelize = function(string) {
	var oStringList = string.split('-');
	if (oStringList.length == 1) return oStringList[0];
	
	var camelizedString = string.indexOf('-') == 0
		? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
		: oStringList[0];
	
	for (var i = 1, len = oStringList.length; i < len; i++) {
		var s = oStringList[i];
		camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
	}
	return camelizedString;
}
// make a transition function that, like an object with momentum being attracted to a point,
// goes past the target then returns
Animator.makeElastic = function(bounces) {
	return function(state) {
		state = Animator.tx.easeInOut(state);
		return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state; 
	}
}
// pre-made transition functions to use with the 'transition' option
Animator.tx = {
	elastic: Animator.makeElastic(1),
	easeInOut: function(pos){
		return ((-Math.cos(pos*Math.PI)*0.5) + 0.5);
	}
}

// animates a pixel-based style property between two integer values
function NumericSS(els, property, from, to, units) {
	this.els = Animator.makeArray(els);
	if (property == 'opacity' && window.ActiveXObject) {
		this.property = 'filter';
	} else {
		this.property = Animator.camelize(property);
	}
	this.from = parseFloat(from);
	this.to = parseFloat(to);
	this.units = units || 'px';
}
NumericSS.prototype = {
	setState: function(state) {
		var style = this.getStyle(state);
		var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
		var j=0;
		for (var i=0; i<this.els.length; i++) {
			this.els[i].style[this.property] = style;
			if (j++ > 20) return;
		}
	},
	getStyle: function(state) {
		state = this.from + ((this.to - this.from) * state);
		if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")";
		if (this.property == 'opacity') return state;
		return state + this.units;
	}
}
// animates a colour based style property between two hex values
function ColorSS(els, property, from, to) {
	this.els = Animator.makeArray(els);
	this.property = Animator.camelize(property);
	this.to = this.expandColor(to);
	this.from = this.expandColor(from);
}
ColorSS.prototype = {
	// parse "#FFFF00" to [256, 256, 0]
	expandColor: function(color) {
		var hexColor, red, green, blue;
		hexColor = ColorSS.parseColor(color);
		if (hexColor) {
			red = parseInt(hexColor.slice(1, 3), 16);
			green = parseInt(hexColor.slice(3, 5), 16);
			blue = parseInt(hexColor.slice(5, 7), 16);
		}
		
		return [red,green,blue];
	},
	getValueForState: function(color, state) {
		return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state));
	},
	setState: function(state) {
		var color = '#'
				+ ColorSS.toColorPart(this.getValueForState(0, state))
				+ ColorSS.toColorPart(this.getValueForState(1, state))
				+ ColorSS.toColorPart(this.getValueForState(2, state));
		for (var i=0; i<this.els.length; i++) {
			this.els[i].style[this.property] = color;
		}
	}
}
// return a properly formatted 6-digit hex colour spec, or false
ColorSS.parseColor = function(string) {
	var color = '#';
	var match = ColorSS.parseColor.rgbRe.exec(string);
	if(match) {
		var part;
		for (var i=1; i<=3; i++) {
			part = Math.max(0, Math.min(255, parseInt(match[i])));
			color += ColorSS.toColorPart(part);
		}
		return color;
	}
	match = ColorSS.parseColor.hexRe.exec(string)
	if (match) {
		if(match[1].length == 3) {
			for (var i=0; i<3; i++) {
				color += match[1].charAt(i) + match[1].charAt(i);
			}
			return color;
		}
		return '#' + match[1];
	}
	return false;
}
// convert a number to a 2 digit hex string
ColorSS.toColorPart = function(number) {
	if (number > 255) number = 255;
	var digits = number.toString(16);
	if (number < 16) return '0' + digits;
	return digits;
}
ColorSS.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
ColorSS.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;

/* The following is not covered under either of the previous lisences.
	Written by Kyle Robertson -- 10 February 2007 */
function Request() {
	if(Request.disabled) return;
	this.req = Request.getObject();
}

Request.factories = [
	function () {return new XMLHttpRequest()},
	function () {return new ActiveXObject("Msxml2.XMLHTTP")},
	function () {return new ActiveXObject("Msxml3.XMLHTTP")},
	function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];

Request.getObject = false;
for(var i = 0; i < Request.factories.length; ++i) {
	try {
		Request.getObject = Request.factories[i];
		Request.getObject();
	} catch(e) {
		continue;
	}
	break;
}

if(!Request.getObject)
	Request.disabled = true;

Request.prototype = {
	send: function(method, url, callback, data, redirectSafe) {
		if(this.busy || Request.disabled) return;
		this.busy = true;
		
		this.url = url;
		this.callback = callback;
		this.data = data;
		this.redirectSafe = redirectSafe;
		
		method = method.toUpperCase();
		
		var query = "";
		if(data) {
			if(typeof data == "string")
				query += "&" + data;
			else
				for(var i in data)
					if(typeof i == "string")
						query += "&" + i + "=" + data[i];
		}
		if(method == "GET") {
			url += (url.indexOf("?") == -1) ? "?" : "&";
			url += "ajax=true&timestamp=" + (new Date()).getTime() + "" + query;
			this.req.open(method, url, true);
			query = "";
		} else {
			this.req.open(method, url, true);
			this.req.setRequestHeader("Content-type",
				"application/x-www-form-urlencoded");
		}
		
		this.req.onreadystatechange = this.onStateChange.bind(this);
		this.req.send(query);
	},
	onStateChange: function() {
		if(this.req.readyState != 4) return;
		if(this.req.status != 200 && this.req.status != 304) {
			Request.disabled = true;
			if(this.redirectSafe)
				window.location = this.url;
			return;
		}
		this.callback(this.req);
		this.busy = false;
	}
}

function Enhance(comics, index, upper, lower, total, prev, next, ratings,
	fullPath) {
	if(!document.getElementById || Request.disabled)
		return;
	
	this.comics = comics; this.initial = index; this.index = index;
	this.lower = lower; this.upper = upper; this.total = total;
	this.prev_url = prev; this.next_url = next;
	
	this.ratings = {};
	for(var i = 0; i < ratings.length; ++i)
		this.ratings[ratings[i]] = true;
	
	var els = ["title", "previous", "date", "next", "comic", "rate-up",
				"rate-down", "tags", "popularity", "rating", "edit", "delete",
				"permalink"];
	for(var i in els)
		this[Animator.camelize(els[i])] = $(els[i]) || new Object();
	
	this.blind = document.createElement("div");
	this.blind.id = "blind";
	this.blind.appendChild(document.createElement("div"));
	document.body.insertBefore(this.blind, $("header"));
	
	this.loadingMsg = document.createElement("div");
	this.loadingMsg.innerHTML = "loading&#8230;";
	this.loadingMsg.className = "hidden";
	this.loadingMsg.id = "loading";
	$("header").appendChild(this.loadingMsg);
	
	this.fx = new Animator()
		.addSubject(new ColorSS(this.title, "color", "rgb(255, 255, 255)", "rgb(0, 0, 0)"))
		.addSubject(new ColorSS(this.date, "color", "rgb(255, 255, 255)", "rgb(0, 0, 0)"))
		.addSubject(new NumericSS(this.blind, "top", -700, 0));
	
	var elements = this.tags.getElementsByTagName("li");
	for(var i = 0, item; item = elements[i]; ++i)
		this.addItemFx(item, 0);
	
	this.comicRequest = new Request();
	addEvent(this.previous, "click", this.onPrevious.bind(this));
	addEvent(this.next, "click", this.onNext.bind(this));
	
	this.ratingRequest = new Request();
	addEvent(this.rateUp, "click", this.onRateUp.bind(this));
	addEvent(this.rateDown, "click", this.onRateDown.bind(this));
	
	this.rawURL = null;
	this.URLLastChar = "";
	
	var qLoc = fullPath.indexOf("?");
	if(qLoc != -1) {
		var query = fullPath.substring(qLoc);
		var tags = /tag=([^&]+)/i.exec(query);
		if(typeof tags[1] == "string") {
			this.rawURL = fullPath + "&page=";
			
			fullPath = fullPath.substring(0, qLoc) + "?tag=" + tags[1];
			var page = /page=([^&]+)/i.exec(query);
			if(page && typeof page[1] == "string")
				fullPath += "&page=" + page[1];
			else
				fullPath += "&page=1";
		}
	} else if(fullPath.indexOf("popular") != -1
			  || fullPath.indexOf("highest") != -1
			  || fullPath.indexOf("tags") != -1) {
		var parts = fullPath.split("/");
		this.rawURL = "/" + parts[1] + "/";
		this.URLLastChar = "/";
		if(parts[2] && isNaN(parseInt(parts[2])) && parts[2].length > 0)
		   this.rawURL += parts[2] + "/";
	}
	this.fullPath = fullPath;

	addEvent(window, "load", this.onWindowLoad.bind(this));
}

Enhance.prototype = {
	onWindowLoad: function() {
		setTimeout(this.preLoad.bind(this), 2000);
	},
	preLoad: function() {
		for(var i in this.comics) {
			this.comics[i].image = new Image();
			this.comics[i].image.src = this.comics[i].path + "/comic.jpg";
		}
	},
	isComplete: function() {
		return ((this.upper - this.lower) == (this.total - 1));
	},
	buildPath: function(index, hash) {
		var url = this.rawURL ? this.rawURL + (index + 1)
			: "/" + this.comics[index].path;
		url += this.URLLastChar;
		if(typeof hash != "undefined")
			url += "#";
		return url;
	},
	getPath: function(index) {
		if(index < 0)
			return this.buildPath(0, true);
		else if(index > (this.total - 1))
			return this.buildPath(this.total - 1, true);
		return this.buildPath(index);
	},
	// Resets the rating links
	toggleRatings: function(enable) {
		if(enable) {
			this.rateUp.className = this.rateDown.className = "";
			this.rateUp.title = "Rate it up.";
			this.rateDown.title = "Rate it down.";
		} else {
			this.rateUp.className = this.rateDown.className = "disabled";
			this.rateUp.title = "This button appreciates clicking..."
			this.rateDown.title = "... and this one does not."
		}
	},
	// Removes the transition subject and deletes the property
	removeSubject: function(subject) {
		if(this[subject]) {
			this.fx.removeSubject(this[subject]);
			delete this[subject];
		}
	},
	// Sets up tag transitions and resets initial positions
	addItemFx: function(item, state) {
		item.fade = new NumericSS(item, "opacity", 1, 0);
		item.fade.setState(state);
		this.fx.addSubject(item.fade);
		
		item.move = new NumericSS(item, "margin-left", 0.5, -5, "em");
		item.move.setState(state);
		this.fx.addSubject(item.move);
	},
	// Removes tag transitions
	removeTagFx: function() {
		var elements = this.tags.getElementsByTagName("li");
		for(var i = 0, item; item = elements[i]; ++i) {
			if(item.fade)
				this.fx.removeSubject(item.fade);
			if(item.move)
				this.fx.removeSubject(item.move);
		}
	},
	setPopularity: function(value) {
		this.popularity.innerHTML = "eyed by " + value + ".";
	},
	setRating: function(value) {
		this.rating.innerHTML = value + ".";
	},
	// Updates the popularity and rating transitions
	onStep: function() {
		var newPop = Math.round(parseInt(this.popularity.style["newValue"]));
		if(!isNaN(newPop))
			this.setPopularity(newPop);
		var newRating = Math.round(parseInt(this.rating.style["newValue"]));
		if(!isNaN(newRating))
			this.setRating(newRating);
	},
	// Swaps out comics between transitions
	onSwap: function() {
		// Clear transition callbacks
		this.fx.options.onComplete = function() { };
		this.fx.options.onStep = function() { };
		
		// Setup new comic properties
		var comic = this.comics[this.index];
		
		if(comic.image)
			this.comic.src = comic.image.src;
		else
			this.comic.src = comic.path + "/comic.jpg";
		
		this.comic.alt = comic.title;
		this.title.innerHTML = comic.title + ".";
		this.date.innerHTML = comic.date;
		this.setPopularity(comic.views);
		this.setRating(comic.rating);
		
		var path = this.getPath(this.index);
		this.rateUp.href = comic.path + "/up/?previous=" + path;
		this.rateDown.href = comic.path + "/down/?previous=" + path;
		this.edit.href = comic.path + "/edit/?previous=" + path;
		
		this["delete"].href = comic.path + "/delete/";
		this.permalink.value = "http://www.lillucifer.com/" + comic.path + "/";
		document.title = comic.title + ".";
		this.toggleRatings((typeof this.ratings[comic.id] == "undefined"));
		
		// Reset custom transitions
		this.removeSubject("imgFx");
		this.removeSubject("ratingFx");
		this.removeSubject("popularityFx");
		
		// Clear tag list
		this.removeTagFx();
		var elements = this.tags.getElementsByTagName("li");
		for(var i = 0, item; item = elements[i]; ++i)
			this.tags.removeChild(item);
		
		// Append new tags
		var tag, item;
		for(var i = 0; i < comic.tags.length; ++i) {
			tag = comic.tags[i];
			item = document.createElement("li");
			item.innerHTML = "<a href=\"tags/" + tag.slug + "/1/\" title=\"View all comics tagged with '" + tag.name + "'\">" + tag.name + ".</a>";
			this.addItemFx(item, 1);
			this.tags.appendChild(item);
		}
		
		this.fx.seekTo(0);
	},
	// Sets up comic transitions
	update: function() {
		// Update onward/retreat links
		
		this.previous.href = this.getPath(this.index - 1);
		if(this.index > 0) {
			this.previous.title = "View the previous comic: " + this.comics[this.index - 1].title;
			this.previous.className = "";
		} else {
			this.previous.title = "No comics come before this one."
			this.previous.className = "vacant";
		}
		
		this.next.href = this.getPath(this.index + 1);
		if(this.index + 1 < this.total) {
			this.next.title = "View the next comic: " + this.comics[this.index + 1].title;
			this.next.className = "";
		} else {
			this.next.title = "No comics come after this one."
			this.next.className = "vacant";
		}
		
		// Setup height transition, if required
		var comic = this.comics[this.index];
		this.removeSubject("imgFx");
		var height = this.comic.style.height || this.comic.height;
		if(height != comic.height) {
			this.imgFx = new NumericSS(this.comic, "height", height, comic.height);
			this.fx.addSubject(this.imgFx);
		}
		
		// Reset rating and popularity transitions
		this.removeSubject("ratingFx");
		this.removeSubject("popularityFx");
		this.ratingFx = new NumericSS(this.rating, "new-value",
			this.comics[this.previousIndex].rating, comic.rating);
		this.popularityFx = new NumericSS(this.popularity, "new-value",
			this.comics[this.previousIndex].views, comic.views);
		this.fx.addSubject(this.ratingFx);
		this.fx.addSubject(this.popularityFx);
		
		// Setup transition callbacks
		this.fx.options.onStep = this.onStep.bind(this);
		this.fx.options.onComplete = this.onSwap.bind(this);
		
		// Animate!
		this.fx.seekTo(1);
	},
	onPrevious: function(e) {
		if(Request.disabled)
			return;
		e.preventDefault();
		if(this.index > 0) {
			if(this.lower > 0 && ((this.index - 5) < this.lower))
				this.requestComics("lower");
			if(this.comics[this.index - 1]) {
				if(this.index <= 1 || typeof this.comics[this.index - 2] != "undefined") {
					this.previousIndex = this.index;
					this.index--;
					this.update();
					return;
				}
			}
			// TODO: ADD LOADING?
		}
	},
	onNext: function(e) {
		if(Request.disabled)
			return;
		e.preventDefault();
		if(this.index < (this.total - 1)) {
			if(this.upper < (this.total - 1) && ((this.index + 5) > this.upper))
				this.requestComics("upper");
			if(this.comics[this.index + 1]) {
				if(this.index == (this.total - 2) || typeof this.comics[this.index + 2] != "undefined") {
					this.previousIndex = this.index;
					this.index++;
					this.update();
				}
			}
			
			// TODO: ADD LOADING?
		}
	},
	onRateUp: function(e) {
		this.onRate(e, this.rateUp.href);
	},
	onRateDown: function(e) {
		this.onRate(e, this.rateDown.href);
	},
	// Sends a request for the comics new rating after the user has voted
	onRate: function(e, href) {
		if(Request.disabled)
			return;
		
		e.preventDefault();
		
		// Prevent rating twice
		if(this.rateIndex || this.ratings[this.comics[this.index].id])
			return;
		
		// Send request
		this.rateIndex = this.index;
		this.ratingRequest.send("get", href.substring(0, href.indexOf("?")),
			this.onRatingChange.bind(this), null, true);
	},
	// Sets up rating transitions after the request has succeded
	onRatingChange: function(req) {
		// Mark this comic as rated
		this.ratings[this.comics[this.rateIndex].id] = true;
		
		// Determine if the rating has changed
		var oldRating = this.comics[this.rateIndex].rating;
		var newRating = parseInt(req.responseText);
		this.toggleRatings(false);
		if(!isNaN(newRating) && newRating != oldRating) {
			this.comics[this.rateIndex].rating = newRating;
			
			// If the comic has changed since the request was called, we don't 
			// need to do any transitions
			if(this.rateIndex != this.index)
				return;
			
			// Set up the rating transition
			var to = (newRating > oldRating) ? 2 : 1;
			var fx = new Animator({duration: 400, transition: Animator.tx.elastic})
				.addSubject(new NumericSS(this.rating, "font-size", 1.5, to, "em"))
				.addSubject(new NumericSS(this.rating, "new-value", oldRating, newRating));
			this.rating.style["expando"] = 5;
			fx.options.onStep = this.onStep.bind(this);
			
			fx.options.onComplete = function() {
				fx.options.onComplete = function() { }
				fx.options.onStep = function() { }
				fx.seekTo(0);
			}.bind(fx);
			fx.seekTo(1);
		}
		this.rateIndex = null;
	},
	// Requests more comics from the given boundary (upper, lower)
	requestComics: function(bound) {
		if(this.busy) return;
		this.busy = true;
		this.bound = bound;
		
		var data = {"initial": this.initial,
		"bound_type": bound, "bound_value":this[bound]};
		
		this.loadingMsg.className = "";
		// Send request
		this.comicRequest.send("get", this.fullPath,
			this.appendComics.bind(this), data, false);
	},
	// Updates the local comic array
	appendComics: function(req) {
		try {
			eval("var data = " + req.responseText);
		}
		catch (e) {
			Request.disabled = true;
			return;
		}
		
		for(var i in data.comics) {
			this.comics[i] = data.comics[i];
			this.comics[i].image = new Image();
			this.comics[i].image.src = data.comics[i].path + "/comic.jpg";
		}
		
		if(!isNaN(data.bound))
			this[this.bound.toLowerCase()] = data.bound
			
		this.loadingMsg.className = "hidden";
		this.busy = false;
	}
}