/* Script: Core.js MooTools - My Object Oriented JavaScript Tools. License: MIT-style license. Copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/). Code & Documentation: [The MooTools production team](http://mootools.net/developers/). Inspiration: - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) */ var MooTools = { 'version': '1.2.1', 'build': '0d4845aab3d9a4fdee2f0d4a6dd59210e4b697cf' }; var Native = function(options){ options = options || {}; var name = options.name; var legacy = options.legacy; var protect = options.protect; var methods = options.implement; var generics = options.generics; var initialize = options.initialize; var afterImplement = options.afterImplement || function(){}; var object = initialize || legacy; generics = generics !== false; object.constructor = Native; object.$family = {name: 'native'}; if (legacy && initialize) object.prototype = legacy.prototype; object.prototype.constructor = object; if (name){ var family = name.toLowerCase(); object.prototype.$family = {name: family}; Native.typize(object, family); } var add = function(obj, name, method, force){ if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method; if (generics) Native.genericize(obj, name, protect); afterImplement.call(obj, name, method); return obj; }; object.alias = function(a1, a2, a3){ if (typeof a1 == 'string'){ if ((a1 = this.prototype[a1])) return add(this, a2, a1, a3); } for (var a in a1) this.alias(a, a1[a], a2); return this; }; object.implement = function(a1, a2, a3){ if (typeof a1 == 'string') return add(this, a1, a2, a3); for (var p in a1) add(this, p, a1[p], a2); return this; }; if (methods) object.implement(methods); return object; }; Native.genericize = function(object, property, check){ if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){ var args = Array.prototype.slice.call(arguments); return object.prototype[property].apply(args.shift(), args); }; }; Native.implement = function(objects, properties){ for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties); }; Native.typize = function(object, family){ if (!object.type) object.type = function(item){ return ($type(item) === family); }; }; (function(){ var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String}; for (var n in natives) new Native({name: n, initialize: natives[n], protect: true}); var types = {'boolean': Boolean, 'native': Native, 'object': Object}; for (var t in types) Native.typize(types[t], t); var generics = { 'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"], 'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"] }; for (var g in generics){ for (var i = generics[g].length; i--;) Native.genericize(window[g], generics[g][i], true); }; })(); var Hash = new Native({ name: 'Hash', initialize: function(object){ if ($type(object) == 'hash') object = $unlink(object.getClean()); for (var key in object) this[key] = object[key]; return this; } }); Hash.implement({ forEach: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this); } }, getClean: function(){ var clean = {}; for (var key in this){ if (this.hasOwnProperty(key)) clean[key] = this[key]; } return clean; }, getLength: function(){ var length = 0; for (var key in this){ if (this.hasOwnProperty(key)) length++; } return length; } }); Hash.alias('forEach', 'each'); Array.implement({ forEach: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this); } }); Array.alias('forEach', 'each'); function $A(iterable){ if (iterable.item){ var array = []; for (var i = 0, l = iterable.length; i < l; i++) array[i] = iterable[i]; return array; } return Array.prototype.slice.call(iterable); }; function $arguments(i){ return function(){ return arguments[i]; }; }; function $chk(obj){ return !!(obj || obj === 0); }; function $clear(timer){ clearTimeout(timer); clearInterval(timer); return null; }; function $defined(obj){ return (obj != undefined); }; function $each(iterable, fn, bind){ var type = $type(iterable); ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind); }; function $empty(){}; function $extend(original, extended){ for (var key in (extended || {})) original[key] = extended[key]; return original; }; function $H(object){ return new Hash(object); }; function $lambda(value){ return (typeof value == 'function') ? value : function(){ return value; }; }; function $merge(){ var mix = {}; for (var i = 0, l = arguments.length; i < l; i++){ var object = arguments[i]; if ($type(object) != 'object') continue; for (var key in object){ var op = object[key], mp = mix[key]; mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $merge(mp, op) : $unlink(op); } } return mix; }; function $pick(){ for (var i = 0, l = arguments.length; i < l; i++){ if (arguments[i] != undefined) return arguments[i]; } return null; }; function $random(min, max){ return Math.floor(Math.random() * (max - min + 1) + min); }; function $splat(obj){ var type = $type(obj); return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : []; }; var $time = Date.now || function(){ return +new Date; }; function $try(){ for (var i = 0, l = arguments.length; i < l; i++){ try { return arguments[i](); } catch(e){} } return null; }; function $type(obj){ if (obj == undefined) return false; if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name; if (obj.nodeName){ switch (obj.nodeType){ case 1: return 'element'; case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; } } else if (typeof obj.length == 'number'){ if (obj.callee) return 'arguments'; else if (obj.item) return 'collection'; } return typeof obj; }; function $unlink(object){ var unlinked; switch ($type(object)){ case 'object': unlinked = {}; for (var p in object) unlinked[p] = $unlink(object[p]); break; case 'hash': unlinked = new Hash(object); break; case 'array': unlinked = []; for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]); break; default: return object; } return unlinked; }; /* Script: Browser.js The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash. License: MIT-style license. */ var Browser = $merge({ Engine: {name: 'unknown', version: 0}, Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()}, Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}, Plugins: {}, Engines: { presto: function(){ return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }, trident: function(){ return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4); }, webkit: function(){ return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419); }, gecko: function(){ return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18); } } }, Browser || {}); Browser.Platform[Browser.Platform.name] = true; Browser.detect = function(){ for (var engine in this.Engines){ var version = this.Engines[engine](); if (version){ this.Engine = {name: engine, version: version}; this.Engine[engine] = this.Engine[engine + version] = true; break; } } return {name: engine, version: version}; }; Browser.detect(); Browser.Request = function(){ return $try(function(){ return new XMLHttpRequest(); }, function(){ return new ActiveXObject('MSXML2.XMLHTTP'); }); }; Browser.Features.xhr = !!(Browser.Request()); Browser.Plugins.Flash = (function(){ var version = ($try(function(){ return navigator.plugins['Shockwave Flash'].description; }, function(){ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); }) || '0 r0').match(/\d+/g); return {version: parseInt(version[0] || 0 + '.' + version[1] || 0), build: parseInt(version[2] || 0)}; })(); function $exec(text){ if (!text) return text; if (window.execScript){ window.execScript(text); } else { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text; document.head.appendChild(script); document.head.removeChild(script); } return text; }; Native.UID = 1; var $uid = (Browser.Engine.trident) ? function(item){ return (item.uid || (item.uid = [Native.UID++]))[0]; } : function(item){ return item.uid || (item.uid = Native.UID++); }; var Window = new Native({ name: 'Window', legacy: (Browser.Engine.trident) ? null: window.Window, initialize: function(win){ $uid(win); if (!win.Element){ win.Element = $empty; if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2 win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {}; } win.document.window = win; return $extend(win, Window.Prototype); }, afterImplement: function(property, value){ window[property] = Window.Prototype[property] = value; } }); Window.Prototype = {$family: {name: 'window'}}; new Window(window); var Document = new Native({ name: 'Document', legacy: (Browser.Engine.trident) ? null: window.Document, initialize: function(doc){ $uid(doc); doc.head = doc.getElementsByTagName('head')[0]; doc.html = doc.getElementsByTagName('html')[0]; if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){ doc.execCommand("BackgroundImageCache", false, true); }); if (Browser.Engine.trident) doc.window.attachEvent('onunload', function() { doc.window.detachEvent('onunload', arguments.callee); doc.head = doc.html = doc.window = null; }); return $extend(doc, Document.Prototype); }, afterImplement: function(property, value){ document[property] = Document.Prototype[property] = value; } }); Document.Prototype = {$family: {name: 'document'}}; new Document(document); /* Script: Array.js Contains Array Prototypes like each, contains, and erase. License: MIT-style license. */ Array.implement({ every: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (!fn.call(bind, this[i], i, this)) return false; } return true; }, filter: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) results.push(this[i]); } return results; }, clean: function() { return this.filter($defined); }, indexOf: function(item, from){ var len = this.length; for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){ if (this[i] === item) return i; } return -1; }, map: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this); return results; }, some: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) return true; } return false; }, associate: function(keys){ var obj = {}, length = Math.min(this.length, keys.length); for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; return obj; }, link: function(object){ var result = {}; for (var i = 0, l = this.length; i < l; i++){ for (var key in object){ if (object[key](this[i])){ result[key] = this[i]; delete object[key]; break; } } } return result; }, contains: function(item, from){ return this.indexOf(item, from) != -1; }, extend: function(array){ for (var i = 0, j = array.length; i < j; i++) this.push(array[i]); return this; }, getLast: function(){ return (this.length) ? this[this.length - 1] : null; }, getRandom: function(){ return (this.length) ? this[$random(0, this.length - 1)] : null; }, include: function(item){ if (!this.contains(item)) this.push(item); return this; }, combine: function(array){ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); return this; }, erase: function(item){ for (var i = this.length; i--; i){ if (this[i] === item) this.splice(i, 1); } return this; }, empty: function(){ this.length = 0; return this; }, flatten: function(){ var array = []; for (var i = 0, l = this.length; i < l; i++){ var type = $type(this[i]); if (!type) continue; array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]); } return array; }, hexToRgb: function(array){ if (this.length != 3) return null; var rgb = this.map(function(value){ if (value.length == 1) value += value; return value.toInt(16); }); return (array) ? rgb : 'rgb(' + rgb + ')'; }, rgbToHex: function(array){ if (this.length < 3) return null; if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; var hex = []; for (var i = 0; i < 3; i++){ var bit = (this[i] - 0).toString(16); hex.push((bit.length == 1) ? '0' + bit : bit); } return (array) ? hex : '#' + hex.join(''); } }); /* Script: Function.js Contains Function Prototypes like create, bind, pass, and delay. License: MIT-style license. */ Function.implement({ extend: function(properties){ for (var property in properties) this[property] = properties[property]; return this; }, create: function(options){ var self = this; options = options || {}; return function(event){ var args = options.arguments; args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0); if (options.event) args = [event || window.event].extend(args); var returns = function(){ return self.apply(options.bind || null, args); }; if (options.delay) return setTimeout(returns, options.delay); if (options.periodical) return setInterval(returns, options.periodical); if (options.attempt) return $try(returns); return returns(); }; }, run: function(args, bind){ return this.apply(bind, $splat(args)); }, pass: function(args, bind){ return this.create({bind: bind, arguments: args}); }, bind: function(bind, args){ return this.create({bind: bind, arguments: args}); }, bindWithEvent: function(bind, args){ return this.create({bind: bind, arguments: args, event: true}); }, attempt: function(args, bind){ return this.create({bind: bind, arguments: args, attempt: true})(); }, delay: function(delay, bind, args){ return this.create({bind: bind, arguments: args, delay: delay})(); }, periodical: function(periodical, bind, args){ return this.create({bind: bind, arguments: args, periodical: periodical})(); } }); /* Script: Number.js Contains Number Prototypes like limit, round, times, and ceil. License: MIT-style license. */ Number.implement({ limit: function(min, max){ return Math.min(max, Math.max(min, this)); }, round: function(precision){ precision = Math.pow(10, precision || 0); return Math.round(this * precision) / precision; }, times: function(fn, bind){ for (var i = 0; i < this; i++) fn.call(bind, i, this); }, toFloat: function(){ return parseFloat(this); }, toInt: function(base){ return parseInt(this, base || 10); } }); Number.alias('times', 'each'); (function(math){ var methods = {}; math.each(function(name){ if (!Number[name]) methods[name] = function(){ return Math[name].apply(null, [this].concat($A(arguments))); }; }); Number.implement(methods); })(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); /* Script: String.js Contains String Prototypes like camelCase, capitalize, test, and toInt. License: MIT-style license. */ String.implement({ test: function(regex, params){ return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this); }, contains: function(string, separator){ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1; }, trim: function(){ return this.replace(/^\s+|\s+$/g, ''); }, clean: function(){ return this.replace(/\s+/g, ' ').trim(); }, camelCase: function(){ return this.replace(/-\D/g, function(match){ return match.charAt(1).toUpperCase(); }); }, hyphenate: function(){ return this.replace(/[A-Z]/g, function(match){ return ('-' + match.charAt(0).toLowerCase()); }); }, capitalize: function(){ return this.replace(/\b[a-z]/g, function(match){ return match.toUpperCase(); }); }, escapeRegExp: function(){ return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); }, toInt: function(base){ return parseInt(this, base || 10); }, toFloat: function(){ return parseFloat(this); }, hexToRgb: function(array){ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); return (hex) ? hex.slice(1).hexToRgb(array) : null; }, rgbToHex: function(array){ var rgb = this.match(/\d{1,3}/g); return (rgb) ? rgb.rgbToHex(array) : null; }, stripScripts: function(option){ var scripts = ''; var text = this.replace(/]*>([\s\S]*?)<\/script>/gi, function(){ scripts += arguments[1] + '\n'; return ''; }); if (option === true) $exec(scripts); else if ($type(option) == 'function') option(scripts, text); return text; }, substitute: function(object, regexp){ return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ if (match.charAt(0) == '\\') return match.slice(1); return (object[name] != undefined) ? object[name] : ''; }); } }); /* Script: Hash.js Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. License: MIT-style license. */ Hash.implement({ has: Object.prototype.hasOwnProperty, keyOf: function(value){ for (var key in this){ if (this.hasOwnProperty(key) && this[key] === value) return key; } return null; }, hasValue: function(value){ return (Hash.keyOf(this, value) !== null); }, extend: function(properties){ Hash.each(properties, function(value, key){ Hash.set(this, key, value); }, this); return this; }, combine: function(properties){ Hash.each(properties, function(value, key){ Hash.include(this, key, value); }, this); return this; }, erase: function(key){ if (this.hasOwnProperty(key)) delete this[key]; return this; }, get: function(key){ return (this.hasOwnProperty(key)) ? this[key] : null; }, set: function(key, value){ if (!this[key] || this.hasOwnProperty(key)) this[key] = value; return this; }, empty: function(){ Hash.each(this, function(value, key){ delete this[key]; }, this); return this; }, include: function(key, value){ var k = this[key]; if (k == undefined) this[key] = value; return this; }, map: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ results.set(key, fn.call(bind, value, key, this)); }, this); return results; }, filter: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ if (fn.call(bind, value, key, this)) results.set(key, value); }, this); return results; }, every: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false; } return true; }, some: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true; } return false; }, getKeys: function(){ var keys = []; Hash.each(this, function(value, key){ keys.push(key); }); return keys; }, getValues: function(){ var values = []; Hash.each(this, function(value){ values.push(value); }); return values; }, toQueryString: function(base){ var queryString = []; Hash.each(this, function(value, key){ if (base) key = base + '[' + key + ']'; var result; switch ($type(value)){ case 'object': result = Hash.toQueryString(value, key); break; case 'array': var qs = {}; value.each(function(val, i){ qs[i] = val; }); result = Hash.toQueryString(qs, key); break; default: result = key + '=' + encodeURIComponent(value); } if (value != undefined) queryString.push(result); }); return queryString.join('&'); } }); Hash.alias({keyOf: 'indexOf', hasValue: 'contains'}); /* Script: Event.js Contains the Event Native, to make the event object completely crossbrowser. License: MIT-style license. */ var Event = new Native({ name: 'Event', initialize: function(event, win){ win = win || window; var doc = win.document; event = event || win.event; if (event.$extended) return event; this.$extended = true; var type = event.type; var target = event.target || event.srcElement; while (target && target.nodeType == 3) target = target.parentNode; if (type.test(/key/)){ var code = event.which || event.keyCode; var key = Event.Keys.keyOf(code); if (type == 'keydown'){ var fKey = code - 111; if (fKey > 0 && fKey < 13) key = 'f' + fKey; } key = key || String.fromCharCode(code).toLowerCase(); } else if (type.match(/(click|mouse|menu)/i)){ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; var page = { x: event.pageX || event.clientX + doc.scrollLeft, y: event.pageY || event.clientY + doc.scrollTop }; var client = { x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX, y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY }; if (type.match(/DOMMouseScroll|mousewheel/)){ var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; } var rightClick = (event.which == 3) || (event.button == 2); var related = null; if (type.match(/over|out/)){ switch (type){ case 'mouseover': related = event.relatedTarget || event.fromElement; break; case 'mouseout': related = event.relatedTarget || event.toElement; } if (!(function(){ while (related && related.nodeType == 3) related = related.parentNode; return true; }).create({attempt: Browser.Engine.gecko})()) related = false; } } return $extend(this, { event: event, type: type, page: page, client: client, rightClick: rightClick, wheel: wheel, relatedTarget: related, target: target, code: code, key: key, shift: event.shiftKey, control: event.ctrlKey, alt: event.altKey, meta: event.metaKey }); } }); Event.Keys = new Hash({ 'enter': 13, 'up': 38, 'down': 40, 'left': 37, 'right': 39, 'esc': 27, 'space': 32, 'backspace': 8, 'tab': 9, 'delete': 46 }); Event.implement({ stop: function(){ return this.stopPropagation().preventDefault(); }, stopPropagation: function(){ if (this.event.stopPropagation) this.event.stopPropagation(); else this.event.cancelBubble = true; return this; }, preventDefault: function(){ if (this.event.preventDefault) this.event.preventDefault(); else this.event.returnValue = false; return this; } }); /* Script: Class.js Contains the Class Function for easily creating, extending, and implementing reusable Classes. License: MIT-style license. */ var Class = new Native({ name: 'Class', initialize: function(properties){ properties = properties || {}; var klass = function(){ for (var key in this){ if ($type(this[key]) != 'function') this[key] = $unlink(this[key]); } this.constructor = klass; if (Class.prototyping) return this; var instance = (this.initialize) ? this.initialize.apply(this, arguments) : this; if (this.options && this.options.initialize) this.options.initialize.call(this); return instance; }; for (var mutator in Class.Mutators){ if (!properties[mutator]) continue; properties = Class.Mutators[mutator](properties, properties[mutator]); delete properties[mutator]; } $extend(klass, this); klass.constructor = Class; klass.prototype = properties; return klass; } }); Class.Mutators = { Extends: function(self, klass){ Class.prototyping = klass.prototype; var subclass = new klass; delete subclass.parent; subclass = Class.inherit(subclass, self); delete Class.prototyping; return subclass; }, Implements: function(self, klasses){ $splat(klasses).each(function(klass){ Class.prototying = klass; $extend(self, ($type(klass) == 'class') ? new klass : klass); delete Class.prototyping; }); return self; } }; Class.extend({ inherit: function(object, properties){ var caller = arguments.callee.caller; for (var key in properties){ var override = properties[key]; var previous = object[key]; var type = $type(override); if (previous && type == 'function'){ if (override != previous){ if (caller){ override.__parent = previous; object[key] = override; } else { Class.override(object, key, override); } } } else if(type == 'object'){ object[key] = $merge(previous, override); } else { object[key] = override; } } if (caller) object.parent = function(){ return arguments.callee.caller.__parent.apply(this, arguments); }; return object; }, override: function(object, name, method){ var parent = Class.prototyping; if (parent && object[name] != parent[name]) parent = null; var override = function(){ var previous = this.parent; this.parent = parent ? parent[name] : object[name]; var value = method.apply(this, arguments); this.parent = previous; return value; }; object[name] = override; } }); Class.implement({ implement: function(){ var proto = this.prototype; $each(arguments, function(properties){ Class.inherit(proto, properties); }); return this; } }); /* Script: Class.Extras.js Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. License: MIT-style license. */ var Chain = new Class({ $chain: [], chain: function(){ this.$chain.extend(Array.flatten(arguments)); return this; }, callChain: function(){ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; }, clearChain: function(){ this.$chain.empty(); return this; } }); var Events = new Class({ $events: {}, addEvent: function(type, fn, internal){ type = Events.removeOn(type); if (fn != $empty){ this.$events[type] = this.$events[type] || []; this.$events[type].include(fn); if (internal) fn.internal = true; } return this; }, addEvents: function(events){ for (var type in events) this.addEvent(type, events[type]); return this; }, fireEvent: function(type, args, delay){ type = Events.removeOn(type); if (!this.$events || !this.$events[type]) return this; this.$events[type].each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, removeEvent: function(type, fn){ type = Events.removeOn(type); if (!this.$events[type]) return this; if (!fn.internal) this.$events[type].erase(fn); return this; }, removeEvents: function(events){ if ($type(events) == 'object'){ for (var type in events) this.removeEvent(type, events[type]); return this; } if (events) events = Events.removeOn(events); for (var type in this.$events){ if (events && events != type) continue; var fns = this.$events[type]; for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]); } return this; } }); Events.removeOn = function(string){ return string.replace(/^on([A-Z])/, function(full, first) { return first.toLowerCase(); }); }; var Options = new Class({ setOptions: function(){ this.options = $merge.run([this.options].extend(arguments)); if (!this.addEvent) return this; for (var option in this.options){ if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; this.addEvent(option, this.options[option]); delete this.options[option]; } return this; } }); /* Script: Element.js One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. License: MIT-style license. */ var Element = new Native({ name: 'Element', legacy: window.Element, initialize: function(tag, props){ var konstructor = Element.Constructors.get(tag); if (konstructor) return konstructor(props); if (typeof tag == 'string') return document.newElement(tag, props); return $(tag).set(props); }, afterImplement: function(key, value){ Element.Prototype[key] = value; if (Array[key]) return; Elements.implement(key, function(){ var items = [], elements = true; for (var i = 0, j = this.length; i < j; i++){ var returns = this[i][key].apply(this[i], arguments); items.push(returns); if (elements) elements = ($type(returns) == 'element'); } return (elements) ? new Elements(items) : items; }); } }); Element.Prototype = {$family: {name: 'element'}}; Element.Constructors = new Hash; var IFrame = new Native({ name: 'IFrame', generics: false, initialize: function(){ var params = Array.link(arguments, {properties: Object.type, iframe: $defined}); var props = params.properties || {}; var iframe = $(params.iframe) || false; var onload = props.onload || $empty; delete props.onload; props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + $time()); iframe = new Element(iframe || 'iframe', props); var onFrameLoad = function(){ var host = $try(function(){ return iframe.contentWindow.location.host; }); if (host && host == window.location.host){ var win = new Window(iframe.contentWindow); new Document(iframe.contentWindow.document); $extend(win.Element.prototype, Element.Prototype); } onload.call(iframe.contentWindow, iframe.contentWindow.document); }; (window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad); return iframe; } }); var Elements = new Native({ initialize: function(elements, options){ options = $extend({ddup: true, cash: true}, options); elements = elements || []; if (options.ddup || options.cash){ var uniques = {}, returned = []; for (var i = 0, l = elements.length; i < l; i++){ var el = $.element(elements[i], !options.cash); if (options.ddup){ if (uniques[el.uid]) continue; uniques[el.uid] = true; } returned.push(el); } elements = returned; } return (options.cash) ? $extend(elements, this) : elements; } }); Elements.implement({ filter: function(filter, bind){ if (!filter) return this; return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){ return item.match(filter); } : filter, bind)); } }); Document.implement({ newElement: function(tag, props){ if (Browser.Engine.trident && props){ ['name', 'type', 'checked'].each(function(attribute){ if (!props[attribute]) return; tag += ' ' + attribute + '="' + props[attribute] + '"'; if (attribute != 'checked') delete props[attribute]; }); tag = '<' + tag + '>'; } return $.element(this.createElement(tag)).set(props); }, newTextNode: function(text){ return this.createTextNode(text); }, getDocument: function(){ return this; }, getWindow: function(){ return this.window; } }); Window.implement({ $: function(el, nocash){ if (el && el.$family && el.uid) return el; var type = $type(el); return ($[type]) ? $[type](el, nocash, this.document) : null; }, $$: function(selector){ if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector); var elements = []; var args = Array.flatten(arguments); for (var i = 0, l = args.length; i < l; i++){ var item = args[i]; switch ($type(item)){ case 'element': elements.push(item); break; case 'string': elements.extend(this.document.getElements(item, true)); } } return new Elements(elements); }, getDocument: function(){ return this.document; }, getWindow: function(){ return this; } }); $.string = function(id, nocash, doc){ id = doc.getElementById(id); return (id) ? $.element(id, nocash) : null; }; $.element = function(el, nocash){ $uid(el); if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){ var proto = Element.Prototype; for (var p in proto) el[p] = proto[p]; }; return el; }; $.object = function(obj, nocash, doc){ if (obj.toElement) return $.element(obj.toElement(doc), nocash); return null; }; $.textnode = $.whitespace = $.window = $.document = $arguments(0); Native.implement([Element, Document], { getElement: function(selector, nocash){ return $(this.getElements(selector, true)[0] || null, nocash); }, getElements: function(tags, nocash){ tags = tags.split(','); var elements = []; var ddup = (tags.length > 1); tags.each(function(tag){ var partial = this.getElementsByTagName(tag.trim()); (ddup) ? elements.extend(partial) : elements = partial; }, this); return new Elements(elements, {ddup: ddup, cash: !nocash}); } }); (function(){ var collected = {}, storage = {}; var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'}; var get = function(uid){ return (storage[uid] || (storage[uid] = {})); }; var clean = function(item, retain){ if (!item) return; var uid = item.uid; if (Browser.Engine.trident){ if (item.clearAttributes){ var clone = retain && item.cloneNode(false); item.clearAttributes(); if (clone) item.mergeAttributes(clone); } else if (item.removeEvents){ item.removeEvents(); } if ((/object/i).test(item.tagName)){ for (var p in item){ if (typeof item[p] == 'function') item[p] = $empty; } Element.dispose(item); } } if (!uid) return; collected[uid] = storage[uid] = null; }; var purge = function(){ Hash.each(collected, clean); if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean); if (window.CollectGarbage) CollectGarbage(); collected = storage = null; }; var walk = function(element, walk, start, match, all, nocash){ var el = element[start || walk]; var elements = []; while (el){ if (el.nodeType == 1 && (!match || Element.match(el, match))){ if (!all) return $(el, nocash); elements.push(el); } el = el[walk]; } return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null; }; var attributes = { 'html': 'innerHTML', 'class': 'className', 'for': 'htmlFor', 'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent' }; var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer']; var camels = ['value', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']; Hash.extend(attributes, bools.associate(bools)); Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase))); var inserters = { before: function(context, element){ if (element.parentNode) element.parentNode.insertBefore(context, element); }, after: function(context, element){ if (!element.parentNode) return; var next = element.nextSibling; (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context); }, bottom: function(context, element){ element.appendChild(context); }, top: function(context, element){ var first = element.firstChild; (first) ? element.insertBefore(context, first) : element.appendChild(context); } }; inserters.inside = inserters.bottom; Hash.each(inserters, function(inserter, where){ where = where.capitalize(); Element.implement('inject' + where, function(el){ inserter(this, $(el, true)); return this; }); Element.implement('grab' + where, function(el){ inserter($(el, true), this); return this; }); }); Element.implement({ set: function(prop, value){ switch ($type(prop)){ case 'object': for (var p in prop) this.set(p, prop[p]); break; case 'string': var property = Element.Properties.get(prop); (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value); } return this; }, get: function(prop){ var property = Element.Properties.get(prop); return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop); }, erase: function(prop){ var property = Element.Properties.get(prop); (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); return this; }, setProperty: function(attribute, value){ var key = attributes[attribute]; if (value == undefined) return this.removeProperty(attribute); if (key && bools[attribute]) value = !!value; (key) ? this[key] = value : this.setAttribute(attribute, '' + value); return this; }, setProperties: function(attributes){ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); return this; }, getProperty: function(attribute){ var key = attributes[attribute]; var value = (key) ? this[key] : this.getAttribute(attribute, 2); return (bools[attribute]) ? !!value : (key) ? value : value || null; }, getProperties: function(){ var args = $A(arguments); return args.map(this.getProperty, this).associate(args); }, removeProperty: function(attribute){ var key = attributes[attribute]; (key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute); return this; }, removeProperties: function(){ Array.each(arguments, this.removeProperty, this); return this; }, hasClass: function(className){ return this.className.contains(className, ' '); }, addClass: function(className){ if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean(); return this; }, removeClass: function(className){ this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1'); return this; }, toggleClass: function(className){ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className); }, adopt: function(){ Array.flatten(arguments).each(function(element){ element = $(element, true); if (element) this.appendChild(element); }, this); return this; }, appendText: function(text, where){ return this.grab(this.getDocument().newTextNode(text), where); }, grab: function(el, where){ inserters[where || 'bottom']($(el, true), this); return this; }, inject: function(el, where){ inserters[where || 'bottom'](this, $(el, true)); return this; }, replaces: function(el){ el = $(el, true); el.parentNode.replaceChild(this, el); return this; }, wraps: function(el, where){ el = $(el, true); return this.replaces(el).grab(el, where); }, getPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, false, nocash); }, getAllPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, true, nocash); }, getNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, false, nocash); }, getAllNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, true, nocash); }, getFirst: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, false, nocash); }, getLast: function(match, nocash){ return walk(this, 'previousSibling', 'lastChild', match, false, nocash); }, getParent: function(match, nocash){ return walk(this, 'parentNode', null, match, false, nocash); }, getParents: function(match, nocash){ return walk(this, 'parentNode', null, match, true, nocash); }, getChildren: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, true, nocash); }, getWindow: function(){ return this.ownerDocument.window; }, getDocument: function(){ return this.ownerDocument; }, getElementById: function(id, nocash){ var el = this.ownerDocument.getElementById(id); if (!el) return null; for (var parent = el.parentNode; parent != this; parent = parent.parentNode){ if (!parent) return null; } return $.element(el, nocash); }, getSelected: function(){ return new Elements($A(this.options).filter(function(option){ return option.selected; })); }, getComputedStyle: function(property){ if (this.currentStyle) return this.currentStyle[property.camelCase()]; var computed = this.getDocument().defaultView.getComputedStyle(this, null); return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null; }, toQueryString: function(){ var queryString = []; this.getElements('input, select, textarea', true).each(function(el){ if (!el.name || el.disabled) return; var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){ return opt.value; }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value; $splat(value).each(function(val){ if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val)); }); }); return queryString.join('&'); }, clone: function(contents, keepid){ contents = contents !== false; var clone = this.cloneNode(contents); var clean = function(node, element){ if (!keepid) node.removeAttribute('id'); if (Browser.Engine.trident){ node.clearAttributes(); node.mergeAttributes(element); node.removeAttribute('uid'); if (node.options){ var no = node.options, eo = element.options; for (var j = no.length; j--;) no[j].selected = eo[j].selected; } } var prop = props[element.tagName.toLowerCase()]; if (prop && element[prop]) node[prop] = element[prop]; }; if (contents){ var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*'); for (var i = ce.length; i--;) clean(ce[i], te[i]); } clean(clone, this); return $(clone); }, destroy: function(){ Element.empty(this); Element.dispose(this); clean(this, true); return null; }, empty: function(){ $A(this.childNodes).each(function(node){ Element.destroy(node); }); return this; }, dispose: function(){ return (this.parentNode) ? this.parentNode.removeChild(this) : this; }, hasChild: function(el){ el = $(el, true); if (!el) return false; if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el); return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16); }, match: function(tag){ return (!tag || (tag == this) || (Element.get(this, 'tag') == tag)); } }); Native.implement([Element, Window, Document], { addListener: function(type, fn){ if (type == 'unload'){ var old = fn, self = this; fn = function(){ self.removeListener('unload', fn); old(); }; } else { collected[this.uid] = this; } if (this.addEventListener) this.addEventListener(type, fn, false); else this.attachEvent('on' + type, fn); return this; }, removeListener: function(type, fn){ if (this.removeEventListener) this.removeEventListener(type, fn, false); else this.detachEvent('on' + type, fn); return this; }, retrieve: function(property, dflt){ var storage = get(this.uid), prop = storage[property]; if (dflt != undefined && prop == undefined) prop = storage[property] = dflt; return $pick(prop); }, store: function(property, value){ var storage = get(this.uid); storage[property] = value; return this; }, eliminate: function(property){ var storage = get(this.uid); delete storage[property]; return this; } }); window.addListener('unload', purge); })(); Element.Properties = new Hash; Element.Properties.style = { set: function(style){ this.style.cssText = style; }, get: function(){ return this.style.cssText; }, erase: function(){ this.style.cssText = ''; } }; Element.Properties.tag = { get: function(){ return this.tagName.toLowerCase(); } }; Element.Properties.html = (function(){ var wrapper = document.createElement('div'); var translations = { table: [1, '', '
'], select: [1, ''], tbody: [2, '', '
'], tr: [3, '', '
'] }; translations.thead = translations.tfoot = translations.tbody; var html = { set: function(){ var html = Array.flatten(arguments).join(''); var wrap = Browser.Engine.trident && translations[this.get('tag')]; if (wrap){ var first = wrapper; first.innerHTML = wrap[1] + html + wrap[2]; for (var i = wrap[0]; i--;) first = first.firstChild; this.empty().adopt(first.childNodes); } else { this.innerHTML = html; } } }; html.erase = html.set; return html; })(); if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = { get: function(){ if (this.innerText) return this.innerText; var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body); var text = temp.innerText; temp.destroy(); return text; } }; /* Script: Element.Event.js Contains Element methods for dealing with events, and custom Events. License: MIT-style license. */ Element.Properties.events = {set: function(events){ this.addEvents(events); }}; Native.implement([Element, Window, Document], { addEvent: function(type, fn){ var events = this.retrieve('events', {}); events[type] = events[type] || {'keys': [], 'values': []}; if (events[type].keys.contains(fn)) return this; events[type].keys.push(fn); var realType = type, custom = Element.Events.get(type), condition = fn, self = this; if (custom){ if (custom.onAdd) custom.onAdd.call(this, fn); if (custom.condition){ condition = function(event){ if (custom.condition.call(this, event)) return fn.call(this, event); return true; }; } realType = custom.base || realType; } var defn = function(){ return fn.call(self); }; var nativeEvent = Element.NativeEvents[realType]; if (nativeEvent){ if (nativeEvent == 2){ defn = function(event){ event = new Event(event, self.getWindow()); if (condition.call(self, event) === false) event.stop(); }; } this.addListener(realType, defn); } events[type].values.push(defn); return this; }, removeEvent: function(type, fn){ var events = this.retrieve('events'); if (!events || !events[type]) return this; var pos = events[type].keys.indexOf(fn); if (pos == -1) return this; events[type].keys.splice(pos, 1); var value = events[type].values.splice(pos, 1)[0]; var custom = Element.Events.get(type); if (custom){ if (custom.onRemove) custom.onRemove.call(this, fn); type = custom.base || type; } return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this; }, addEvents: function(events){ for (var event in events) this.addEvent(event, events[event]); return this; }, removeEvents: function(events){ if ($type(events) == 'object'){ for (var type in events) this.removeEvent(type, events[type]); return this; } var attached = this.retrieve('events'); if (!attached) return this; if (!events){ for (var type in attached) this.removeEvents(type); this.eliminate('events'); } else if (attached[events]){ while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]); attached[events] = null; } return this; }, fireEvent: function(type, args, delay){ var events = this.retrieve('events'); if (!events || !events[type]) return this; events[type].keys.each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, cloneEvents: function(from, type){ from = $(from); var fevents = from.retrieve('events'); if (!fevents) return this; if (!type){ for (var evType in fevents) this.cloneEvents(from, evType); } else if (fevents[type]){ fevents[type].keys.each(function(fn){ this.addEvent(type, fn); }, this); } return this; } }); Element.NativeEvents = { click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons mousewheel: 2, DOMMouseScroll: 2, //mouse wheel mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement keydown: 2, keypress: 2, keyup: 2, //keyboard focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window error: 1, abort: 1, scroll: 1 //misc }; (function(){ var $check = function(event){ var related = event.relatedTarget; if (related == undefined) return true; if (related === false) return false; return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related)); }; Element.Events = new Hash({ mouseenter: { base: 'mouseover', condition: $check }, mouseleave: { base: 'mouseout', condition: $check }, mousewheel: { base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel' } }); })(); /* Script: Element.Style.js Contains methods for interacting with the styles of Elements in a fashionable way. License: MIT-style license. */ Element.Properties.styles = {set: function(styles){ this.setStyles(styles); }}; Element.Properties.opacity = { set: function(opacity, novisibility){ if (!novisibility){ if (opacity == 0){ if (this.style.visibility != 'hidden') this.style.visibility = 'hidden'; } else { if (this.style.visibility != 'visible') this.style.visibility = 'visible'; } } if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1; if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')'; this.style.opacity = opacity; this.store('opacity', opacity); }, get: function(){ return this.retrieve('opacity', 1); } }; Element.implement({ setOpacity: function(value){ return this.set('opacity', value, true); }, getOpacity: function(){ return this.get('opacity'); }, setStyle: function(property, value){ switch (property){ case 'opacity': return this.set('opacity', parseFloat(value)); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); if ($type(value) != 'string'){ var map = (Element.Styles.get(property) || '@').split(' '); value = $splat(value).map(function(val, i){ if (!map[i]) return ''; return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; }).join(' '); } else if (value == String(Number(value))){ value = Math.round(value); } this.style[property] = value; return this; }, getStyle: function(property){ switch (property){ case 'opacity': return this.get('opacity'); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); var result = this.style[property]; if (!$chk(result)){ result = []; for (var style in Element.ShortStyles){ if (property != style) continue; for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s)); return result.join(' '); } result = this.getComputedStyle(property); } if (result){ result = String(result); var color = result.match(/rgba?\([\d\s,]+\)/); if (color) result = result.replace(color[0], color[0].rgbToHex()); } if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result)))){ if (property.test(/^(height|width)$/)){ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; values.each(function(value){ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); }, this); return this['offset' + property.capitalize()] - size + 'px'; } if ((Browser.Engine.presto) && String(result).test('px')) return result; if (property.test(/(border(.+)Width|margin|padding)/)) return '0px'; } return result; }, setStyles: function(styles){ for (var style in styles) this.setStyle(style, styles[style]); return this; }, getStyles: function(){ var result = {}; Array.each(arguments, function(key){ result[key] = this.getStyle(key); }, this); return result; } }); Element.Styles = new Hash({ left: '@px', top: '@px', bottom: '@px', right: '@px', width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@' }); Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; ['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ var Short = Element.ShortStyles; var All = Element.Styles; ['margin', 'padding'].each(function(style){ var sd = style + direction; Short[style][sd] = All[sd] = '@px'; }); var bd = 'border' + direction; Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; Short[bd] = {}; Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; }); /* Script: Element.Dimensions.js Contains methods to work with size, scroll, or positioning of Elements and the window object. License: MIT-style license. Credits: - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). */ (function(){ Element.implement({ scrollTo: function(x, y){ if (isBody(this)){ this.getWindow().scrollTo(x, y); } else { this.scrollLeft = x; this.scrollTop = y; } return this; }, getSize: function(){ if (isBody(this)) return this.getWindow().getSize(); return {x: this.offsetWidth, y: this.offsetHeight}; }, getScrollSize: function(){ if (isBody(this)) return this.getWindow().getScrollSize(); return {x: this.scrollWidth, y: this.scrollHeight}; }, getScroll: function(){ if (isBody(this)) return this.getWindow().getScroll(); return {x: this.scrollLeft, y: this.scrollTop}; }, getScrolls: function(){ var element = this, position = {x: 0, y: 0}; while (element && !isBody(element)){ position.x += element.scrollLeft; position.y += element.scrollTop; element = element.parentNode; } return position; }, getOffsetParent: function(){ var element = this; if (isBody(element)) return null; if (!Browser.Engine.trident) return element.offsetParent; while ((element = element.parentNode) && !isBody(element)){ if (styleString(element, 'position') != 'static') return element; } return null; }, getOffsets: function(){ if (Browser.Engine.trident){ var bound = this.getBoundingClientRect(), html = this.getDocument().documentElement; return { x: bound.left + html.scrollLeft - html.clientLeft, y: bound.top + html.scrollTop - html.clientTop }; } var element = this, position = {x: 0, y: 0}; if (isBody(this)) return position; while (element && !isBody(element)){ position.x += element.offsetLeft; position.y += element.offsetTop; if (Browser.Engine.gecko){ if (!borderBox(element)){ position.x += leftBorder(element); position.y += topBorder(element); } var parent = element.parentNode; if (parent && styleString(parent, 'overflow') != 'visible'){ position.x += leftBorder(parent); position.y += topBorder(parent); } } else if (element != this && Browser.Engine.webkit){ position.x += leftBorder(element); position.y += topBorder(element); } element = element.offsetParent; } if (Browser.Engine.gecko && !borderBox(this)){ position.x -= leftBorder(this); position.y -= topBorder(this); } return position; }, getPosition: function(relative){ if (isBody(this)) return {x: 0, y: 0}; var offset = this.getOffsets(), scroll = this.getScrolls(); var position = {x: offset.x - scroll.x, y: offset.y - scroll.y}; var relativePosition = (relative && (relative = $(relative))) ? relative.getPosition() : {x: 0, y: 0}; return {x: position.x - relativePosition.x, y: position.y - relativePosition.y}; }, getCoordinates: function(element){ if (isBody(this)) return this.getWindow().getCoordinates(); var position = this.getPosition(element), size = this.getSize(); var obj = {left: position.x, top: position.y, width: size.x, height: size.y}; obj.right = obj.left + obj.width; obj.bottom = obj.top + obj.height; return obj; }, computePosition: function(obj){ return {left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top')}; }, position: function(obj){ return this.setStyles(this.computePosition(obj)); } }); Native.implement([Document, Window], { getSize: function(){ var win = this.getWindow(); if (Browser.Engine.presto || Browser.Engine.webkit) return {x: win.innerWidth, y: win.innerHeight}; var doc = getCompatElement(this); return {x: doc.clientWidth, y: doc.clientHeight}; }, getScroll: function(){ var win = this.getWindow(); var doc = getCompatElement(this); return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; }, getScrollSize: function(){ var doc = getCompatElement(this); var min = this.getSize(); return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)}; }, getPosition: function(){ return {x: 0, y: 0}; }, getCoordinates: function(){ var size = this.getSize(); return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; } }); // private methods var styleString = Element.getComputedStyle; function styleNumber(element, style){ return styleString(element, style).toInt() || 0; }; function borderBox(element){ return styleString(element, '-moz-box-sizing') == 'border-box'; }; function topBorder(element){ return styleNumber(element, 'border-top-width'); }; function leftBorder(element){ return styleNumber(element, 'border-left-width'); }; function isBody(element){ return (/^(?:body|html)$/i).test(element.tagName); }; function getCompatElement(element){ var doc = element.getDocument(); return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; }; })(); //aliases Native.implement([Window, Document, Element], { getHeight: function(){ return this.getSize().y; }, getWidth: function(){ return this.getSize().x; }, getScrollTop: function(){ return this.getScroll().y; }, getScrollLeft: function(){ return this.getScroll().x; }, getScrollHeight: function(){ return this.getScrollSize().y; }, getScrollWidth: function(){ return this.getScrollSize().x; }, getTop: function(){ return this.getPosition().y; }, getLeft: function(){ return this.getPosition().x; } }); /* Script: Selectors.js Adds advanced CSS Querying capabilities for targeting elements. Also includes pseudoselectors support. License: MIT-style license. */ Native.implement([Document, Element], { getElements: function(expression, nocash){ expression = expression.split(','); var items, local = {}; for (var i = 0, l = expression.length; i < l; i++){ var selector = expression[i], elements = Selectors.Utils.search(this, selector, local); if (i != 0 && elements.item) elements = $A(elements); items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements); } return new Elements(items, {ddup: (expression.length > 1), cash: !nocash}); } }); Element.implement({ match: function(selector){ if (!selector || (selector == this)) return true; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false; var parsed = Selectors.Utils.parseSelector(selector); return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true; } }); var Selectors = {Cache: {nth: {}, parsed: {}}}; Selectors.RegExps = { id: (/#([\w-]+)/), tag: (/^(\w+|\*)/), quick: (/^(\w+|\*)$/), splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g), combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g) }; Selectors.Utils = { chk: function(item, uniques){ if (!uniques) return true; var uid = $uid(item); if (!uniques[uid]) return uniques[uid] = true; return false; }, parseNthArgument: function(argument){ if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument]; var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/); if (!parsed) return false; var inta = parseInt(parsed[1]); var a = (inta || inta === 0) ? inta : 1; var special = parsed[2] || false; var b = parseInt(parsed[3]) || 0; if (a != 0){ b--; while (b < 1) b += a; while (b >= a) b -= a; } else { a = b; special = 'index'; } switch (special){ case 'n': parsed = {a: a, b: b, special: 'n'}; break; case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break; case 'even': parsed = {a: 2, b: 1, special: 'n'}; break; case 'first': parsed = {a: 0, special: 'index'}; break; case 'last': parsed = {special: 'last-child'}; break; case 'only': parsed = {special: 'only-child'}; break; default: parsed = {a: (a - 1), special: 'index'}; } return Selectors.Cache.nth[argument] = parsed; }, parseSelector: function(selector){ if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector]; var m, parsed = {classes: [], pseudos: [], attributes: []}; while ((m = Selectors.RegExps.combined.exec(selector))){ var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7]; if (cn){ parsed.classes.push(cn); } else if (pn){ var parser = Selectors.Pseudo.get(pn); if (parser) parsed.pseudos.push({parser: parser, argument: pa}); else parsed.attributes.push({name: pn, operator: '=', value: pa}); } else if (an){ parsed.attributes.push({name: an, operator: ao, value: av}); } } if (!parsed.classes.length) delete parsed.classes; if (!parsed.attributes.length) delete parsed.attributes; if (!parsed.pseudos.length) delete parsed.pseudos; if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null; return Selectors.Cache.parsed[selector] = parsed; }, parseTagAndID: function(selector){ var tag = selector.match(Selectors.RegExps.tag); var id = selector.match(Selectors.RegExps.id); return [(tag) ? tag[1] : '*', (id) ? id[1] : false]; }, filter: function(item, parsed, local){ var i; if (parsed.classes){ for (i = parsed.classes.length; i--; i){ var cn = parsed.classes[i]; if (!Selectors.Filters.byClass(item, cn)) return false; } } if (parsed.attributes){ for (i = parsed.attributes.length; i--; i){ var att = parsed.attributes[i]; if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false; } } if (parsed.pseudos){ for (i = parsed.pseudos.length; i--; i){ var psd = parsed.pseudos[i]; if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false; } } return true; }, getByTagAndID: function(ctx, tag, id){ if (id){ var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true); return (item && Selectors.Filters.byTag(item, tag)) ? [item] : []; } else { return ctx.getElementsByTagName(tag); } }, search: function(self, expression, local){ var splitters = []; var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){ splitters.push(m1); return ':)' + m2; }).split(':)'); var items, filtered, item; for (var i = 0, l = selectors.length; i < l; i++){ var selector = selectors[i]; if (i == 0 && Selectors.RegExps.quick.test(selector)){ items = self.getElementsByTagName(selector); continue; } var splitter = splitters[i - 1]; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (i == 0){ items = Selectors.Utils.getByTagAndID(self, tag, id); } else { var uniques = {}, found = []; for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques); items = found; } var parsed = Selectors.Utils.parseSelector(selector); if (parsed){ filtered = []; for (var m = 0, n = items.length; m < n; m++){ item = items[m]; if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item); } items = filtered; } } return items; } }; Selectors.Getters = { ' ': function(found, self, tag, id, uniques){ var items = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = items.length; i < l; i++){ var item = items[i]; if (Selectors.Utils.chk(item, uniques)) found.push(item); } return found; }, '>': function(found, self, tag, id, uniques){ var children = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = children.length; i < l; i++){ var child = children[i]; if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child); } return found; }, '+': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); break; } } return found; }, '~': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (!Selectors.Utils.chk(self, uniques)) break; if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); } } return found; } }; Selectors.Filters = { byTag: function(self, tag){ return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag)); }, byID: function(self, id){ return (!id || (self.id && self.id == id)); }, byClass: function(self, klass){ return (self.className && self.className.contains(klass, ' ')); }, byPseudo: function(self, parser, argument, local){ return parser.call(self, argument, local); }, byAttribute: function(self, name, operator, value){ var result = Element.prototype.getProperty.call(self, name); if (!result) return (operator == '!='); if (!operator || value == undefined) return true; switch (operator){ case '=': return (result == value); case '*=': return (result.contains(value)); case '^=': return (result.substr(0, value.length) == value); case '$=': return (result.substr(result.length - value.length) == value); case '!=': return (result != value); case '~=': return result.contains(value, ' '); case '|=': return result.contains(value, '-'); } return false; } }; Selectors.Pseudo = new Hash({ // w3c pseudo selectors checked: function(){ return this.checked; }, empty: function(){ return !(this.innerText || this.textContent || '').length; }, not: function(selector){ return !Element.match(this, selector); }, contains: function(text){ return (this.innerText || this.textContent || '').contains(text); }, 'first-child': function(){ return Selectors.Pseudo.index.call(this, 0); }, 'last-child': function(){ var element = this; while ((element = element.nextSibling)){ if (element.nodeType == 1) return false; } return true; }, 'only-child': function(){ var prev = this; while ((prev = prev.previousSibling)){ if (prev.nodeType == 1) return false; } var next = this; while ((next = next.nextSibling)){ if (next.nodeType == 1) return false; } return true; }, 'nth-child': function(argument, local){ argument = (argument == undefined) ? 'n' : argument; var parsed = Selectors.Utils.parseNthArgument(argument); if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local); var count = 0; local.positions = local.positions || {}; var uid = $uid(this); if (!local.positions[uid]){ var self = this; while ((self = self.previousSibling)){ if (self.nodeType != 1) continue; count ++; var position = local.positions[$uid(self)]; if (position != undefined){ count = position + count; break; } } local.positions[uid] = count; } return (local.positions[uid] % parsed.a == parsed.b); }, // custom pseudo selectors index: function(index){ var element = this, count = 0; while ((element = element.previousSibling)){ if (element.nodeType == 1 && ++count > index) return false; } return (count == index); }, even: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n+1', local); }, odd: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n', local); } }); /* Script: Domready.js Contains the domready custom event. License: MIT-style license. */ Element.Events.domready = { onAdd: function(fn){ if (Browser.loaded) fn.call(this); } }; (function(){ var domready = function(){ if (Browser.loaded) return; Browser.loaded = true; window.fireEvent('domready'); document.fireEvent('domready'); }; if (Browser.Engine.trident){ var temp = document.createElement('div'); (function(){ ($try(function(){ temp.doScroll('left'); return $(temp).inject(document.body).set('html', 'temp').dispose(); })) ? domready() : arguments.callee.delay(50); })(); } else if (Browser.Engine.webkit && Browser.Engine.version < 525){ (function(){ (['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50); })(); } else { window.addEvent('load', domready); document.addEvent('DOMContentLoaded', domready); } })(); /* Script: JSON.js JSON encoder and decoder. License: MIT-style license. See Also: */ var JSON = new Hash({ $specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}, $replaceChars: function(chr){ return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16); }, encode: function(obj){ switch ($type(obj)){ case 'string': return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"'; case 'array': return '[' + String(obj.map(JSON.encode).filter($defined)) + ']'; case 'object': case 'hash': var string = []; Hash.each(obj, function(value, key){ var json = JSON.encode(value); if (json) string.push(JSON.encode(key) + ':' + json); }); return '{' + string + '}'; case 'number': case 'boolean': return String(obj); case false: return 'null'; } return null; }, decode: function(string, secure){ if ($type(string) != 'string' || !string.length) return null; if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null; return eval('(' + string + ')'); } }); Native.implement([Hash, Array, String, Number], { toJSON: function(){ return JSON.encode(this); } }); /* Script: Cookie.js Class for creating, loading, and saving browser Cookies. License: MIT-style license. Credits: Based on the functions by Peter-Paul Koch (http://quirksmode.org). */ var Cookie = new Class({ Implements: Options, options: { path: false, domain: false, duration: false, secure: false, document: document }, initialize: function(key, options){ this.key = key; this.setOptions(options); }, write: function(value){ value = encodeURIComponent(value); if (this.options.domain) value += '; domain=' + this.options.domain; if (this.options.path) value += '; path=' + this.options.path; if (this.options.duration){ var date = new Date(); date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); value += '; expires=' + date.toGMTString(); } if (this.options.secure) value += '; secure'; this.options.document.cookie = this.key + '=' + value; return this; }, read: function(){ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); return (value) ? decodeURIComponent(value[1]) : null; }, dispose: function(){ new Cookie(this.key, $merge(this.options, {duration: -1})).write(''); return this; } }); Cookie.write = function(key, value, options){ return new Cookie(key, options).write(value); }; Cookie.read = function(key){ return new Cookie(key).read(); }; Cookie.dispose = function(key, options){ return new Cookie(key, options).dispose(); }; /* Script: Fx.js Contains the basic animation logic to be extended by all other Fx Classes. License: MIT-style license. */ var Fx = new Class({ Implements: [Chain, Events, Options], options: { /* onStart: $empty, onCancel: $empty, onComplete: $empty, */ fps: 50, unit: false, duration: 500, link: 'ignore' }, initialize: function(options){ this.subject = this.subject || this; this.setOptions(options); this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt(); var wait = this.options.wait; if (wait === false) this.options.link = 'cancel'; }, getTransition: function(){ return function(p){ return -(Math.cos(Math.PI * p) - 1) / 2; }; }, step: function(){ var time = $time(); if (time < this.time + this.options.duration){ var delta = this.transition((time - this.time) / this.options.duration); this.set(this.compute(this.from, this.to, delta)); } else { this.set(this.compute(this.from, this.to, 1)); this.complete(); } }, set: function(now){ return now; }, compute: function(from, to, delta){ return Fx.compute(from, to, delta); }, check: function(caller){ if (!this.timer) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(caller.bind(this, Array.slice(arguments, 1))); return false; } return false; }, start: function(from, to){ if (!this.check(arguments.callee, from, to)) return this; this.from = from; this.to = to; this.time = 0; this.transition = this.getTransition(); this.startTimer(); this.onStart(); return this; }, complete: function(){ if (this.stopTimer()) this.onComplete(); return this; }, cancel: function(){ if (this.stopTimer()) this.onCancel(); return this; }, onStart: function(){ this.fireEvent('start', this.subject); }, onComplete: function(){ this.fireEvent('complete', this.subject); if (!this.callChain()) this.fireEvent('chainComplete', this.subject); }, onCancel: function(){ this.fireEvent('cancel', this.subject).clearChain(); }, pause: function(){ this.stopTimer(); return this; }, resume: function(){ this.startTimer(); return this; }, stopTimer: function(){ if (!this.timer) return false; this.time = $time() - this.time; this.timer = $clear(this.timer); return true; }, startTimer: function(){ if (this.timer) return false; this.time = $time() - this.time; this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this); return true; } }); Fx.compute = function(from, to, delta){ return (to - from) * delta + from; }; Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; /* Script: Fx.CSS.js Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. License: MIT-style license. */ Fx.CSS = new Class({ Extends: Fx, //prepares the base from/to object prepare: function(element, property, values){ values = $splat(values); var values1 = values[1]; if (!$chk(values1)){ values[1] = values[0]; values[0] = element.getStyle(property); } var parsed = values.map(this.parse); return {from: parsed[0], to: parsed[1]}; }, //parses a value into an array parse: function(value){ value = $lambda(value)(); value = (typeof value == 'string') ? value.split(' ') : $splat(value); return value.map(function(val){ val = String(val); var found = false; Fx.CSS.Parsers.each(function(parser, key){ if (found) return; var parsed = parser.parse(val); if ($chk(parsed)) found = {value: parsed, parser: parser}; }); found = found || {value: val, parser: Fx.CSS.Parsers.String}; return found; }); }, //computes by a from and to prepared objects, using their parsers. compute: function(from, to, delta){ var computed = []; (Math.min(from.length, to.length)).times(function(i){ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); }); computed.$family = {name: 'fx:css:value'}; return computed; }, //serves the value as settable serve: function(value, unit){ if ($type(value) != 'fx:css:value') value = this.parse(value); var returned = []; value.each(function(bit){ returned = returned.concat(bit.parser.serve(bit.value, unit)); }); return returned; }, //renders the change to an element render: function(element, property, value, unit){ element.setStyle(property, this.serve(value, unit)); }, //searches inside the page css to find the values for a selector search: function(selector){ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; var to = {}; Array.each(document.styleSheets, function(sheet, j){ var href = sheet.href; if (href && href.contains('://') && !href.contains(document.domain)) return; var rules = sheet.rules || sheet.cssRules; Array.each(rules, function(rule, i){ if (!rule.style) return; var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ return m.toLowerCase(); }) : null; if (!selectorText || !selectorText.test('^' + selector + '$')) return; Element.Styles.each(function(value, style){ if (!rule.style[style] || Element.ShortStyles[style]) return; value = String(rule.style[style]); to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value; }); }); }); return Fx.CSS.Cache[selector] = to; } }); Fx.CSS.Cache = {}; Fx.CSS.Parsers = new Hash({ Color: { parse: function(value){ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; }, compute: function(from, to, delta){ return from.map(function(value, i){ return Math.round(Fx.compute(from[i], to[i], delta)); }); }, serve: function(value){ return value.map(Number); } }, Number: { parse: parseFloat, compute: Fx.compute, serve: function(value, unit){ return (unit) ? value + unit : value; } }, String: { parse: $lambda(false), compute: $arguments(1), serve: $arguments(0) } }); /* Script: Fx.Tween.js Formerly Fx.Style, effect to transition any CSS property for an element. License: MIT-style license. */ Fx.Tween = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = $(element); this.parent(options); }, set: function(property, now){ if (arguments.length == 1){ now = property; property = this.property || this.options.property; } this.render(this.element, property, now, this.options.unit); return this; }, start: function(property, from, to){ if (!this.check(arguments.callee, property, from, to)) return this; var args = Array.flatten(arguments); this.property = this.options.property || args.shift(); var parsed = this.prepare(this.element, this.property, args); return this.parent(parsed.from, parsed.to); } }); Element.Properties.tween = { set: function(options){ var tween = this.retrieve('tween'); if (tween) tween.cancel(); return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('tween')){ if (options || !this.retrieve('tween:options')) this.set('tween', options); this.store('tween', new Fx.Tween(this, this.retrieve('tween:options'))); } return this.retrieve('tween'); } }; Element.implement({ tween: function(property, from, to){ this.get('tween').start(arguments); return this; }, fade: function(how){ var fade = this.get('tween'), o = 'opacity', toggle; how = $pick(how, 'toggle'); switch (how){ case 'in': fade.start(o, 1); break; case 'out': fade.start(o, 0); break; case 'show': fade.set(o, 1); break; case 'hide': fade.set(o, 0); break; case 'toggle': var flag = this.retrieve('fade:flag', this.get('opacity') == 1); fade.start(o, (flag) ? 0 : 1); this.store('fade:flag', !flag); toggle = true; break; default: fade.start(o, arguments); } if (!toggle) this.eliminate('fade:flag'); return this; }, highlight: function(start, end){ if (!end){ end = this.retrieve('highlight:original', this.getStyle('background-color')); end = (end == 'transparent') ? '#fff' : end; } var tween = this.get('tween'); tween.start('background-color', start || '#ffff88', end).chain(function(){ this.setStyle('background-color', this.retrieve('highlight:original')); tween.callChain(); }.bind(this)); return this; } }); /* Script: Fx.Morph.js Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. License: MIT-style license. */ Fx.Morph = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = $(element); this.parent(options); }, set: function(now){ if (typeof now == 'string') now = this.search(now); for (var p in now) this.render(this.element, p, now[p], this.options.unit); return this; }, compute: function(from, to, delta){ var now = {}; for (var p in from) now[p] = this.parent(from[p], to[p], delta); return now; }, start: function(properties){ if (!this.check(arguments.callee, properties)) return this; if (typeof properties == 'string') properties = this.search(properties); var from = {}, to = {}; for (var p in properties){ var parsed = this.prepare(this.element, p, properties[p]); from[p] = parsed.from; to[p] = parsed.to; } return this.parent(from, to); } }); Element.Properties.morph = { set: function(options){ var morph = this.retrieve('morph'); if (morph) morph.cancel(); return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('morph')){ if (options || !this.retrieve('morph:options')) this.set('morph', options); this.store('morph', new Fx.Morph(this, this.retrieve('morph:options'))); } return this.retrieve('morph'); } }; Element.implement({ morph: function(props){ this.get('morph').start(props); return this; } }); /* Script: Fx.Transitions.js Contains a set of advanced transitions to be used with any of the Fx Classes. License: MIT-style license. Credits: Easing Equations by Robert Penner, , modified and optimized to be used with MooTools. */ Fx.implement({ getTransition: function(){ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; if (typeof trans == 'string'){ var data = trans.split(':'); trans = Fx.Transitions; trans = trans[data[0]] || trans[data[0].capitalize()]; if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; } return trans; } }); Fx.Transition = function(transition, params){ params = $splat(params); return $extend(transition, { easeIn: function(pos){ return transition(pos, params); }, easeOut: function(pos){ return 1 - transition(1 - pos, params); }, easeInOut: function(pos){ return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2; } }); }; Fx.Transitions = new Hash({ linear: $arguments(0) }); Fx.Transitions.extend = function(transitions){ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); }; Fx.Transitions.extend({ Pow: function(p, x){ return Math.pow(p, x[0] || 6); }, Expo: function(p){ return Math.pow(2, 8 * (p - 1)); }, Circ: function(p){ return 1 - Math.sin(Math.acos(p)); }, Sine: function(p){ return 1 - Math.sin((1 - p) * Math.PI / 2); }, Back: function(p, x){ x = x[0] || 1.618; return Math.pow(p, 2) * ((x + 1) * p - x); }, Bounce: function(p){ var value; for (var a = 0, b = 1; 1; a += b, b /= 2){ if (p >= (7 - 4 * a) / 11){ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); break; } } return value; }, Elastic: function(p, x){ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3); } }); ['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ Fx.Transitions[transition] = new Fx.Transition(function(p){ return Math.pow(p, [i + 2]); }); }); /* Script: Request.js Powerful all purpose Request Class. Uses XMLHTTPRequest. License: MIT-style license. */ var Request = new Class({ Implements: [Chain, Events, Options], options: {/* onRequest: $empty, onComplete: $empty, onCancel: $empty, onSuccess: $empty, onFailure: $empty, onException: $empty,*/ url: '', data: '', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }, async: true, format: false, method: 'post', link: 'ignore', isSuccess: null, emulation: true, urlEncoded: true, encoding: 'utf-8', evalScripts: false, evalResponse: false }, initialize: function(options){ this.xhr = new Browser.Request(); this.setOptions(options); this.options.isSuccess = this.options.isSuccess || this.isSuccess; this.headers = new Hash(this.options.headers); }, onStateChange: function(){ if (this.xhr.readyState != 4 || !this.running) return; this.running = false; this.status = 0; $try(function(){ this.status = this.xhr.status; }.bind(this)); if (this.options.isSuccess.call(this, this.status)){ this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML}; this.success(this.response.text, this.response.xml); } else { this.response = {text: null, xml: null}; this.failure(); } this.xhr.onreadystatechange = $empty; }, isSuccess: function(){ return ((this.status >= 200) && (this.status < 300)); }, processScripts: function(text){ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text); return text.stripScripts(this.options.evalScripts); }, success: function(text, xml){ this.onSuccess(this.processScripts(text), xml); }, onSuccess: function(){ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); }, failure: function(){ this.onFailure(); }, onFailure: function(){ this.fireEvent('complete').fireEvent('failure', this.xhr); }, setHeader: function(name, value){ this.headers.set(name, value); return this; }, getHeader: function(name){ return $try(function(){ return this.xhr.getResponseHeader(name); }.bind(this)); }, check: function(caller){ if (!this.running) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(caller.bind(this, Array.slice(arguments, 1))); return false; } return false; }, send: function(options){ if (!this.check(arguments.callee, options)) return this; this.running = true; var type = $type(options); if (type == 'string' || type == 'element') options = {data: options}; var old = this.options; options = $extend({data: old.data, url: old.url, method: old.method}, options); var data = options.data, url = options.url, method = options.method; switch ($type(data)){ case 'element': data = $(data).toQueryString(); break; case 'object': case 'hash': data = Hash.toQueryString(data); } if (this.options.format){ var format = 'format=' + this.options.format; data = (data) ? format + '&' + data : format; } if (this.options.emulation && ['put', 'delete'].contains(method)){ var _method = '_method=' + method; data = (data) ? _method + '&' + data : _method; method = 'post'; } if (this.options.urlEncoded && method == 'post'){ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding); } if (data && method == 'get'){ url = url + (url.contains('?') ? '&' : '?') + data; data = null; } this.xhr.open(method.toUpperCase(), url, this.options.async); this.xhr.onreadystatechange = this.onStateChange.bind(this); this.headers.each(function(value, key){ try { this.xhr.setRequestHeader(key, value); } catch (e){ this.fireEvent('exception', [key, value]); } }, this); this.fireEvent('request'); this.xhr.send(data); if (!this.options.async) this.onStateChange(); return this; }, cancel: function(){ if (!this.running) return this; this.running = false; this.xhr.abort(); this.xhr.onreadystatechange = $empty; this.xhr = new Browser.Request(); this.fireEvent('cancel'); return this; } }); (function(){ var methods = {}; ['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){ methods[method] = function(){ var params = Array.link(arguments, {url: String.type, data: $defined}); return this.send($extend(params, {method: method.toLowerCase()})); }; }); Request.implement(methods); })(); Element.Properties.send = { set: function(options){ var send = this.retrieve('send'); if (send) send.cancel(); return this.eliminate('send').store('send:options', $extend({ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') }, options)); }, get: function(options){ if (options || !this.retrieve('send')){ if (options || !this.retrieve('send:options')) this.set('send', options); this.store('send', new Request(this.retrieve('send:options'))); } return this.retrieve('send'); } }; Element.implement({ send: function(url){ var sender = this.get('send'); sender.send({data: this, url: url || sender.options.url}); return this; } }); /* Script: Request.HTML.js Extends the basic Request Class with additional methods for interacting with HTML responses. License: MIT-style license. */ Request.HTML = new Class({ Extends: Request, options: { update: false, evalScripts: true, filter: false }, processHTML: function(text){ var match = text.match(/]*>([\s\S]*?)<\/body>/i); text = (match) ? match[1] : text; var container = new Element('div'); return $try(function(){ var root = '' + text + '', doc; if (Browser.Engine.trident){ doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = false; doc.loadXML(root); } else { doc = new DOMParser().parseFromString(root, 'text/xml'); } root = doc.getElementsByTagName('root')[0]; for (var i = 0, k = root.childNodes.length; i < k; i++){ var child = Element.clone(root.childNodes[i], true, true); if (child) container.grab(child); } return container; }) || container.set('html', text); }, success: function(text){ var options = this.options, response = this.response; response.html = text.stripScripts(function(script){ response.javascript = script; }); var temp = this.processHTML(response.html); response.tree = temp.childNodes; response.elements = temp.getElements('*'); if (options.filter) response.tree = response.elements.filter(options.filter); if (options.update) $(options.update).empty().set('html', response.html); if (options.evalScripts) $exec(response.javascript); this.onSuccess(response.tree, response.elements, response.html, response.javascript); } }); Element.Properties.load = { set: function(options){ var load = this.retrieve('load'); if (load) load.cancel(); return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options)); }, get: function(options){ if (options || ! this.retrieve('load')){ if (options || !this.retrieve('load:options')) this.set('load', options); this.store('load', new Request.HTML(this.retrieve('load:options'))); } return this.retrieve('load'); } }; Element.implement({ load: function(){ this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type})); return this; } }); /* Script: Request.JSON.js Extends the basic Request Class with additional methods for sending and receiving JSON data. License: MIT-style license. */ Request.JSON = new Class({ Extends: Request, options: { secure: true }, initialize: function(options){ this.parent(options); this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'}); }, success: function(text){ this.response.json = JSON.decode(text, this.options.secure); this.onSuccess(this.response.json, text); } }); /* Script: TipsX3.js Tooltips, BubbleTips, whatever they are, they will appear on mouseover License: MIT-style license. Credits: The idea behind Tips.js is based on Bubble Tooltips () by Alessandro Fulcitiniti TipsX3.js is based on Tips.js, with slight modifications, by razvan@e-magine.ro */ /* Class: TipsX3 Display a tip on any element with a title and/or href. Note: Tips requires an XHTML doctype. Arguments: elements - a collection of elements to apply the tooltips to on mouseover. options - an object. See options Below. Options: maxTitleChars - the maximum number of characters to display in the title of the tip. defaults to 30. onShow - optionally you can alter the default onShow behaviour with this option (like displaying a fade in effect); onHide - optionally you can alter the default onHide behaviour with this option (like displaying a fade out effect); showDelay - the delay the onShow method is called. (defaults to 100 ms) hideDelay - the delay the onHide method is called. (defaults to 100 ms) className - the prefix for your tooltip classNames. defaults to 'tool'. the whole tooltip will have as classname: tool-tip the title will have as classname: tool-title the text will have as classname: tool-text offsets - the distance of your tooltip from the mouse. an Object with x/y properties. fixed - if set to true, the toolTip will not follow the mouse. loadingText - text to display as a title while loading an AJAX tooltip. errTitle, errText - text to display when there's a problem with the AJAX request. Example: (start code) (end) Note: The title of the element will always be used as the tooltip body. If you put :: on your title, the text before :: will become the tooltip title. If you put DOM:someElementID in your title, $('someElementID').innerHTML will be used as your tooltip contents (same syntax as above). If you put AJAX:http://www.example.com/path/to/ajax_file.php in your title, the response text will be used as tooltip contents (same syntax as above). Either absolute or relative paths are ok. */ var Garbage = { elements: [], collect: function(el){ if (!el.$tmp){ Garbage.elements.push(el); el.$tmp = {'opacity': 1}; } return el; }, trash: function(elements){ for (var i = 0, j = elements.length, el; i < j; i++){ if (!(el = elements[i]) || !el.$tmp) continue; if (el.$events) el.fireEvent('trash').removeEvents(); for (var p in el.$tmp) el.$tmp[p] = null; for (var p in Element.prototype) el[p] = null; el.htmlElement = el.$tmp = el = null; Garbage.elements.remove(el); } }, empty: function(){ Garbage.collect(window); Garbage.collect(document); Garbage.trash(Garbage.elements); } }; var TipsX3 = new Class({ options: { // modded for X3 onShow: function(tip){ //tip.fade('in'); tip.setStyle('visibility', 'visible'); }, onHide: function(tip){ //tip.fade('out'); tip.setStyle('visibility', 'hidden'); }, maxTitleChars: 8, showDelay: 100, hideDelay: 100, className: 'armory-tooltip', offsets: {'x': 8, 'y': 8}, fixed: false, loadingText: 'Please wait...', errTitle: 'Error', errText: 'There was a problem retrieving the item from the WoW Armory', maxWidth: 250, idName: '' }, initialize: function(elements, options){ this.setOptions(options); this.toolTip = new Element('div', { 'class': this.options.className + '-tip', 'styles': { 'position': 'absolute', 'top': '0', 'left': '0', 'visibility': 'hidden', 'max-width' : this.options.maxWidth, 'z-index' : 5000 } }).inject(document.body); this.wrapper = new Element('div').inject(this.toolTip); $$(elements).each(this.build, this); if (this.options.initialize) this.options.initialize.call(this); }, build: function(el){ if (!el) return false; if (el) Garbage.collect( el ); if( !el.$tmp || el.$tmp == undefined ) return; el.$tmp.myTitle = (el.href && el.getTag() == 'a') ? el.href.replace('http://', '') : (el.rel || false); if (el.title){ // check if we need to extract contents from a DOM element if (el.title.test('^DOM:', 'i')) { el.title = $(el.title.split(':')[1].trim()).innerHTML; } // check for an URL to retrieve content from if (el.title.test('^AJAX:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } // check for an URL to retrieve content from if (el.title.test('^Item:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } // check for an URL to retrieve content from if (el.title.test('^WoWArmory:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } // check for an URL to retrieve content from if (el.title.test('^Character:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } var dual = el.title.split('::'); if (dual.length > 1) { el.$tmp.myTitle = dual[0].trim(); el.$tmp.myText = dual[1].trim(); } else { el.$tmp.myText = el.title; } el.removeAttribute('title'); } else { el.$tmp.myText = false; } if (el.$tmp.myTitle && el.$tmp.myTitle.length > this.options.maxTitleChars) el.$tmp.myTitle = el.$tmp.myTitle.substr(0, this.options.maxTitleChars - 1) + "…"; el.addEvent('mouseenter', function(event){ this.start(el); if (!this.options.fixed) this.locate(event); else this.position(el); }.bind(this)); if (!this.options.fixed) el.addEvent('mousemove', this.locate.bindWithEvent(this)); var end = this.end.bind(this); el.addEvent('mouseleave', end); el.addEvent('trash', end); }, modify: function( element, newTitle, newText ){ element.$tmp.myTitle = newTitle; element.$tmp.myText = newText; this.startFinal( element, false ); }, modifyItem: function( element, newTitle, newText, itemIcon ){ element.$tmp.myTitle = newTitle; element.$tmp.myText = newText; element.$tmp.itemIcon = itemIcon; this.startFinal( element, false ); }, modifyNotActive: function( element, newTitle, newText ){ element.$tmp.myTitle = newTitle; element.$tmp.myText = newText; }, start: function( el ) { this.startFinal( el, true ); }, startFinal: function( el, show ) { this.wrapper.empty(); // check if we have an AJAX Item request - if so, show a loading animation and launch the request if (el.$tmp.myText && el.$tmp.myText.test('^Item:', 'i')) { var linkObj = this; this.ajax = new Request.JSON( { link: 'cancel', evalScripts: false, url: el.$tmp.myText.replace(/Item:/i,'/powered/items/'), onComplete: function( item ){ linkObj.modifyItem( el, '', item.html, item.icon ) }}).get(); el.$tmp.myText = '
'; } // check if we have an AJAX Char request - if so, show a loading animation and launch the request if (el.$tmp.myText && el.$tmp.myText.test('^Character:', 'i')) { var linkObj = this; this.ajax = new Request.HTML( { link: 'cancel', evalScripts: false, url: el.$tmp.myText.replace(/Character:/i,'/powered/characters/'), onSuccess: function( var1, var2, response, var3 ) { linkObj.modify( el, '', response ) }}).get(); el.$tmp.myText = '
'; } // check if we have an AJAX wow armory item request - if so, show a loading animation and launch the request if (el.$tmp.myText && el.$tmp.myText.test('^WoWArmory:', 'i')) { var linkObj = this; var myArmoryUrl = el.$tmp.myText.replace(/WoWArmory:/i,''); myArmoryUrl = '/wowArmoryProxy.php?url=' + escape( myArmoryUrl ); this.ajax = new Request.HTML( { link: 'cancel', evalScripts: false, url: myArmoryUrl, onSuccess: function( var1, var2, response, var3 ) { linkObj.modify( el, '', response ) }}).get(); el.$tmp.myText = '
'; } if( el.$tmp.itemIcon ) { this.wrapper.set( 'html', '
' ); } else { this.wrapper.set( 'html', '
' ); } this.theTip = $( this.options.idName ); if (el.$tmp.myTitle){ this.title = new Element('span').inject( new Element('div', {'class': this.options.className + '-title'}).inject( this.theTip ) ).set( 'html', el.$tmp.myTitle ); } if (el.$tmp.myText){ this.text = new Element('span').inject( new Element('div', {'class': this.options.className + '-text'}).inject( this.theTip ) ).set( 'html', el.$tmp.myText ); } if( show == true ) { $clear(this.timer); this.timer = this.show.delay( this.options.showDelay, this); } }, end: function(event){ $clear(this.timer); this.timer = this.hide.delay( this.options.hideDelay, this); }, position: function(element){ var pos = element.getPosition(); this.toolTip.setStyles({ 'left': pos.x + this.options.offsets.x, 'top': pos.y + this.options.offsets.y }); }, locate: function(event){ var win = {'x': window.getWidth(), 'y': window.getHeight()}; var scroll = {'x': window.getScrollLeft(), 'y': window.getScrollTop()}; var tip = {'x': this.toolTip.offsetWidth, 'y': this.toolTip.offsetHeight}; var prop = {'x': 'left', 'y': 'top'}; for (var z in prop){ var pos = event.page[z] + this.options.offsets[z]; if ((pos + tip[z] - scroll[z]) > win[z]) pos =event.page[z] - this.options.offsets[z] - tip[z]; if( z == 'y' && pos - scroll.y < 10 ) { pos = scroll.y + 10; } this.toolTip.setStyle(prop[z], pos); }; }, show: function(){ if (this.options.timeout) this.timer = this.hide.delay( this.options.timeout, this ); this.fireEvent( 'onShow', [this.toolTip] ); }, hide: function(){ this.fireEvent( 'onHide', [this.toolTip] ); } }); TipsX3.implement( new Events, new Options );var MorphList = new Class({ Implements: [Events, Options], options: {/* onClick: $empty, onMorph: $empty,*/ morph: { 'link': 'cancel' } }, initialize: function(menu, options) { var that = this; this.setOptions(options); this.menu = $(menu); this.menuitems = this.menu.getChildren(); this.menuitems.addEvents({ mouseenter: function(){ that.morphTo(this); }, mouseleave: function(){ that.morphTo(that.current); }, click: function(ev){ that.click(ev, this); } }); this.bg = new Element('li', {'class': 'background'}).adopt(new Element('div', {'class': 'left'})); this.bg.inject(this.menu).set('morph', this.options.morph); this.setCurrent(this.menu.getElement('.current')); }, click: function(ev, item) { this.setCurrent(item, true); this.fireEvent('onClick', [ev, item]); }, setCurrent: function(el, effect){ if(el && ! this.current) { this.bg.set('styles', { left: el.offsetLeft, width: el.offsetWidth, height: el.offsetHeight, top: el.offsetTop }); (effect) ? this.bg.fade('hide').fade('in') : this.bg.fade('show'); } if(this.current) this.current.removeClass('current'); if(el) this.current = el.addClass('current'); }, morphTo: function(to) { if(! this.current) return; this.bg.morph({ left: to.offsetLeft, top: to.offsetTop, width: to.offsetWidth, height: to.offsetHeight }); this.fireEvent('onMorph', to); } }); window.addEvent('domready', function() { // attach fancy menu for browsers other than ie6 if( !Browser.Engine.trident4 ) { new MorphList( $('nav'), { transition: Fx.Transitions.backOut, duration: 700 } ); } // Check all links for item links or character links var allLinks = $$( 'a' ); for (var i = allLinks.length - 1; i >= 0; i--){ var currentLink = allLinks[i]; if( !currentLink.href || currentLink.href == '' ) continue; if( currentLink.rel && currentLink.rel == 'notooltip' ) continue; if( currentLink.id && currentLink.id == 'fdbk_tab' ) continue; var regExp1 = new RegExp( "^http://" + window.location.hostname + "\/items\/", "mi" ); var regExp2 = new RegExp( "^\/items\/", "mi" ); var regExp3 = new RegExp( "^http://" + window.location.hostname + "\/eu\/", "mi" ); var regExp4 = new RegExp( "^\/eu\/", "mi" ); var regExp5 = new RegExp( "^http://" + window.location.hostname + "\/us\/", "mi" ); var regExp6 = new RegExp( "^\/us\/", "mi" ); var regExp7 = new RegExp( "^http://" + window.location.hostname + "\/cn\/", "mi" ); var regExp8 = new RegExp( "^\/cn\/", "mi" ); var regExp9 = new RegExp( "^http://" + window.location.hostname + "\/kr\/", "mi" ); var regExp10 = new RegExp( "^\/kr\/", "mi" ); var regExp11 = new RegExp( "^http://" + window.location.hostname + "\/tw\/", "mi" ); var regExp12 = new RegExp( "^\/tw\/", "mi" ); // Check whether it starts with Armory Light if( regExp1.test( currentLink.href ) == false && regExp2.test( currentLink.href ) == false && regExp3.test( currentLink.href ) == false && regExp4.test( currentLink.href ) == false && regExp5.test( currentLink.href ) == false && regExp6.test( currentLink.href ) == false && regExp7.test( currentLink.href ) == false && regExp8.test( currentLink.href ) == false && regExp9.test( currentLink.href ) == false && regExp10.test( currentLink.href ) == false && regExp11.test( currentLink.href ) == false && regExp12.test( currentLink.href ) == false ) continue; var linkElements = currentLink.href.split( '/' ); var linkRel = currentLink.rel; for (var j=linkElements.length; j <= 6; j++) { linkElements[j] = ''; }; var LinkType = linkElements[3].toLowerCase(); var ItemId = linkElements[4].toLowerCase(); var ServerName = linkElements[4]; var CharacterName = linkElements[5]; var JSONRequestUrl = false; if( LinkType == 'items' && ItemId != '' && ItemId > 0 && linkRel != 'notip' && linkRel.match('wowarmory') == null ) { // Ok it's an item Link, create a span wrapper var mySpan = new Element('span', { 'title': 'Item:' + ItemId, 'class': 'armoryitemtip' } ); mySpan.wraps( currentLink ); } else if( LinkType == 'items' && ItemId != '' && ItemId > 0 && linkRel.match('wowarmory') == 'wowarmory' ) { // Ok it's an item Link for the wow armory, create a span wrapper var linkRelElements = currentLink.rel.split( ':' ); var armoryRegion = linkRelElements[1]; if( armoryRegion == 'us' ) armoryRegion = 'www'; var mySpan = new Element('span', { 'title': 'WoWArmory:http://' + armoryRegion + '.wowarmory.com/item-tooltip.xml?i=' + ItemId + '&s=' + linkRelElements[4] + '&r=' + linkRelElements[2] + '&n=' + linkRelElements[3], 'class': 'armoryitemtip' } ); mySpan.wraps( currentLink ); } else if( ( LinkType == 'eu' || LinkType == 'us' || LinkType == 'kr' || LinkType == 'cn' || LinkType == 'tw' )&& ServerName != '' && CharacterName != '' ) { // Ok it's an item Link, create a span wrapper var mySpan = new Element('span', { 'title': 'Character:' + LinkType + '/' + ServerName + '/' + CharacterName, 'class': 'armoryitemtip' } ); mySpan.wraps( currentLink ); } else { continue; } }; // Preload tooltip background new Element('img',{ src: 'http://static.armory-light.com/images/tooltip-trans.png' }); // Attach tooltips to all armorytip elements var ArmoryLightTips = new TipsX3( $$('.armorytip'), { maxTitleChars: 100, showDelay: 0, maxWidth: 265, idName: 'armoryGenericTipContent' } ); // Attach item tooltips to all armoryitemtip elements var ArmoryLightTips = new TipsX3( $$('.armoryitemtip'), { maxTitleChars: 100, maxWidth: 335, showDelay: 0, idName: 'armoryItemTipContent', loadingText: 'Loading...' } ); // Set highslide options if( undefined !== window.hs ) { hs.graphicsDir = 'http://static.armory-light.com/images/highslide/'; hs.outlineType = 'rounded-black'; hs.wrapperClassName = 'draggable-header no-footer wide-border'; hs.allowSizeReduction = false; hs.preserveContent = false; hs.align = 'center'; hs.dimmingOpacity = 0.35; hs.dragByHeading = false; hs.fadeInOut = true; hs.dimmingGeckoFix = true; hs.dimmingDuration = 20; hs.showCredits = false; hs.objectLoadTime = 'after'; hs.transitions = ["expand"]; hs.Expander.prototype.onDrag = function (sender, e) { return false; }; hs.Expander.prototype.onBeforeClose = function (sender, e) { window.location.hash = '#'; return true; }; } }); Array.prototype.inArray = function( value ) { var i; for (i=0; i < this.length; i++) { // Matches identical (===), not just similar (==). if (this[i] === value) { return true; } } return false; }; var talent = new Array(); var rank = new Array(); var tree = new Array(); var treeStartStop = new Array(); var currentLevel = getLevel(); var numPointsAvailable = currentLevel - 9; var windowTitle = ''; var CurrentSpec = ''; var maxTierArray = new Array(); var ArmoryLightTalentTips = null; // Onload handler window.addEvent( 'domready', function() { // Get the window title if( document.title ) { windowTitle = document.title; var firstArrowPos = windowTitle.indexOf( '»' ); if( firstArrowPos > 0 ) { windowTitle = windowTitle.substring( 0, firstArrowPos ) + '({t0}/{t1}/{t2}) ' + windowTitle.substring( firstArrowPos ); } } // initialise all talents initTalents(); // Create/edit link to talent spec $('resetalltalents').addEvent( 'click', function( e ) { var pQuestion = confirm( 'Do you really want to reset all talent trees?' ); if( pQuestion ) { CurrentSpec = ''; CheckSpec(); for (theTalentID = 0; talent[theTalentID]; theTalentID++) { RenderOneTalent( theTalentID, false ); } RenderTalents(); // prevent default link e.stop(); return false; } }); // initialise tooltips ArmoryLightTalentTips = new TipsX3( $$('.armorytiptalents'), { maxTitleChars: 100, showDelay: 0, maxWidth: 300, idName: 'armoryTalentTipContent' } ); }); window.addEvent( 'load', function() { var myFx = new Fx.Tween( 'overlay' ); myFx.start( 'opacity', '0.85', '0' ); }); function UpdateControls() { // Display available points var tPointsAvail = getAvailablePoints(); if( tPointsAvail == 0 ) tPointsAvail = 'none'; $('pointsAvailable').set( 'text', tPointsAvail ); // Display available points var tRequiredLevel = 80 - ( getAvailablePoints() ) ; if( tRequiredLevel < 10 ) tRequiredLevel = 10; $('requiresLevel').set( 'text', tRequiredLevel ); // Update spent points for( var i=1; i <=3; i++ ) { var control = $( 'talentTreePointsSpent' + i ); control.set( 'text', getSpentPointsInTree( i-1 ) ); }; // Update window title if( document.title ) { var obj = { t0: getSpentPointsInTree( 0 ).toString(), t1: getSpentPointsInTree( 1 ).toString(), t2: getSpentPointsInTree( 2 ).toString() }; var title = windowTitle; document.title = title.substitute( obj ); } } // Fill the trees with talents function initTalents() { // First, hide the table with an overlay var tablesContainer = $('talentTrees'); var tablesContainerSize = tablesContainer.getSize(); $('overlay').style.width = tablesContainerSize.x + 'px'; $('overlay').style.height = tablesContainerSize.y + 'px'; $('overlay').style.display = 'block'; // Get current talent tree CurrentSpec = getTalents(); CheckSpec(); // Create/edit link to talent spec $('talentbuildlink').set( 'href', '/talents/' + playerClass.toLowerCase().stripSpaces() + '/' + CurrentSpec ); // Set the tree backgrounds $( 'talentTree0' ).setStyle( 'background-image', 'url(http://static.armory-light.com/images/talentCalculator/' + playerClass + '/' + tree[0].toLowerCase().stripSpaces() + '/background.jpg)' ); $( 'talentTree1' ).setStyle( 'background-image', 'url(http://static.armory-light.com/images/talentCalculator/' + playerClass + '/' + tree[1].toLowerCase().stripSpaces() + '/background.jpg)' ); $( 'talentTree2' ).setStyle( 'background-image', 'url(http://static.armory-light.com/images/talentCalculator/' + playerClass + '/' + tree[2].toLowerCase().stripSpaces() + '/background.jpg)' ); // Set the max tier for each tree to 0 maxTierArray[0] = 0; maxTierArray[1] = 0; maxTierArray[2] = 0; // Make text in talent trees not selectable disableSelection( $( 'talentTrees' ) ); // Display the controls for (var i=1; i <=3; i++) { var controls = $( 'talentTreeControls' + i ); var controlHtml = ' '; controlHtml += tree[i-1]; controlHtml += ' ()'; controls.set( 'html', controlHtml ); }; // Attach tooltips var ArmoryLightTipsGenerated = new TipsX3( $$('.armorytipgenerated'), { maxTitleChars: 100, showDelay: 0, maxWidth: 265, idName: 'armoryGenericTipContentGenerated' } ); UpdateControls(); // Figure out which image we use var cTalentIcons = 'http://static.armory-light.com/images/talentCalculator/' + playerClass.toLowerCase().stripSpaces() + '/' + playerClass.toLowerCase().stripSpaces(); // Loop over all talents var iPosition = 0; var cMyIconIndent = 0; var cTreeTurnover = 0; for (theTalentID = 0; talent[theTalentID]; theTalentID++) { curTalent = talent[theTalentID]; var cTree = curTalent[0]; // Tree 0-2 var cColumn = curTalent[3]; // Column 1-4 var cTier = curTalent[4]; // Tier 1-11 (11 for the patch 3.0.2 talents) var cName = curTalent[1]; // The talent name var cMaxPoint = curTalent[2]; // Maximum number of points for that talent (1-5) // Find out the ID of the element (for example s0t9c2) var tId = 's' + cTree + 't' + cTier + 'c' + cColumn; var tIdElement = $( tId ); // Find out how many points were spent for that talent var talentPointsSet = getSpentPointsInTalent( iPosition ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // If we're in a new tree, reset the indent if( cTreeTurnover != cTree ) { cTreeTurnover = cTree; cMyIconIndent = 0; } // If talent points are spent, update the maxTier array if( talentPointsSet > 0 ) maxTierArray[cTree] = cTier; // Fill the ID element with a clickable div and a span which holds the talent points tIdElement.set( 'html', '
', talentPointsSet, '/', cMaxPoint, '
' ); // Add the hasTalent class (this adds the background graphic for that talent slot) tIdElement.addClass( 'hasTalent' ); var myTalentImage = $( 'talent' + tId ); myTalentImage.setStyle( 'background-image', 'url(' + cTalentIcons + '_' + (cTree + 1) + '.jpg)' ); if( talentPointsSet > 0 ) myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px 0px' ); else myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px -36px' ); // Find out which other talents this talent depends on if( curTalent[5] && curTalent[5][0] && typeof curTalent[5] == "object" ) { var dependency = curTalent[5][0]; var depTalent = talent[dependency]; var stepsLeft = ( cColumn - depTalent[3] ); var stepsUp = ( cTier - depTalent[4] ); var imageFile = ''; if( stepsUp > 0 ) imageFile += "down-" + stepsUp; if( stepsLeft > 0 ) imageFile += "-right-" + stepsLeft; else if( stepsLeft < 0 ) imageFile += "-left-" + (stepsLeft*-1); var arrowColor = 'grey'; if( getSpentPointsInTalent( iPosition ) == cMaxPoint ) arrowColor = 'yellow'; else if( getAvailablePoints() <= 0 ) arrowColor = 'grey'; else if( isDependencySatisfied( curTalent ) && getSpentPointsInTree( cTree ) >= ( cTier - 1 ) * 5 ) arrowColor = 'green'; var myArrow = new Element( 'div', { 'class' : arrowColor + ' dependency arrow-' + imageFile, 'id' : 'arrow' + tId }); // Inject the arrow element myArrow.inject( tIdElement, 'top' ); } // Add hover and click events var myButton = $( 'talentButton' + iPosition ); if( Browser.Engine.trident == true ) myButton.addEvent( 'dblclick', function( event ) { clickTalent( event, this.getProperty( 'rel' ) ) } ); myButton.addEvent( 'mousedown', function( event ) { clickTalent( event, this.getProperty( 'rel' ) ) } ); myButton.addEvent( 'contextmenu', function( event ) { event.stop(); return false; } ); // Add the correct class to the TD, remove the other classes if( getSpentPointsInTalent( iPosition ) == 0 ) { tIdElement.addClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( iPosition ) == cMaxPoint ) { tIdElement.addClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( iPosition ) < cMaxPoint ) { tIdElement.addClass( 'talentSomePoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); } // Check whether the talent can be learned if( getAvailablePoints() > 0 && parseInt( getSpentPointsInTree( cTree ) ) >= parseInt( ( cTier - 1 ) * 5 ) && isDependencySatisfied( curTalent ) ) tIdElement.addClass( 'talentCanLearn' ); else tIdElement.removeClass( 'talentCanLearn' ); // Go to next position iPosition++; // Increase the indent by 36 (pixels) since each talent icon is offset by 36 pixels cMyIconIndent += 36; } } // This function re-renders the talent tree based on the current spec function RenderOneTalent( theTalentID, callRenderTalents ) { curTalent = talent[theTalentID]; var cTree = curTalent[0]; var cColumn = curTalent[3]; var cTier = curTalent[4]; var cName = curTalent[1]; var cMaxPoint = curTalent[2]; var cMyIconIndent = theTalentID * 36; if( cTree > 0 ) cMyIconIndent = cMyIconIndent - ( parseInt( treeStartStop[0] + 1 ) * 36); if( cTree > 1 ) cMyIconIndent = cMyIconIndent + ( parseInt( treeStartStop[0] + 1 ) * 36) - ( parseInt( treeStartStop[1] + 1 ) * 36 ); // Find out the ID of the element (for example s0t9c2) var tId = 's' + cTree + 't' + cTier + 'c' + cColumn; var tIdElement = $( tId ); // Find out how many points were spent for that talent var talentPointsSet = getSpentPointsInTalent( theTalentID ); var myTalentImage = $( 'talent' + tId ); var myTalentPoints = $( 'talentPoints' + theTalentID ); // Set the background position of the talent icon, greying it out or making it visible if( talentPointsSet > 0 ) myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px 0px' ); else myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px -36px' ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // Set the talent button title ArmoryLightTalentTips.modify( $('talentButton' + theTalentID ), cName, cRankDescription ); // Update the points myTalentPoints.set( 'text', talentPointsSet + '/' + cMaxPoint ); // Add the correct class to the TD if( getSpentPointsInTalent( theTalentID ) == 0 ) { tIdElement.addClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( theTalentID ) == cMaxPoint ) { tIdElement.addClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( theTalentID ) < cMaxPoint ) { tIdElement.addClass( 'talentSomePoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); } if( callRenderTalents ) RenderTalents(); } // This function re-renders the talent tree based on the current spec function RenderTalents() { UpdateControls(); // Create/edit link to talent spec $('talentbuildlink').set( 'href', '/talents/' + playerClass.toLowerCase().stripSpaces() + '/' + CurrentSpec ); // Loop over all talents var iPosition = 0; for (theTalentID = 0; talent[theTalentID]; theTalentID++) { curTalent = talent[theTalentID]; var cTree = curTalent[0]; var cColumn = curTalent[3]; var cTier = curTalent[4]; var cName = curTalent[1]; var cMaxPoint = curTalent[2]; // Find out how many points were spent for that talent var talentPointsSet = getSpentPointsInTalent( theTalentID ); // If talent points are spent, update the maxTier array if( talentPointsSet > 0 ) maxTierArray[cTree] = cTier; // Find out the ID of the element (for example s0t9c2) var tId = 's' + cTree + 't' + cTier + 'c' + cColumn; var tIdElement = $( tId ); // Check whether the talent can be learned if( getAvailablePoints() > 0 && getSpentPointsInTree( cTree ) >= ( cTier - 1 ) * 5 && isDependencySatisfied( curTalent ) ) { // CanLearn, check if the element already has the class if( !tIdElement.hasClass( 'talentCanLearn' ) ) { tIdElement.addClass( 'talentCanLearn' ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // Set the talent button title ArmoryLightTalentTips.modifyNotActive( $('talentButton' + theTalentID ), cName, cRankDescription ); } } else { // CanNOTLearn, check if the element already has the class if( tIdElement.hasClass( 'talentCanLearn' ) ) { tIdElement.removeClass( 'talentCanLearn' ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // Set the talent button title ArmoryLightTalentTips.modifyNotActive( $('talentButton' + theTalentID ), cName, cRankDescription ); } } // Find out which other talents this talent depends on if( curTalent[5] && curTalent[5][0] && typeof curTalent[5] == "object" ) { var arrowColor = 'grey'; if( getSpentPointsInTalent( iPosition ) == cMaxPoint ) arrowColor = 'yellow'; else if( getAvailablePoints() <= 0 ) arrowColor = 'grey'; else if( isDependencySatisfied( curTalent ) && getSpentPointsInTree( cTree ) >= ( cTier - 1 ) * 5 ) arrowColor = 'green'; // Try to find the arrow element var myArrow = $( 'arrow' + tId ); // if the arrow element really exists... if( myArrow ) { if( arrowColor == 'grey' ) { myArrow.removeClass( 'yellow' ); myArrow.removeClass( 'green' ); myArrow.addClass( 'grey' ); } else if( arrowColor == 'yellow' ) { myArrow.removeClass( 'grey' ); myArrow.removeClass( 'green' ); myArrow.addClass( 'yellow' ); } else if( arrowColor == 'green' ) { myArrow.removeClass( 'grey' ); myArrow.removeClass( 'yellow' ); myArrow.addClass( 'green' ); } } } // Go to next position iPosition++; } } // Checks a talent tree for correct length function CheckSpec() { if( CurrentSpec.length != ( parseInt( treeStartStop[2] ) + 1 ) ) { CurrentSpec = CurrentSpec.pad( parseInt( treeStartStop[2] ) + 1, "0", 1 ); } } // Strip all spaces from a string String.prototype.stripSpaces = function() { return this.replace( /\s/g, "" ); }; String.prototype.pad = function(l, s, t){ return s || (s = " "), (l -= this.length) > 0 ? (s = new Array(Math.ceil(l / s.length) + 1).join(s)).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2)) + this + s.substr(0, l - t) : this; }; function getTalentID(talentName) { var theTalentID; for (theTalentID = 0; talent[theTalentID]; theTalentID++) { if (talent[theTalentID][1] == talentName) return theTalentID; } } function getAvailablePoints() { return parseInt( numPointsAvailable - getSpentPoints( CurrentSpec ) ); } function getSpentPoints( TalentTree ) { var PointsSpent = 0; for( var i = TalentTree.length - 1; i >= 0; i-- ) { PointsSpent += parseInt( TalentTree.charAt( i ) ); }; return PointsSpent; } function getSpentPointsInTalent( talentID ) { var PointsSpent = CurrentSpec.charAt( talentID ); if( PointsSpent == "" ) PointsSpent = 0; return parseInt( PointsSpent ); } function getSpentPointsInTree( treeID ) { var myTalentTree = new Array(); myTalentTree[0] = CurrentSpec.substring( 0, treeStartStop[0] + 1 ); myTalentTree[1] = CurrentSpec.substring( treeStartStop[0] + 1, treeStartStop[1] + 1 ); myTalentTree[2] = CurrentSpec.substring( treeStartStop[1] + 1, treeStartStop[2] + 1 ); var cSelectedTree = myTalentTree[treeID]; var PointsSpent = 0; for( var i = cSelectedTree.length - 1; i >= 0; i-- ) { PointsSpent += parseInt( cSelectedTree.charAt( i ) ); }; return PointsSpent; } function isDependencySatisfied( learnTalent ) { // Does the talent have a dependency and is that dependency satisfied if( learnTalent[5] && learnTalent[5][0] && typeof learnTalent[5] == "object" ) { var dependency = learnTalent[5][0]; var dependantPoints = learnTalent[5][1]; var depTalent = talent[dependency]; var depTalentID = getTalentID( depTalent[1] ); if( getSpentPointsInTalent( depTalentID ) != dependantPoints ) return false; } return true; } function hasDependentTalentWithPoints( talentID ) { var loopStart; var loopStop; var theTree = talent[talentID][0]; if ( talentID != 0 ) loopStart = talentID - 1; else loopStart = talentID; loopStop = treeStartStop[theTree]; while( loopStart <= loopStop ){ if( talent[loopStart][5] && talent[loopStart][5][0] == talentID && getSpentPointsInTalent( loopStart ) != 0 ) return true; loopStart++; } return false; } function getPointsSpentInTier( theTree, theTier ) { var PointsSpentInTier = 0; for (var i = talent.length - 1; i >= 0; i--){ if( talent[i][0] == theTree && talent[i][4] == theTier ) { // This is a talent in the right tree and in the right tier! PointsSpentInTier += parseInt( getSpentPointsInTalent( i ) ); } }; return PointsSpentInTier; } function clickTalent( e, talentID ) { if( e.shift || e.alt || e.control || e.meta || e.rightClick ) UnlearnTalent( talentID, 1 ); else LearnTalent( talentID, 1 ); e.stop(); } function LearnTalent( talentID, numPoints ) { var learnTalent = talent[talentID]; var cTree = learnTalent[0]; var cColumn = learnTalent[3]; var cTier = learnTalent[4]; var cName = learnTalent[1]; var cMaxPoint = learnTalent[2]; // Check whether there are points left to learn if( getAvailablePoints() <= 0 ) return; // Check whether max number of points already spent in that talent if( getSpentPointsInTalent( talentID ) >= cMaxPoint ) return; // Depending on the tier, calculate whether enough points were spent to go deeper into the tree if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) return; // Does the talent have a dependency and is that dependency satisfied if( !isDependencySatisfied( learnTalent ) ) return; // Ok, all dependencies are satisfied... build the new tree with one more point spent :) CurrentSpec = (CurrentSpec.slice( 0, talentID )).toString() + ( parseInt( getSpentPointsInTalent( talentID ) ) + numPoints ).toString() + (CurrentSpec.slice( parseInt( talentID ) + 1 ) ).toString(); // Render the talent trees RenderOneTalent( talentID, true ); } function UnlearnTalent( talentID, numPoints ) { // Check whether there are points spent in that talent if( getSpentPointsInTalent( talentID ) < numPoints ) return; if( hasDependentTalentWithPoints( talentID ) ) return; var unLearnTalent = talent[talentID]; var cTree = unLearnTalent[0]; var cColumn = unLearnTalent[3]; var cTier = unLearnTalent[4]; var cName = unLearnTalent[1]; var cMaxPoint = unLearnTalent[2]; // Get the max tier for this tree var maxTier = parseInt( maxTierArray[cTree] ); // Unlearning in the max tier is always allowed if( cTier != maxTier ) { var numPointsSpentInTier = new Array(); // For every tier up to the max tier, check if there are enough points left for( var i = maxTier; i >= 1; i-- ) { // Get the number of points spent in this tier numPointsSpentInTier[i] = getPointsSpentInTier( cTree, i ); // If we are removing points from THIS tier, calculate this too if( cTier == i ) numPointsSpentInTier[i] = numPointsSpentInTier[i] - parseInt( numPoints ); // Fix if below 0 if( numPointsSpentInTier[i] < 0 ) numPointsSpentInTier[i] = 0; }; for( var i = maxTier; i >= 1; i-- ) { // Calculate how many points we need for the talents in this tier var pointsRequired = parseInt( ( i - 1 ) * 5 ); var numPointsSpentSoFar = 0; var j = (i-1); while( j >= 1 ) { numPointsSpentSoFar += parseInt( numPointsSpentInTier[j] ); j--; } // Return if number of points not satisfied if( numPointsSpentSoFar < pointsRequired ) return; } } // Ok, all dependencies are satisfied... build the new tree with one less point spent :) CurrentSpec = (CurrentSpec.slice( 0, talentID )).toString() + ( parseInt( getSpentPointsInTalent( talentID ) ) - numPoints ).toString() + (CurrentSpec.slice( parseInt( talentID ) + 1 ) ).toString(); // Render the talent trees RenderOneTalent( talentID, true ); } function disableSelection( element ) { if( typeof element.onselectstart != "undefined" ) element.onselectstart = function() { return false; }; if( !element || !element.style ) return; element.onmousedown = function(){ return false; }; element.unselectable = 'on'; element.setStyle( 'MozUserSelect', 'none' ); element.setStyle( 'cursor', 'default' ) ; } var playerClass = "warlock"; var i = 0; var t = 0; var className = "Warlock Talents"; tree[i] = "Affliction"; i++; tree[i] = "Demonology"; i++; tree[i] = "Destruction"; i++; i = 0; talent[i] = [0, "Improved Curse of Agony", 2, 1, 1]; i++; talent[i] = [0, "Suppression", 3, 2, 1]; i++; talent[i] = [0, "Improved Corruption", 5, 3, 1]; i++; talent[i] = [0, "Improved Curse of Weakness", 2, 1, 2]; i++; talent[i] = [0, "Improved Drain Soul", 2, 2, 2]; i++; talent[i] = [0, "Improved Life Tap", 2, 3, 2]; i++; talent[i] = [0, "Soul Siphon", 2, 4, 2]; i++; talent[i] = [0, "Improved Fear", 2, 1, 3]; i++; talent[i] = [0, "Fel Concentration", 3, 2, 3]; i++; talent[i] = [0, "Amplify Curse", 1, 3, 3]; i++; talent[i] = [0, "Grim Reach", 2, 1, 4]; i++; talent[i] = [0, "Nightfall", 2, 2, 4]; i++; talent[i] = [0, "Empowered Corruption", 3, 4, 4]; i++; talent[i] = [0, "Shadow Embrace", 5, 1, 5]; i++; talent[i] = [0, "Siphon Life", 1, 2, 5]; i++; talent[i] = [0, "Curse of Exhaustion", 1, 3, 5, [getTalentID("Amplify Curse"),1]]; i++; talent[i] = [0, "Improved Felhunter", 2, 1, 6]; i++; talent[i] = [0, "Shadow Mastery", 5, 2, 6, [getTalentID("Siphon Life"),1]]; i++; talent[i] = [0, "Eradication", 3, 1, 7]; i++; talent[i] = [0, "Contagion", 5, 2, 7]; i++; talent[i] = [0, "Dark Pact", 1, 3, 7]; i++; talent[i] = [0, "Improved Howl of Terror", 2, 1, 8]; i++; talent[i] = [0, "Malediction", 3, 3, 8]; i++; talent[i] = [0, "Death's Embrace", 3, 1, 9]; i++; talent[i] = [0, "Unstable Affliction", 1, 2, 9, [getTalentID("Contagion"),5]]; i++; talent[i] = [0, "Pandemic", 1, 3, 9, [getTalentID("Unstable Affliction"),1]]; i++; talent[i] = [0, "Everlasting Affliction", 5, 2, 10]; i++; talent[i] = [0, "Haunt", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [1, "Improved Healthstone", 2, 1, 1]; i++; talent[i] = [1, "Improved Imp", 3, 2, 1]; i++; talent[i] = [1, "Demonic Embrace", 3, 3, 1]; i++; talent[i] = [1, "Fel Synergy", 2, 4, 1]; i++; talent[i] = [1, "Improved Health Funnel", 2, 1, 2]; i++; talent[i] = [1, "Demonic Brutality", 3, 2, 2]; i++; talent[i] = [1, "Fel Vitality", 3, 3, 2]; i++; talent[i] = [1, "Improved Succubus", 3, 1, 3]; i++; talent[i] = [1, "Soul Link", 1, 2, 3]; i++; talent[i] = [1, "Fel Domination", 1, 3, 3]; i++; talent[i] = [1, "Demonic Aegis", 3, 4, 3]; i++; talent[i] = [1, "Unholy Power", 5, 2, 4, [getTalentID("Soul Link"),1]]; i++; talent[i] = [1, "Master Summoner", 2, 3, 4, [getTalentID("Fel Domination"),1]]; i++; talent[i] = [1, "Mana Feed", 1, 1, 5, [getTalentID("Unholy Power"),5]]; i++; talent[i] = [1, "Master Conjuror", 2, 3, 5]; i++; talent[i] = [1, "Master Demonologist", 5, 2, 6, [getTalentID("Unholy Power"),5]]; i++; talent[i] = [1, "Molten Core", 3, 3, 6]; i++; talent[i] = [1, "Demonic Resilience", 3, 1, 7]; i++; talent[i] = [1, "Demonic Empowerment", 1, 2, 7, [getTalentID("Master Demonologist"),5]]; i++; talent[i] = [1, "Demonic Knowledge", 3, 3, 7]; i++; talent[i] = [1, "Demonic Tactics", 5, 2, 8]; i++; talent[i] = [1, "Decimation", 2, 3, 8]; i++; talent[i] = [1, "Improved Demonic Tactics", 3, 1, 9, [getTalentID("Demonic Tactics"),5]]; i++; talent[i] = [1, "Summon Felguard", 1, 2, 9]; i++; talent[i] = [1, "Nemesis", 3, 3, 9]; i++; talent[i] = [1, "Demonic Pact", 5, 2, 10]; i++; talent[i] = [1, "Metamorphosis", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [2, "Improved Shadow Bolt", 5, 2, 1]; i++; talent[i] = [2, "Bane", 5, 3, 1]; i++; talent[i] = [2, "Aftermath", 2, 1, 2]; i++; talent[i] = [2, "Molten Skin", 3, 2, 2]; i++; talent[i] = [2, "Cataclysm", 3, 3, 2]; i++; talent[i] = [2, "Demonic Power", 2, 1, 3]; i++; talent[i] = [2, "Shadowburn", 1, 2, 3]; i++; talent[i] = [2, "Ruin", 5, 3, 3]; i++; talent[i] = [2, "Intensity", 2, 1, 4]; i++; talent[i] = [2, "Destructive Reach", 2, 2, 4]; i++; talent[i] = [2, "Improved Searing Pain", 3, 4, 4]; i++; talent[i] = [2, "Backlash", 3, 1, 5, [getTalentID("Intensity"),2]]; i++; talent[i] = [2, "Improved Immolate", 3, 2, 5]; i++; talent[i] = [2, "Devastation", 1, 3, 5, [getTalentID("Ruin"),5]]; i++; talent[i] = [2, "Nether Protection", 3, 1, 6]; i++; talent[i] = [2, "Emberstorm", 5, 3, 6]; i++; talent[i] = [2, "Conflagrate", 1, 2, 7, [getTalentID("Improved Immolate"),3]]; i++; talent[i] = [2, "Soul Leech", 3, 3, 7]; i++; talent[i] = [2, "Pyroclasm", 3, 4, 7]; i++; talent[i] = [2, "Shadow and Flame", 5, 2, 8]; i++; talent[i] = [2, "Improved Soul Leech", 2, 3, 8, [getTalentID("Soul Leech"),3]]; i++; talent[i] = [2, "Backdraft", 3, 1, 9, [getTalentID("Conflagrate"),1]]; i++; talent[i] = [2, "Shadowfury", 1, 2, 9]; i++; talent[i] = [2, "Empowered Imp", 3, 3, 9]; i++; talent[i] = [2, "Fire and Brimstone", 5, 2, 10]; i++; talent[i] = [2, "Chaos Bolt", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; i = 0; // Name: Improved Curse of Agony rank[i] = [ "Increases the damage done by your Curse of Agony by 5%.", "Increases the damage done by your Curse of Agony by 10%." ]; i++; // Name: Suppression rank[i] = [ "Increases your chance to hit with spells by 1%, and reduces the mana cost of your Affliction spells by 2%.", "Increases your chance to hit with spells by 2%, and reduces the mana cost of your Affliction spells by 4%.", "Increases your chance to hit with spells by 3%, and reduces the mana cost of your Affliction spells by 6%." ]; i++; // Name: Improved Corruption rank[i] = [ "Increases the damage done by your Corruption by 2%, and increases the critical strike chance of your Seed of Corruption by 1%.", "Increases the damage done by your Corruption by 4%, and increases the critical strike chance of your Seed of Corruption by 2%.", "Increases the damage done by your Corruption by 6%, and increases the critical strike chance of your Seed of Corruption by 3%.", "Increases the damage done by your Corruption by 8%, and increases the critical strike chance of your Seed of Corruption by 4%.", "Increases the damage done by your Corruption by 10%, and increases the critical strike chance of your Seed of Corruption by 5%." ]; i++; // Name: Improved Curse of Weakness rank[i] = [ "Increases the amount of attack power reduced by your Curse of Weakness by 10%.", "Increases the amount of attack power reduced by your Curse of Weakness by 20%." ]; i++; // Name: Improved Drain Soul rank[i] = [ "Returns 7% of your maximum mana if the target is killed by you while you drain its soul.  In addition, your Affliction spells generate 5% less threat.", "Returns 15% of your maximum mana if the target is killed by you while you drain its soul.  In addition, your Affliction spells generate 10% less threat." ]; i++; // Name: Improved Life Tap rank[i] = [ "Increases the amount of Mana awarded by your Life Tap spell by 10%.", "Increases the amount of Mana awarded by your Life Tap spell by 20%." ]; i++; // Name: Soul Siphon rank[i] = [ "Increases the amount drained by your Drain Life and Drain Soul spells by an additional 3% for each of your Affliction effects on the target, up to a maximum of 9% additional effect.", "Increases the amount drained by your Drain Life and Drain Soul spells by an additional 6% for each of your Affliction effects on the target, up to a maximum of 18% additional effect." ]; i++; // Name: Improved Fear rank[i] = [ "Causes your Fear spell to inflict a Nightmare on the target when the fear effect ends. The Nightmare effect reduces the target\'s movement speed by 15% for 5 sec.", "Causes your Fear spell to inflict a Nightmare on the target when the fear effect ends. The Nightmare effect reduces the target\'s movement speed by 30% for 5 sec." ]; i++; // Name: Fel Concentration rank[i] = [ "Reduces the pushback suffered from damaging attacks  while casting Drain Life, Drain Mana, Drain Soul, Unstable Affliction, and Haunt by 23%.", "Reduces the pushback suffered from damaging attacks  while casting Drain Life, Drain Mana, Drain Soul, Unstable Affliction, and Haunt spells by 46%.", "Reduces the pushback suffered from damaging attacks  while casting Drain Life, Drain Mana, Drain Soul,  Unstable Affliction, and Haunt spells by 70%." ]; i++; // Name: Amplify Curse rank[i] = [ "Reduces the global cooldown of your Curses by 0.5 sec." ]; i++; // Name: Grim Reach rank[i] = [ "Increases the range of your Affliction spells by 10%.", "Increases the range of your Affliction spells by 20%." ]; i++; // Name: Nightfall rank[i] = [ "Gives your Corruption and Drain Life spells a 2% chance to cause you to enter a Shadow Trance state after damaging the opponent.  The Shadow Trance state reduces the casting time of your next Shadow Bolt spell by 100%.", "Gives your Corruption and Drain Life spells a 4% chance to cause you to enter a Shadow Trance state after damaging the opponent.  The Shadow Trance state reduces the casting time of your next Shadow Bolt spell by 100%." ]; i++; // Name: Empowered Corruption rank[i] = [ "Increases the damage of your Corruption spell by an amount equal to 12% of your spell power.", "Increases the damage of your Corruption spell by an amount equal to 24% of your spell power.", "Increases the damage of your Corruption spell by an amount equal to 36% of your spell power." ]; i++; // Name: Shadow Embrace rank[i] = [ "Your Shadow Bolt and Haunt spells apply the Shadow Embrace effect, increasing all shadow periodic damage dealt to the target by you by 1%, and reduces all periodic healing done to the target by 3%. Lasts for 12 sec. Stacks up to 2 times.", "Your Shadow Bolt and Haunt spells apply the Shadow Embrace effect, increasing all shadow periodic damage dealt to the target by you by 2%, and reduces all periodic healing done to the target by 6%. Lasts for 12 sec. Stacks up to 2 times.", "Your Shadow Bolt and Haunt spells apply the Shadow Embrace effect, increasing all shadow periodic damage dealt to the target by you by 3%, and reduces all periodic healing done to the target by 9%. Lasts for 12 sec. Stacks up to 2 times.", "Your Shadow Bolt and Haunt spells apply the Shadow Embrace effect, increasing all shadow periodic damage dealt to the target by you by 4%, and reduces all periodic healing done to the target by 12%. Lasts for 12 sec. Stacks up to 2 times.", "Your Shadow Bolt and Haunt spells apply the Shadow Embrace effect, increasing all shadow periodic damage dealt to the target by you by 5%, and reduces all periodic healing done to the target by 15%. Lasts for 12 sec. Stacks up to 2 times." ]; i++; // Name: Siphon Life rank[i] = [ "When you deal damage with your Corruption spell, you are instantly healed for 40% of the damage done. In addition, the damage done by your Corruption, Seed of Corruption and Unstable Affliction damage over time effects is increased by 5%." ]; i++; // Name: Curse of Exhaustion rank[i] = [ "Reduces the target\'s movement speed by 30% for 12 sec.  Only one Curse per Warlock can be active on any one target." ]; i++; // Name: Improved Felhunter rank[i] = [ "Your Felhunter regains 4% of is maximum mana each time it hits with its Shadow Bite ability, and increases the effect of your Felhunter\'s Fel Intelligence by 5%.", "Your Felhunter regains 8% of is maximum mana each time it hits with its Shadow Bite ability, and increases the effect of your Felhunter\'s Fel Intelligence by 10%." ]; i++; // Name: Shadow Mastery rank[i] = [ "Increases the damage dealt or life drained by your Shadow spells by 3%.", "Increases the damage dealt or life drained by your Shadow spells by 6%.", "Increases the damage dealt or life drained by your Shadow spells by 9%.", "Increases the damage dealt or life drained by your Shadow spells by 12%.", "Increases the damage dealt or life drained by your Shadow spells by 15%." ]; i++; // Name: Eradication rank[i] = [ "When you deal damage with Corruption, you have 6% chance to increase your spell casting speed by 6% for 10 sec.", "When you deal damage with Corruption, you have 6% chance to increase your spell casting speed by 12% for 10 sec.", "When you deal damage with Corruption, you have 6% chance to increase your spell casting speed by 20% for 10 sec." ]; i++; // Name: Contagion rank[i] = [ "Increases the damage of Curse of Agony, Corruption and Seed of Corruption by 1% and reduces the chance your helpful Affliction spells and damage over time effects will be dispelled by an additional 6%.", "Increases the damage of Curse of Agony, Corruption and Seed of Corruption by 2% and reduces the chance your helpful Affliction spells and damage over time effects  will be dispelled by an additional 12%.", "Increases the damage of Curse of Agony, Corruption and Seed of Corruption by 3% and reduces the chance your helpful Affliction spells and damage over time effects  will be dispelled by an additional 18%.", "Increases the damage of Curse of Agony, Corruption and Seed of Corruption by 4% and reduces the chance your helpful Affliction spells and damage over time effects  will be dispelled by an additional 24%.", "Increases the damage of Curse of Agony, Corruption and Seed of Corruption by 5% and reduces the chance your helpful Affliction spells and damage over time effects  will be dispelled by an additional 30%." ]; i++; // Name: Dark Pact rank[i] = [ "Drains 305 of your summoned demon\'s Mana, returning 100% to you." ]; i++; // Name: Improved Howl of Terror rank[i] = [ "Reduces the casting time of your Howl of Terror spell by 0.8 sec.", "Reduces the casting time of your Howl of Terror spell by 1.5 sec." ]; i++; // Name: Malediction rank[i] = [ "Increases your spell damage by 1%, and increases the periodic critical strike chance of your Corruption and Unstable Affliction spells by 3%.", "Increases your spell damage by 2%, and increases the periodic critical strike chance of your Corruption and Unstable Affliction spells by 6%.", "Increases your spell damage by 3%, and increases the periodic critical strike chance of your Corruption and Unstable Affliction spells by 9%." ]; i++; // Name: Death's Embrace rank[i] = [ "Increases the amount drained by your Drain Life by 10% while your health is at or below 20% health, and increases the damage done by your Shadow spells by 4% when your target is at or below 35% health.", "Increases the amount drained by your Drain Life by 20% while your health is at or below 20% health, and increases the damage done by your Shadow spells by 8% when your target is at or below 35% health.", "Increases the amount drained by your Drain Life by 30% while your health is at or below 20% health, and increases the damage done by your Shadow spells by 12% when your target is at or below 35% health." ]; i++; // Name: Unstable Affliction rank[i] = [ "Shadow energy slowly destroys the target, causing 550 damage over 15 sec.  In addition, if the Unstable Affliction is dispelled it will cause 990 damage to the dispeller and silence them for 5 sec." ]; i++; // Name: Pandemic rank[i] = [ "Grants the periodic damage from your Corruption and Unstable Affliction spells the ability to critically hit for 100% increased damage, and increases the critical strike damage bonus of your Haunt spell by 100%." ]; i++; // Name: Everlasting Affliction rank[i] = [ "Your Corruption and Unstable Affliction spells gain an additional 1% of your bonus spell damage, and your Drain Life and Haunt spells have a 20% chance to reset the duration of your Corruption spell on the target.", "Your Corruption and Unstable Affliction spells gain an additional 2% of your bonus spell damage, and your Drain Life and Haunt spells have a 40% chance to reset the duration of your Corruption spell on the target.", "Your Corruption and Unstable Affliction spells gain an additional 3% of your bonus spell damage, and your Drain Life and Haunt spells have a 60% chance to reset the duration of your Corruption spell on the target.", "Your Corruption and Unstable Affliction spells gain an additional 4% of your bonus spell damage, and your Drain Life and Haunt spells have a 80% chance to reset the duration of your Corruption spell on the target.", "Your Corruption and Unstable Affliction spells gain an additional 5% of your bonus spell damage, and your Drain Life and Haunt spells have a 100% chance to reset the duration of your Corruption spell on the target." ]; i++; // Name: Haunt rank[i] = [ "You send a ghostly soul into the target, dealing 405 to 473 Shadow damage and increasing all damage done by your Shadow damage-over-time effects on the target by 20% for 12 sec. When the Haunt spell ends or is dispelled, the soul returns to you, healing you for 100% of the damage it did to the target." ]; i++; // Name: Improved Healthstone rank[i] = [ "Increases the amount of Health restored by your Healthstone by 10%.", "Increases the amount of Health restored by your Healthstone by 20%." ]; i++; // Name: Improved Imp rank[i] = [ "Increases the effect of your Imp\'s Firebolt, Fire Shield, and Blood Pact spells by 10%.", "Increases the effect of your Imp\'s Firebolt, Fire Shield, and Blood Pact spells by 20%.", "Increases the effect of your Imp\'s Firebolt, Fire Shield, and Blood Pact spells by 30%." ]; i++; // Name: Demonic Embrace rank[i] = [ "Increases your total Stamina by 4%.", "Increases your total Stamina by 7%.", "Increases your total Stamina by 10%." ]; i++; // Name: Fel Synergy rank[i] = [ "You have a 50% chance to heal your pet for 15% of the amount of spell damage done by you.", "You have a 100% chance to heal your pet for 15% of the amount of spell damage done by you." ]; i++; // Name: Improved Health Funnel rank[i] = [ "Increases the amount of Health transferred by your Health Funnel spell by 10% and reduces the initial health cost by 10%. In addition, your summoned Demon takes 15% less damage while under the effect of your Health Funnel.", "Increases the amount of Health transferred by your Health Funnel spell by 20% and reduces the initial health cost by 20%. In addition, your summoned Demon takes 30% less damage while under the effect of your Health Funnel." ]; i++; // Name: Demonic Brutality rank[i] = [ "Increases the effectiveness of your Voidwalker\'s Torment, Consume Shadows, Sacrifice and Suffering spells by 10%, and increases the attack power bonus on your Felguard\'s Demonic Frenzy effect by 1%.", "Increases the effectiveness of your Voidwalker\'s Torment, Consume Shadows, Sacrifice and Suffering spells by 20%, and increases the attack power bonus on your Felguard\'s Demonic Frenzy effect by 2%.", "Increases the effectiveness of your Voidwalker\'s Torment, Consume Shadows, Sacrifice and Suffering spells by 30%, and increases the attack power bonus on your Felguard\'s Demonic Frenzy effect by 3%." ]; i++; // Name: Fel Vitality rank[i] = [ "Increases the Stamina and Intellect of your Imp, Voidwalker, Succubus, Felhunter and Felguard by 5% and increases your maximum health and mana by 1%.", "Increases the Stamina and Intellect of your Imp, Voidwalker, Succubus, Felhunter and Felguard by 10% and increases your maximum health and mana by 2%.", "Increases the Stamina and Intellect of your Imp, Voidwalker, Succubus, Felhunter and Felguard by 15% and increases your maximum health and mana by 3%." ]; i++; // Name: Improved Succubus rank[i] = [ "Reduces the cast time of your Succubus\' Seduction by 22%, and increases the duration of your Succubus\' Seduction and Lesser Invisibility spells by 10%.", "Reduces the cast time of your Succubus\' Seduction by 44%, and increases the duration of your Succubus\' Seduction and Lesser Invisibility spells by 20%.", "Reduces the cast time of your Succubus\' Seduction by 66%, and increases the duration of your Succubus\' Seduction and Lesser Invisibility spells by 30%." ]; i++; // Name: Soul Link rank[i] = [ "When active, 20% of all damage taken by the caster is taken by your Imp, Voidwalker, Succubus, Felhunter, Felguard, or enslaved demon instead.  That damage cannot be prevented. Lasts as long as the demon is active and controlled." ]; i++; // Name: Fel Domination rank[i] = [ "Your next Imp, Voidwalker, Succubus, Felhunter or Felguard Summon spell has its casting time reduced by 5.5 sec and its Mana cost reduced by 50%." ]; i++; // Name: Demonic Aegis rank[i] = [ "Increases the effectiveness of your Demon Armor and Fel Armor spells by 10%.", "Increases the effectiveness of your Demon Armor and Fel Armor spells by 20%.", "Increases the effectiveness of your Demon Armor and Fel Armor spells by 30%." ]; i++; // Name: Unholy Power rank[i] = [ "Increases the damage done by your Voidwalker, Succubus, Felhunter and Felguard\'s melee attacks and your Imp\'s Firebolt by 4%.", "Increases the damage done by your Voidwalker, Succubus, Felhunter and Felguard\'s melee attacks and your Imp\'s Firebolt by 8%.", "Increases the damage done by your Voidwalker, Succubus, Felhunter and Felguard\'s melee attacks and your Imp\'s Firebolt by 12%.", "Increases the damage done by your Voidwalker, Succubus, Felhunter and Felguard\'s melee attacks and your Imp\'s Firebolt by 16%.", "Increases the damage done by your Voidwalker, Succubus, Felhunter and Felguard\'s melee attacks and your Imp\'s Firebolt by 20%." ]; i++; // Name: Master Summoner rank[i] = [ "Reduces the casting time of your Imp, Voidwalker, Succubus, Felhunter and Fel Guard Summoning spells by 2 sec and the Mana cost by 20%.", "Reduces the casting time of your Imp, Voidwalker, Succubus, Felhunter and Fel Guard Summoning spells by 4 sec and the Mana cost by 40%." ]; i++; // Name: Mana Feed rank[i] = [ "When you gain mana from Drain Mana or Life Tap spells, your summoned demon gains 100% of the mana you gain." ]; i++; // Name: Master Conjuror rank[i] = [ "Increases the combat ratings gained from your conjured Firestone and Spellstone by 150%.", "Increases the combat ratings gained from your conjured Firestone and Spellstone by 300%." ]; i++; // Name: Master Demonologist rank[i] = [ "Grants both the Warlock and the summoned demon an effect as long as that demon is active.

Imp - Increases your Fire damage by 1%, and increases the critical effect chance of your Fire spells by 1%.

Voidwalker - Reduces Physical damage taken by 2%.

Succubus - Increases your Shadow damage by 1%, and increases the critical effect chance of your Shadow spells by 1%.

Felhunter - Reduces all spell damage taken by 2%.

Felguard - Increases all damage done by 1%, and reduces all damage taken by 1%.", "Grants both the Warlock and the summoned demon an effect as long as that demon is active.

Imp - Increases your Fire damage by 2%, and increases the critical effect chance of your Fire spells by 2%.

Voidwalker - Reduces Physical damage taken by 4%.

Succubus - Increases your Shadow damage by 2%, and increases the critical effect chance of your Shadow spells by 2%.

Felhunter - Reduces all spell damage taken by 4%.

Felguard - Increases all damage done by 2%, and reduces all damage taken by 2%.", "Grants both the Warlock and the summoned demon an effect as long as that demon is active.

Imp - Increases your Fire damage by 3%, and increases the critical effect chance of your Fire spells by 3%.

Voidwalker - Reduces Physical damage taken by 6%.

Succubus - Increases your Shadow damage by 3%, and increases the critical effect chance of your Shadow spells by 3%.

Felhunter - Reduces all spell damage taken by 6%.

Felguard - Increases all damage done by 3%, and reduces all damage taken by 3%.", "Grants both the Warlock and the summoned demon an effect as long as that demon is active.

Imp - Increases your Fire damage by 4%, and increases the critical effect chance of your Fire spells by 4%.

Voidwalker - Reduces Physical damage taken by 8%.

Succubus - Increases your Shadow damage by 4%, and increases the critical effect chance of your Shadow spells by 4%.

Felhunter - Reduces all spell damage taken by 8%.

Felguard - Increases all damage done by 4%, and reduces all damage taken by 4%.", "Grants both the Warlock and the summoned demon an effect as long as that demon is active.

Imp - Increases your Fire damage by 5%, and increases the critical effect chance of your Fire spells by 5%.

Voidwalker - Reduces Physical damage taken by 10%.

Succubus - Increases your Shadow damage by 5%, and increases the critical effect chance of your Shadow spells by 5%.

Felhunter - Reduces all spell damage taken by 10%.

Felguard - Increases all damage done by 5%, and reduces all damage taken by 5%." ]; i++; // Name: Molten Core rank[i] = [ "Your Shadow spells and damage over time effects have a 5% chance to increase the damage of your Fire spells by 10% for 12 sec.", "Your Shadow spells and damage over time effects have a 10% chance to increase the damage of your Fire spells by 10% for 12 sec.", "Your Shadow spells and damage over time effects have a 15% chance to increase the damage of your Fire spells by 10% for 12 sec." ]; i++; // Name: Demonic Resilience rank[i] = [ "Reduces the chance you\'ll be critically hit by melee and spells by 1% and reduces all damage your summoned demon takes by 5%.", "Reduces the chance you\'ll be critically hit by melee and spells by 2% and reduces all damage your summoned demon takes by 10%.", "Reduces the chance you\'ll be critically hit by melee and spells by 3% and reduces all damage your summoned demon takes by 15%." ]; i++; // Name: Demonic Empowerment rank[i] = [ "Grants the Warlock\'s summoned demon Empowerment.

Imp - Increases the Imp\'s spell critical strike chance by 20% for 30 sec.

Voidwalker - Increases the Voidwalker\'s health by 20%, and its threat generated from spells and attacks by 20% for 20 sec.

Succubus - Instantly vanishes, causing the Succubus to go into an improved Invisibility state. The vanish effect removes all stuns, snares and movement impairing effects from the Succubus.

Felhunter - Dispels all magical effects from the Felhunter.

Felguard - Increases the Felguard\'s attack speed by 20% and breaks all stun, snare and movement impairing effects and makes your Felguard immune to them for 15 sec." ]; i++; // Name: Demonic Knowledge rank[i] = [ "Increases your spell damage by an amount equal to 4% of the total of your active demon\'s Stamina plus Intellect.", "Increases your spell damage by an amount equal to 8% of the total of your active demon\'s Stamina plus Intellect.", "Increases your spell damage by an amount equal to 12% of the total of your active demon\'s Stamina plus Intellect." ]; i++; // Name: Demonic Tactics rank[i] = [ "Increases melee and spell critical strike chance for you and your summoned demon by 2%.", "Increases melee and spell critical strike chance for you and your summoned demon by 4%.", "Increases melee and spell critical strike chance for you and your summoned demon by 6%.", "Increases melee and spell critical strike chance for you and your summoned demon by 8%.", "Increases melee and spell critical strike chance for you and your summoned demon by 10%." ]; i++; // Name: Decimation rank[i] = [ "When you Shadowbolt or Incinerate a target that is at or below 35% health, the cast time of your next Soulfire is reduced by 30%. Soulfires cast under the effect of Decimation cost no shard. Lasts 10 sec.", "When you Shadowbolt or Incinerate a target that is at or below 35% health, the cast time of your next Soulfire is reduced by 60%. Soulfires cast under the effect of Decimation cost no shard. Lasts 10 sec." ]; i++; // Name: Improved Demonic Tactics rank[i] = [ "Increases your summoned demon\'s critical strike chance equal to 10% of your critical strike chance.", "Increases your summoned demon\'s critical strike chance equal to 20% of your critical strike chance.", "Increases your summoned demon\'s critical strike chance equal to 30% of your critical strike chance." ]; i++; // Name: Summon Felguard rank[i] = [ "Summons a Felguard under the command of the Warlock." ]; i++; // Name: Nemesis rank[i] = [ "Reduces the cooldown of your Demonic Empowerment, Metamorphosis, and Fel Domination spells by 10%.", "Reduces the cooldown of your Demonic Empowerment, Metamorphosis, and Fel Domination spells by 20%.", "Reduces the cooldown of your Demonic Empowerment, Metamorphosis, and Fel Domination spells by 30%." ]; i++; // Name: Demonic Pact rank[i] = [ "Your pet\'s criticals apply the Demonic Pact effect to your party or raid members. Demonic Pact increases spell power by 2% of your Spell Damage for 12 sec.  Does not work on Enslaved demons.", "Your pet\'s criticals apply the Demonic Pact effect to your party or raid members. Demonic Pact increases spell power by 4% of your Spell Damage for 12 sec.  Does not work on Enslaved demons.", "Your pet\'s criticals apply the Demonic Pact effect to your party or raid members. Demonic Pact increases spell power by 6% of your Spell Damage for 12 sec.  Does not work on Enslaved demons.", "Your pet\'s criticals apply the Demonic Pact effect to your party or raid members. Demonic Pact increases spell power by 8% of your Spell Damage for 12 sec.  Does not work on Enslaved demons.", "Your pet\'s criticals apply the Demonic Pact effect to your party or raid members. Demonic Pact increases spell power by 10% of your Spell Damage for 12 sec.  Does not work on Enslaved demons." ]; i++; // Name: Metamorphosis rank[i] = [ "You transform into a Demon for 30 sec.  This form increases your armor by 600%, damage by 20%, reduces the chance you\'ll be critically hit by melee attacks by 6% and reduces the duration of stun and snare effects by 50%.  You gain some unique demon abilities in addition to your normal abilities. 3 minute cooldown." ]; i++; // Name: Improved Shadow Bolt rank[i] = [ "Increases the damage done by your Shadow Bolt spell by 1%, and your Shadow Bolt has a 20% chance to cause your target to be vulnerable to spell damage, increasing spell critical strike chance against that target by 5%. Effect lasts 30 sec.", "Increases the damage done by your Shadow Bolt spell by 2%, and your Shadow Bolt has a 40% chance to cause your target to be vulnerable to spell damage, increasing spell critical strike chance against that target by 5%. Effect lasts 30 sec.", "Increases the damage done by your Shadow Bolt spell by 3%, and your Shadow Bolt has a 60% chance to cause your target to be vulnerable to spell damage, increasing spell critical strike chance against that target by 5%. Effect lasts 30 sec.", "Increases the damage done by your Shadow Bolt spell by 4%, and your Shadow Bolt has a 80% chance to cause your target to be vulnerable to spell damage, increasing spell critical strike chance against that target by 5%. Effect lasts 30 sec.", "Increases the damage done by your Shadow Bolt spell by 5%, and your Shadow Bolt has a 100% chance to cause your target to be vulnerable to spell damage, increasing spell critical strike chance against that target by 5%. Effect lasts 30 sec." ]; i++; // Name: Bane rank[i] = [ "Reduces the casting time of your Shadow Bolt. Chaos Bolt and Immolate spells by 0.1 sec and your Soul Fire spell by 0.4 sec.", "Reduces the casting time of your Shadow Bolt. Chaos Bolt and Immolate spells by 0.2 sec and your Soul Fire spell by 0.8 sec.", "Reduces the casting time of your Shadow Bolt. Chaos Bolt and Immolate spells by 0.3 sec and your Soul Fire spell by 1.2 sec.", "Reduces the casting time of your Shadow Bolt. Chaos Bolt and Immolate spells by 0.4 sec and your Soul Fire spell by 1.6 sec.", "Reduces the casting time of your Shadow Bolt. Chaos Bolt and Immolate spells by 0.5 sec and your Soul Fire spell by 2 sec." ]; i++; // Name: Aftermath rank[i] = [ "Increases the periodic damage done by your Immolate by 3%, and your Conflagrate has a 50% chance to daze the target for 5 sec.", "Increases the periodic damage done by your Immolate by 6%, and your Conflagrate has a 100% chance to daze the target for 5 sec." ]; i++; // Name: Molten Skin rank[i] = [ "Reduces all damage taken by 2%.", "Reduces all damage taken by 4%.", "Reduces all damage taken by 6%." ]; i++; // Name: Cataclysm rank[i] = [ "Reduces the Mana cost of your Destruction spells by 4%.", "Reduces the Mana cost of your Destruction spells by 7%.", "Reduces the Mana cost of your Destruction spells by 10%." ]; i++; // Name: Demonic Power rank[i] = [ "Reduces the cooldown of your Succubus\' Lash of Pain spell by 3 sec. and reduces the casting time of your Imp\'s Firebolt spell by 0.25 sec.", "Reduces the cooldown of your Succubus\' Lash of Pain spell by 6 sec. and reduces the casting time of your Imp\'s Firebolt spell by 0.5 sec." ]; i++; // Name: Shadowburn rank[i] = [ "Instantly blasts the target for 87 to 99 Shadow damage.  If the target dies within 5 sec of Shadowburn, and yields experience or honor, the caster gains a Soul Shard." ]; i++; // Name: Ruin rank[i] = [ "Increases the critical strike damage bonus of your Destruction spells by 20%.", "Increases the critical strike damage bonus of your Destruction spells by 40%.", "Increases the critical strike damage bonus of your Destruction spells by 60%.", "Increases the critical strike damage bonus of your Destruction spells by 80%.", "Increases the critical strike damage bonus of your Destruction spells by 100%." ]; i++; // Name: Intensity rank[i] = [ "Reduces the pushback suffered from damaging attacks while casting or channeling any Destruction spell by 35%.", "Reduces the pushback suffered from damaging attacks  while casting or channeling any Destruction spell by 70%." ]; i++; // Name: Destructive Reach rank[i] = [ "Increases the range of your Destruction spells by 10% and reduces threat caused by Destruction spells by 5%.", "Increases the range of your Destruction spells by 20% and reduces threat caused by Destruction spells by 10%." ]; i++; // Name: Improved Searing Pain rank[i] = [ "Increases the critical strike chance of your Searing Pain spell by 4%.", "Increases the critical strike chance of your Searing Pain spell by 7%.", "Increases the critical strike chance of your Searing Pain spell by 10%." ]; i++; // Name: Backlash rank[i] = [ "Increases your critical strike chance with spells by an additional 1% and gives you a 8% chance when hit by a physical attack to reduce the cast time of your next Shadow Bolt or Incinerate spell by 100%.  This effect lasts 8 sec and will not occur more than once every 8 seconds.", "Increases your critical strike chance with spells by an additional 2% and gives you a 16% chance when hit by a physical attack to reduce the cast time of your next Shadow Bolt or Incinerate spell by 100%.  This effect lasts 8 sec and will not occur more than once every 8 seconds.", "Increases your critical strike chance with spells by an additional 3% and gives you a 25% chance when hit by a physical attack to reduce the cast time of your next Shadow Bolt or Incinerate spell by 100%.  This effect lasts 8 sec and will not occur more than once every 8 seconds." ]; i++; // Name: Improved Immolate rank[i] = [ "Increases the damage done by your Immolate spell by 10%.", "Increases the damage done by your Immolate spell by 20%.", "Increases the damage done by your Immolate spell by 30%." ]; i++; // Name: Devastation rank[i] = [ "Increases the critical strike chance of your Destruction spells by 5%." ]; i++; // Name: Nether Protection rank[i] = [ "After being hit with a spell, you have a 10% chance to gain Nether Protection, reducing all damage by that spell school by 30% for 8 sec.", "After being hit with a spell, you have a 20% chance to gain Nether Protection, reducing all damage by that spell school by 30% for 8 sec.", "After being hit with a spell, you have a 30% chance to gain Nether Protection, reducing all damage by that spell school by 30% for 8 sec." ]; i++; // Name: Emberstorm rank[i] = [ "Increases the damage done by your Fire spells by 3% and reduces the cast time of your Incinerate spell by 0.05 sec.", "Increases the damage done by your Fire spells by 6% and reduces the cast time of your Incinerate spell by 0.1 sec.", "Increases the damage done by your Fire spells by 9% and reduces the cast time of your Incinerate spell by 0.15 sec.", "Increases the damage done by your Fire spells by 12% and reduces the cast time of your Incinerate spell by 0.2 sec.", "Increases the damage done by your Fire spells by 15% and reduces the cast time of your Incinerate spell by 0.25 sec." ]; i++; // Name: Conflagrate rank[i] = [ "Consumes an Immolate or Shadowflame effect on the enemy target to instantly deal damage equal to 12 sec of your Immolate or 8 sec of your Shadowflame." ]; i++; // Name: Soul Leech rank[i] = [ "Gives your Shadow Bolt, Shadowburn, Chaos Bolt, Soul Fire, Incinerate, Searing Pain and Conflagrate spells a 10% chance to return health equal to 20% of the damage caused.", "Gives your Shadow Bolt, Shadowburn, Chaos Bolt, Soul Fire, Incinerate, Searing Pain and Conflagrate spells a 20% chance to return health equal to 20% of the damage caused.", "Gives your Shadow Bolt, Shadowburn, Chaos Bolt, Soul Fire, Incinerate, Searing Pain and Conflagrate spells a 30% chance to return health equal to 20% of the damage caused." ]; i++; // Name: Pyroclasm rank[i] = [ "When you critically strike with Searing Pain or Conflagrate, your Fire and Shadow spell damage is increased by 2% for 10 sec.", "When you critically strike with Searing Pain or Conflagrate, your Fire and Shadow spell damage is increased by 4% for 10 sec.", "When you critically strike with Searing Pain or Conflagrate, your Fire and Shadow spell damage is increased by 6% for 10 sec." ]; i++; // Name: Shadow and Flame rank[i] = [ "Your Shadow Bolt, Shadowburn, Chaos Bolt and Incinerate spells gain an additional 4% of your bonus spell damage effects.", "Your Shadow Bolt, Shadowburn, Chaos Bolt and Incinerate spells gain an additional 8% of your bonus spell damage effects.", "Your Shadow Bolt, Shadowburn, Chaos Bolt and Incinerate spells gain an additional 12% of your bonus spell damage effects.", "Your Shadow Bolt, Shadowburn, Chaos Bolt and Incinerate spells gain an additional 16% of your bonus spell damage effects.", "Your Shadow Bolt, Shadowburn, Chaos Bolt and Incinerate spells gain an additional 20% of your bonus spell damage effects." ]; i++; // Name: Improved Soul Leech rank[i] = [ "Your Soul Leech effect also restores mana to you and your summoned demon equal to 1% of maximum mana, and has a 50% chance to grant up to 10 party or raid members mana regeneration equal to 1% of maximum mana per 5 sec. Lasts for 15 sec.", "Your Soul Leech effect also restores mana to you and your summoned demon equal to 2% of maximum mana, and has a 100% chance to grant up to 10 party or raid members mana regeneration equal to 1% of maximum mana per 5 sec. Lasts for 15 sec." ]; i++; // Name: Backdraft rank[i] = [ "When you cast Conflagrate, the cast time and global cooldown of your next three Destruction spells is reduced by 10%. Lasts 15 sec.", "When you cast Conflagrate, the cast time and global cooldown of your next three Destruction spells is reduced by 20%. Lasts 15 sec.", "When you cast Conflagrate, the cast time and global cooldown of your next three Destruction spells is reduced by 30%. Lasts 15 sec." ]; i++; // Name: Shadowfury rank[i] = [ "Shadowfury is unleashed, causing 343 to 407 Shadow damage and stunning all enemies within 8 yds for 3 sec." ]; i++; // Name: Empowered Imp rank[i] = [ "Increases the damage done by your Imp by 5%, and all critical hits done by your Imp have a 33% chance to increase your spell critical hit chance for your next spell by 100%. This effect lasts 8 sec.", "Increases the damage done by your Imp by 10%, and all critical hits done by your Imp have a 66% chance to increase your spell critical hit chance for your next spell by 100%. This effect lasts 8 sec.", "Increases the damage done by your Imp by 15%, and all critical hits done by your Imp have a 100% chance to increase your spell critical hit chance for your next spell by 100%. This effect lasts 8 sec." ]; i++; // Name: Fire and Brimstone rank[i] = [ "Increases the damage done by your Incinerate and Chaos Bolt spells to targets afflicted by your Immolate by 2%, and the critical strike chance of your Conflagrate spell is increased by 5%.", "Increases the damage done by your Incinerate and Chaos Bolt spells to targets afflicted by your Immolate by 4%, and the critical strike chance of your Conflagrate spell is increased by 10%.", "Increases the damage done by your Incinerate and Chaos Bolt spells to targets afflicted by your Immolate by 6%, and the critical strike chance of your Conflagrate spell is increased by 15%.", "Increases the damage done by your Incinerate and Chaos Bolt spells to targets afflicted by your Immolate by 8%, and the critical strike chance of your Conflagrate spell is increased by 20%.", "Increases the damage done by your Incinerate and Chaos Bolt spells to targets afflicted by your Immolate by 10%, and the critical strike chance of your Conflagrate spell is increased by 25%." ]; i++; // Name: Chaos Bolt rank[i] = [ "Sends a bolt of chaotic fire at the enemy, dealing 837 to 1061 Fire damage. Chaos Bolt cannot be resisted, and pierces through all absorption effects." ]; i++;