/**
 * Twitter - http://twitter.com
 * Copyright (C) 2010 Twitter
 * Author: Dustin Diaz (dustin@twitter.com)
 *
 * V 2.2.5 Twitter search/profile/faves/list widget
 * http://twitter.com/widgets
 * For full documented source see http://twitter.com/javascripts/widgets/widget.js
 * Hosting and modifications of the original source IS allowed.
 *
 * Example usage:
   <script src="path/to/widget.js"></script>
   <script>
     new TWTR.Widget({
       type: 'search',
       search: "your search query", // includes all advanced search queries.
       width: 250,
       height: 350,
       interval: 6000,
       subject: "What's being said about...",
       title: "San Francisco",
       rpp: 30,
       footer: "Follow me",
       theme: {
         shell: {
           background: '#1deb25',
           color: '#ffffff'
         },
         tweets: {
           background: 'silver',
           color: 'blue',
           links: 'black'
         }
       },

       features: {
         avatars: true, // defaults to false for profile widget, but true for search & faves widget
         hashtags: true,
         timestamp: true,
         fullscreen: false, // ignores width and height and just goes full screen
         scrollbar: false,
         live: true,
         loop: true,
         behavior: 'default',
         dateformat: 'absolute', // defaults to relative (eg: 3 minutes ago)
         toptweets: true // only for search widget
       }
     }).render().start();
   </script>
 *
 */

/**
  * @namespace TWTR public namespace for Twitter widget
  */
TWTR = window.TWTR || {};

/**
  * add core functionality to JS
  * Sugar Arrays http://www.dustindiaz.com/basement/sugar-arrays.html
  */
if (!Array.forEach) {

  Array.prototype.filter = function(fn, thisObj) {
    var scope = thisObj || window;
    var a = [];
    for (var i=0, j=this.length; i < j; ++i) {
      if (!fn.call(scope, this[i], i, this)) {
        continue;
      }
      a.push(this[i]);
    }
    return a;
  };

  // sorta like inArray if used clever-like
  Array.prototype.indexOf = function(el, start) {
    var start = start || 0;

    for (var i=0; i < this.length; ++i) {
      if (this[i] === el) {
        return i;
      }
    }

    return -1;
  };
}

/* first, a few dependencies */
(function() {
  if (TWTR && TWTR.Widget) {
    // this is most likely to happen when people try to embed multiple
    // widgets on the same page and include this script again
    return;
  }

  /**
    * Basic Array methods
    */
  function each(a, fn, opt_scope) {
    for (var i=0, j=a.length; i < j; ++i) {
      fn.call(opt_scope || window, a[i], i, a);
    }
  }

  /**
    * Generic Animation utility to tween dom elements
    *
    * Copyright (c) 2009 Dustin Diaz & Twitter (http://www.dustindiaz.com)
    * MIT License
    */

  /**
    * @constructor Animate
    * @param {HTMLElement} el the element we want to animate
    * @param {String} prop the CSS property we will be animating
    * @param {Object} opts a configuration object
    * object properties include
    * from {Int}
    * to {Int}
    * time {Int} time in milliseconds
    * callback {Function}
    */
  function Animate(el, prop, opts) {
    this.el = el;
    this.prop = prop;
    this.from = opts.from;
    this.to = opts.to;
    this.time = opts.time;
    this.callback = opts.callback;
    this.animDiff = this.to - this.from;
  };

  /**
    * @static
    * @boolean
    * allows us to check if native CSS transitions are possible
    */
  Animate.canTransition = function() {
    var el = document.createElement('twitter');
    el.style.cssText = '-webkit-transition: all .5s linear;';
    return !!el.style.webkitTransitionProperty;
  }();

  /**
    * @private
    * @param {String} val the CSS value we will set on the property
    */
  Animate.prototype._setStyle = function(val) {
    switch (this.prop) {
      case 'opacity':
        this.el.style[this.prop] = val;
        this.el.style.filter = 'alpha(opacity=' + val * 100 + ')';
        break;

      default:
        this.el.style[this.prop] = val + 'px';
        break;
    };
  };

  /**
    * @private
    * this is the tweening function
    */
  Animate.prototype._animate = function() {
    var that = this;
    this.now = new Date();
    this.diff = this.now - this.startTime;

    if (this.diff > this.time) {
      this._setStyle(this.to);

      if (this.callback) {
        this.callback.call(this);
      }
      clearInterval(this.timer);
      return;
    }

    this.percentage = (Math.floor((this.diff / this.time) * 100) / 100);
    this.val = (this.animDiff * this.percentage) + this.from;
    this._setStyle(this.val);
  };

  /**
    * @public
    * begins the animation
    */
  Animate.prototype.start = function() {
    var that = this;
    this.startTime = new Date();

    this.timer = setInterval(function() {
      that._animate.call(that);
    }, 15);
  };


  /**
    * @constructor
    * Widget Base for new instances of the Twitter search widget
    * @param {Object} opts the configuration options for the widget
    */
  TWTR.Widget = function(opts) {
    this.init(opts);
  };

  (function() {


    // Internal Namespace.
    var twttr = {};

    var isHttps = location.protocol.match(/https/);
    var httpsImageRegex = /^.+\/profile_images/;
    var httpsImageReplace = 'https://s3.amazonaws.com/twitter_production/profile_images';

    var matchUrlScheme = function(url) {
      return isHttps ? url.replace(httpsImageRegex, httpsImageReplace) : url;
    }

    // cache object for searching duplicates
    var reClassNameCache = {};

    // reusable regex for searching classnames
    var getClassRegEx = function(c) {

      // check to see if regular expression already exists
      var re = reClassNameCache[c];

      if (!re) {
        re = new RegExp('(?:^|\\s+)' + c + '(?:\\s+|$)');
        reClassNameCache[c] = re;
      }

      return re;
    };

    var getByClass = function(c, tag, root, apply) {
      var tag = tag || '*';
      var root = root || document;

      var nodes = [],
          elements = root.getElementsByTagName(tag),
          re = getClassRegEx(c);

      for (var i = 0, len = elements.length; i < len; ++i) {
        if (re.test(elements[i].className)) {
          nodes[nodes.length] = elements[i];

          if (apply) {
            apply.call(elements[i], elements[i]);
          }

        }
      }

      return nodes;
    };

    var browser = function() {
      var ua = navigator.userAgent;
      return {
        ie: ua.match(/MSIE\s([^;]*)/)
      };
    }();

    var byId = function(id) {
      if (typeof id == 'string') {
        return document.getElementById(id);
      }
      return id;
    };

    var trim = function(str) {
      return str.replace(/^\s+|\s+$/g, '')
    };

    var getViewportHeight = function() {
      var height = self.innerHeight; // Safari, Opera
      var mode = document.compatMode;
      if ((mode || browser.ie)) { // IE, Gecko
        height = (mode == 'CSS1Compat') ?
          document.documentElement.clientHeight : // Standards
          document.body.clientHeight; // Quirks
      }
      return height;
    };

    var getTarget = function(e, resolveTextNode) {
      var target = e.target || e.srcElement;
      return resolveTextNode(target);
    };

    var resolveTextNode = function(el) {
      try {
        if (el && 3 == el.nodeType) {
          return el.parentNode;
        } else {
          return el;
        }
      } catch (ex) { }
    };

    var getRelatedTarget = function(e) {
      var target = e.relatedTarget;
      if (!target) {
        if (e.type == 'mouseout') {
          target = e.toElement;
        }
        else if (e.type == 'mouseover') {
          target = e.fromElement;
        }
      }
      return resolveTextNode(target);
    };

    var insertAfter = function(el, reference) {
      reference.parentNode.insertBefore(el, reference.nextSibling);
    };

    var removeElement = function(el) {
      try {
        el.parentNode.removeChild(el);
      }
      catch (ex) { }
    };

    var getFirst = function(el) {
      return el.firstChild;
    };

    var withinElement = function(e) {
      var parent = getRelatedTarget(e);
      while (parent && parent != this) {
        try {
          parent = parent.parentNode;
        }
        catch(ex) {
          parent = this;
        }
      }
      if (parent != this) {
        return true;
      }
      return false;
    };

    var getStyle = function() {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        return function(el, property) {
          var value = null;
          var computed = document.defaultView.getComputedStyle(el, '');
          if (computed) {
            value = computed[property];
          }
          var ret = el.style[property] || value;
          return ret;
        };
      }
      else if (document.documentElement.currentStyle && browser.ie) { // IE method
        return function(el, property) {
          var value = el.currentStyle ? el.currentStyle[property] : null;
          return (el.style[property] || value);
        };
      }
    }();

    /**
      * classes object
      * - has - add - remove
      */
    var classes = {
      has: function(el, c) {
        return new RegExp("(^|\\s)" + c + "(\\s|$)").test(byId(el).className);
      },

      add: function(el, c) {
        if (!this.has(el, c)) {
          byId(el).className = trim(byId(el).className) + ' ' + c;
        }
      },

      remove: function(el, c) {
        if (this.has(el, c)) {
          byId(el).className = byId(el).className.replace(new RegExp("(^|\\s)" + c + "(\\s|$)", "g"), "");
        }
      }
    };

    /**
      * basic x-browser event listener util
      * eg: events.add(element, 'click', fn);
      */
    var events = {
      add: function(el, type, fn) {
        if (el.addEventListener) {
          el.addEventListener(type, fn, false);
        }
        else {
          el.attachEvent('on' + type, function() {
            fn.call(el, window.event);
          });
        }
      },
      remove: function(el, type, fn) {
        if (el.removeEventListener) {
          el.removeEventListener(type, fn, false);
        }
        else {
          el.detachEvent('on' + type, fn);
        }
      }
    };

    var hex_rgb = function() {

      function HexToR(h) {
        return parseInt((h).substring(0,2),16);
      }
      function HexToG(h) {
        return parseInt((h).substring(2,4),16);
      }
      function HexToB(h) {
        return parseInt((h).substring(4,6),16);
      }

      return function(hex) {
        return [HexToR(hex), HexToG(hex), HexToB(hex)];
      };

    }();

    /**
      * core type detection on javascript objects
      */
    var is = {
      bool: function(b) {
        return typeof b === 'boolean';
      },

      def: function(o) {
        return !(typeof o === 'undefined');
      },

      number: function(n) {
        return typeof n === 'number' && isFinite(n);
      },
      string: function(s) {
        return typeof s === 'string';
      },

      fn: function(f) {
        return typeof f === 'function';
      },

      array: function(a) {
        if (a) {
          return is.number(a.length) && is.fn(a.splice);
        }
        return false;
      }
    };

    var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

    var absoluteTime = function(s) {
      var d = new Date(s);
      if (browser.ie) {
        d = Date.parse(s.replace(/( \+)/, ' UTC$1'));
      }
      var ampm = '';
      var hour = function() {
        var h = d.getHours();
        if (h > 0 && h < 13) {
          ampm = 'am';
          return h;
        }
        else if (h < 1) {
          ampm = 'am';
          return 12;
        }
        else {
          ampm = 'pm';
          return h - 12;
        }
      }();
      var minutes = d.getMinutes();
      var seconds = d.getSeconds();
      function getRest() {
        var today = new Date();
        if (today.getDate() != d.getDate() || today.getYear() != d.getYear() || today.getMonth() != d.getMonth()) {
          return ' - ' + months[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
        }
        else {
          return '';
        }
      }
      return hour + ':' + minutes + ampm + getRest();
    };

    /**
      * relative time calculator
      * @param {string} twitter date string returned from Twitter API
      * @return {string} relative time like "2 minutes ago"
      */
    var timeAgo = function(dateString) {
      var rightNow = new Date();
      var then = new Date(dateString);

      if (browser.ie) {
        // IE can't parse these crazy Ruby dates
        then = Date.parse(dateString.replace(/( \+)/, ' UTC$1'));
      }

      var diff = rightNow - then;

      var second = 1000,
          minute = second * 60,
          hour = minute * 60,
          day = hour * 24,
          week = day * 7;

      if (isNaN(diff) || diff < 0) {
        return ""; // return blank string if unknown
      }

      if (diff < second * 2) {
        // within 2 seconds
        return "right now";
      }

      if (diff < minute) {
        return Math.floor(diff / second) + " seconds ago";
      }

      if (diff < minute * 2) {
        return "about 1 minute ago";
      }

      if (diff < hour) {
        return Math.floor(diff / minute) + " minutes ago";
      }

      if (diff < hour * 2) {
        return "about 1 hour ago";
      }

      if (diff < day) {
        return  Math.floor(diff / hour) + " hours ago";
      }

      if (diff > day && diff < day * 2) {
        return "yesterday";
      }

      if (diff < day * 365) {
        return Math.floor(diff / day) + " days ago";
      }

      else {
        return "over a year ago";
      }

    };

    /**
      * The Twitalinkahashifyer!
      * http://www.dustindiaz.com/basement/ify.html
      * Eg:
      * ify.clean('your tweet text');
      */


    var ify = {
      link: function(tweet) {
        return tweet.replace(/\b(((https*\:\/\/)|www\.)[^\"\']+?)(([!?,.\)]+)?(\s|$))/g, function(link, m1, m2, m3, m4) {
          var http = m2.match(/w/) ? 'http://' : '';
          return '<a class="twtr-hyperlink" target="_blank" href="' + http + m1 + '">' + ((m1.length > 25) ? m1.substr(0, 24) + '...' : m1) + '</a>' + m4;
        });
      },

      at: function(tweet) {
        return tweet.replace(/\B[@＠]([a-zA-Z0-9_]{1,20})/g, function(m, username) {
          return '@<a target="_blank" class="twtr-atreply" href="http://twitter.com/intent/user?screen_name=' + username + '">' + username + '</a>';
        });
      },

      list: function(tweet) {
        return tweet.replace(/\B[@＠]([a-zA-Z0-9_]{1,20}\/\w+)/g, function(m, userlist) {
          return '@<a target="_blank" class="twtr-atreply" href="http://twitter.com/' + userlist + '">' + userlist + '</a>';
        });
      },

      hash: function(tweet) {
        return tweet.replace(/(^|\s+)#(\w+)/gi, function(m, before, hash) {
          return before + '<a target="_blank" class="twtr-hashtag" href="http://twitter.com/search?q=%23' + hash + '">#' + hash + '</a>';
        });
      },

      clean: function(tweet) {
        return this.hash(this.at(this.list(this.link(tweet))));
      }
    };


    /**
      * @constructor the classic twitter occasional job
      * @param {Function} job The job to execute upon each request
      * @param {Function} decay The deciding boolean method on whether to decay
      * @param {Int} interval The number in milliseconds to wait before executing
      */
    function Occasionally(job, decayFn, interval) {
      this.job = job;
      this.decayFn = decayFn;
      this.interval = interval;
      this.decayRate = 1;
      this.decayMultiplier = 1.25;
      this.maxDecayTime = 3 * 60 * 1000; // 3 minutes
    }

    Occasionally.prototype = {

      /**
        * @public
        * @return self
        * starts our occasional job
        */
      start: function() {
        this.stop().run();
        return this;
      },

      /**
        * @public
        * @return self
        * stops the occasional job
        */
      stop: function() {
        if (this.worker) {
          window.clearTimeout(this.worker);
        }
        return this;
      },

      /**
        * @private
        */
      run: function() {
        var that = this;
        this.job(function() {
          // running our decayer callback
          that.decayRate = that.decayFn() ? Math.max(1, that.decayRate / that.decayMultiplier)
                                          : that.decayRate * that.decayMultiplier;

          var expire = that.interval * that.decayRate;
          expire = (expire >= that.maxDecayTime) ? that.maxDecayTime : expire;
          expire = Math.floor(expire);

          that.worker = window.setTimeout(
            function () {
              that.run.call(that);
            },
            expire
          );

        });
      },

      /**
        * @public
        * @return self
        * stops occasional job and resets object
        */
      destroy: function() {
        this.stop();
        this.decayRate = 1;
        return this;
      }
    };

    /**
      * @Constructor runs a timer on an array passing back
      *              the next needle on each interval
      * @param haystack {Array}
      * @param time {Int} time in ms
      * @param loop {Bool} does this continue forever?
      * @param callback {Function} method that is passed back a needle for each interval
      */
    function IntervalJob(time, loop, callback) {
      this.time = time || 6000;
      this.loop = loop || false;
      this.repeated = 0;
      this.callback = callback;
      this.haystack = [];
    };

    IntervalJob.prototype = {

      set: function(haystack) {
        this.haystack = haystack;
      },

      add: function(needle) {
        this.haystack.unshift(needle);
      },

      /**
        * @public
        * @return self
        * begins the interval job
        */
      start: function() {
        if (this.timer) {
          return this;
        }
        this._job();
        var that = this;
        this.timer = setInterval(
          function() {
            that._job.call(that);
          }, this.time
        );

        return this;
      },

      /**
        * @public
        * @return self
        * stops the interval
        */
      stop: function() {
        if (this.timer) {
          window.clearInterval(this.timer);
          this.timer = null;
        }

        return this;
      },

      /**
        * @private
        */
      _next: function() {
        var old = this.haystack.shift();
        if (old && this.loop) {
          this.haystack.push(old);
        }
        return old || null;
      },

      /**
        * @private
        */
      _job: function() {
        var next = this._next();
        if (next) {
          this.callback(next);
        }

        return this;
      }

    };

    function Tweet(tweet) {
      function showPopular() {
        if (tweet.needle.metadata && tweet.needle.metadata.result_type && tweet.needle.metadata.result_type == 'popular') {
          return '<span class="twtr-popular">' + tweet.needle.metadata.recent_retweets + '+ recent retweets</span>';
        } else {
          return '';
        }
      }

      var html = '<div class="twtr-tweet-wrap"> \
        <div class="twtr-avatar"> \
          <div class="twtr-img"><a target="_blank" href="http://twitter.com/intent/user?screen_name=' + tweet.user + '"><img alt="' + tweet.user + ' profile" src="' + matchUrlScheme(tweet.avatar) + '"></a></div> \
        </div> \
        <div class="twtr-tweet-text"> \
          <p> \
            <a target="_blank" href="http://twitter.com/intent/user?screen_name=' + tweet.user + '" class="twtr-user">' + tweet.user + '</a> ' + tweet.tweet + ' \
            <em>\
            <a target="_blank" class="twtr-timestamp" time="' + tweet.timestamp + '" href="http://twitter.com/' + tweet.user + '/status/' + tweet.id + '">' + tweet.created_at + '</a> &middot;\
            <a target="_blank" class="twtr-reply" href="http://twitter.com/intent/tweet?in_reply_to=' + tweet.id + '">reply</a> &middot; \
            <a target="_blank" class="twtr-rt" href="http://twitter.com/intent/retweet?tweet_id=' + tweet.id + '">retweet</a> &middot; \
            <a target="_blank" class="twtr-fav" href="http://twitter.com/intent/favorite?tweet_id=' + tweet.id + '">favorite</a> \
            </em> ' + showPopular() + ' \
          </p> \
        </div> \
      </div>';

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

      div.id = 'tweet-id-' + ++Tweet._tweetCount;
      div.className = 'twtr-tweet';
      div.innerHTML = html;
      this.element = div;
    };

    // static count so all tweets (even on multiple inst widgets) will have unique ids
    Tweet._tweetCount = 0;

      twttr.loadStyleSheet = function(url, widgetEl) {
        if (!TWTR.Widget.loadingStyleSheet) {
          TWTR.Widget.loadingStyleSheet = true;
          var linkElement = document.createElement('link');
          linkElement.href = url;
          linkElement.rel = 'stylesheet';
          linkElement.type = 'text/css';
          document.getElementsByTagName('head')[0].appendChild(linkElement);
          var timer = setInterval(function() {
            var style = getStyle(widgetEl, 'position');
            if (style == 'relative') {
              clearInterval(timer);
              timer = null;
              TWTR.Widget.hasLoadedStyleSheet = true;
            }
          }, 50);
        }
      };

    (function() {

      var isLoaded = false;
      twttr.css = function(rules) {
        var styleElement = document.createElement('style');
        styleElement.type = 'text/css';
        if (browser.ie) {
          styleElement.styleSheet.cssText = rules;
        }
        else {
          var frag = document.createDocumentFragment();
          frag.appendChild(document.createTextNode(rules));
          styleElement.appendChild(frag);
        }
        function append() {
          document.getElementsByTagName('head')[0].appendChild(styleElement);
        }

        // oh IE we love you.
        // this is needed because you can't modify document body when page is loading
        if (!browser.ie || isLoaded) {
          append();
        }
        else {
          window.attachEvent('onload', function() {
            isLoaded = true;
            append();
          });
        }
      };
    })();


    TWTR.Widget.isLoaded = false;
    TWTR.Widget.loadingStyleSheet = false;
    TWTR.Widget.hasLoadedStyleSheet = false;
    TWTR.Widget.WIDGET_NUMBER = 0;
    TWTR.Widget.matches = {
      mentions: /^@[a-zA-Z0-9_]{1,20}\b/,
      any_mentions: /\b@[a-zA-Z0-9_]{1,20}\b/
    };

    TWTR.Widget.jsonP = function(url, callback) {
      var script = document.createElement('script');
      var head = document.getElementsByTagName('head')[0];
      script.type = 'text/javascript';
      script.src = url;
      head.insertBefore(script, head.firstChild)
      callback(script);
      return script;
    };

    TWTR.Widget.prototype = function() {

      var http = isHttps ? 'https://' : 'http://';
      var domain = window.location.hostname.match(/twitter\.com/) ?
          (window.location.hostname + ":" + window.location.port) : 'twitter.com';
      var base = http + 'search.' + domain + '/search.';

      var profileBase = http + 'api.' + domain + '/1/statuses/user_timeline.';
      var favBase = http + domain + '/favorites/';
      var listBase = http + 'api.' + domain + '/1/';
      var occasionalInterval = 25000; // 25 seconds
      var defaultAvatar = isHttps ? 'https://twitter-widgets.s3.amazonaws.com/j/1/default.gif' : 'http://widgets.twimg.com/j/1/default.gif';

      return {
        init: function(opts) {
          var that = this;
          // first, define public callback for this widget
          this._widgetNumber = ++TWTR.Widget.WIDGET_NUMBER;
          TWTR.Widget['receiveCallback_' + this._widgetNumber] = function(resp) {
            that._prePlay.call(that, resp);
          };
          this._cb = 'TWTR.Widget.receiveCallback_' + this._widgetNumber;
          this.opts = opts;
          this._base = base;
          this._isRunning = false;
          this._hasOfficiallyStarted = false;
          this._hasNewSearchResults = false;
          this._rendered = false;
          this._profileImage = false;
          this._isCreator = !!opts.creator;

          this._setWidgetType(opts.type);

          this.timesRequested = 0;
          this.runOnce = false;
          this.newResults = false;
          this.results = [];
          this.jsonMaxRequestTimeOut = 19000;
          this.showedResults = [];
          this.sinceId = 1;
          this.source = 'TWITTERINC_WIDGET';
          this.id = opts.id || 'twtr-widget-' + this._widgetNumber;

          this.tweets = 0;
          this.setDimensions(opts.width, opts.height);
          this.interval = opts.interval || 6000;
          this.format = 'json';
          this.rpp = opts.rpp || 50;
          this.subject = opts.subject || '';
          this.title = opts.title || '';
          this.setFooterText(opts.footer);

          this.setSearch(opts.search);
          this._setUrl();
          this.theme = opts.theme ? opts.theme : this._getDefaultTheme();

          if (!opts.id) {
            document.write('<div class="twtr-widget" id="' + this.id + '"></div>');
          }
          this.widgetEl = byId(this.id);
          if (opts.id) {
            classes.add(this.widgetEl, 'twtr-widget');
          }

          if (opts.version >= 2 && !TWTR.Widget.hasLoadedStyleSheet) {
            if (isHttps) {
              twttr.loadStyleSheet('https://twitter-widgets.s3.amazonaws.com/j/2/widget.css', this.widgetEl);
            } else if (opts.creator) {
              twttr.loadStyleSheet('/stylesheets/widgets/widget.css', this.widgetEl);
            } else {
              // twttr.loadStyleSheet('http://localhost.twitter.com:3000/stylesheets/widgets/widget.css', this.widgetEl);
              twttr.loadStyleSheet('http://widgets.twimg.com/j/2/widget.css', this.widgetEl);
            }
          }

          this.occasionalJob = new Occasionally(
            function(decay) {
              that.decay = decay;
              that._getResults.call(that);
            },

            function() {
              return that._decayDecider.call(that);
            },

            occasionalInterval
          );

          this._ready = is.fn(opts.ready) ? opts.ready : function() { };

          // preset features
          this._isRelativeTime = true;
          this._tweetFilter = false;
          this._avatars = true;
          this._isFullScreen = false;
          this._isLive = true;
          this._isScroll = false;
          this._loop = true;
          this._showTopTweets = (this._isSearchWidget) ? true : false;
          this._behavior = 'default';
          this.setFeatures(this.opts.features);

          this.intervalJob = new IntervalJob(this.interval, this._loop, function(tweet) {
            that._normalizeTweet(tweet);
          });

          return this;
        },

        /**
          * @public
          * @param {Int} w - width for widget
          * @param {Int} h - height for widget
          * @return self
          */
        setDimensions: function(w, h) {
          this.wh = (w && h) ? [w, h] : [250, 300]; // default w/h if none provided
          if (w == 'auto' || w == '100%') {
            this.wh[0] = '100%';
          } else {
            this.wh[0] = ((this.wh[0] < 150) ? 150 : this.wh[0]) + 'px'; // min width is 150
          }
          this.wh[1] = ((this.wh[1] < 100) ? 100 : this.wh[1]) + 'px'; // min height is 100
          return this;
        },

        setRpp: function(rpp) {
          var rpp = parseInt(rpp);
          this.rpp = (is.number(rpp) && (rpp > 0 && rpp <= 100)) ? rpp : 30;
          return this;
        },

        /**
          * @private
          * @param {String} type the kind of widget you're instantiating
          * @return self
          */
        _setWidgetType: function(type) {
          this._isSearchWidget = false,
          this._isProfileWidget = false,
          this._isFavsWidget = false,
          this._isListWidget = false;
          switch(type) {
            case 'profile':
              this._isProfileWidget = true;
              break;
            case 'search':
              this._isSearchWidget = true,
              this.search = this.opts.search;
              break;
            case 'faves':
            case 'favs':
              this._isFavsWidget = true;
              break;
            case 'list':
            case 'lists':
              this._isListWidget = true;
              break;
          };
          return this;
        },

        /**
          * @public
          * @param {object}
          * @return self
          * allows implementer to set features which include:
          * - avatars {bool}
          * - timestamp {bool}
          * - hashtags {bool}
          * setting any of the previous properties will appropriately hide/show that feature
          * @example
          * WidgetInstance.setFeatures({ fullscreen: true, avatars: true, timestamp: false, hashtags: false }).render().start();
          * @return self
          */
        setFeatures: function(features) {

          if (features) {
            if (is.def(features.filters)) {
              this._tweetFilter = features.filters;
            }
            if (is.def(features.dateformat)) {
              this._isRelativeTime = !!(features.dateformat !== 'absolute')
            }

            if (is.def(features.fullscreen) && is.bool(features.fullscreen)) {
              if (features.fullscreen) {
                this._isFullScreen = true;
                this.wh[0] = '100%';
                this.wh[1] = (getViewportHeight() - 90) + 'px';
                var that = this;
                events.add(window, 'resize', function(e) {
                  that.wh[1] = getViewportHeight();
                  that._fullScreenResize();
                });
              }
            }

            if (is.def(features.loop) && is.bool(features.loop)) {
              this._loop = features.loop;
            }

            if (is.def(features.behavior) && is.string(features.behavior)) {
              switch (features.behavior) {
                case 'all':
                  this._behavior = 'all';
                  break;
                case 'preloaded':
                  this._behavior = 'preloaded';
                  break;
                default:
                  this._behavior = 'default';
                  break;
              };
            }

            if (is.def(features.toptweets) && is.bool(features.toptweets)) {
              this._showTopTweets = features.toptweets;
              var showTopTweet = (this._showTopTweets) ? 'inline-block' : 'none';
              twttr.css('#' + this.id + ' .twtr-popular { display: ' + showTopTweet + '; }');
            }

            if (!is.def(features.toptweets)) {
              this._showTopTweets = true;
              var showTopTweet = (this._showTopTweets) ? 'inline-block' : 'none';
              twttr.css('#' + this.id + ' .twtr-popular { display: ' + showTopTweet + '; }');
            }

            if (is.def(features.avatars) && is.bool(features.avatars)) {

              if (!features.avatars) {
                twttr.css('#' + this.id + ' .twtr-avatar, #' + this.id + ' .twtr-user { display: none; } ' +
                '#' + this.id + ' .twtr-tweet-text { margin-left: 0; }');
                this._avatars = false;
              } else {
                var margin = (this._isFullScreen) ? '90px' : '40px';
                twttr.css('#' + this.id + ' .twtr-avatar { display: block; } #' + this.id + ' .twtr-user { display: inline; } ' +
                '#' + this.id + ' .twtr-tweet-text { margin-left: ' + margin + '; }');
                this._avatars = true;
              }

            }
            else {
              if (this._isProfileWidget) {
                this.setFeatures({ avatars: false });
                this._avatars = false;
              }
              else {
                this.setFeatures({ avatars: true });
                this._avatars = true;
              }
            }

            if (is.def(features.hashtags) && is.bool(features.hashtags)) {
              (!features.hashtags) ?
                  twttr.css('#' + this.id + ' a.twtr-hashtag { display: none; }') : '';
            }

            if (is.def(features.timestamp) && is.bool(features.timestamp)) {
              var display = features.timestamp ? 'block' : 'none';
              twttr.css('#' + this.id + ' em { display: ' + display + '; }');
            }

            if (is.def(features.live) && is.bool(features.live)) {
              this._isLive = features.live;
            }
            if (is.def(features.scrollbar) && is.bool(features.scrollbar)) {
              this._isScroll = features.scrollbar;
            }

          }

          else {

            if (this._isProfileWidget) {
              this.setFeatures({ avatars: false });
              this._avatars = false;
            }
            if (this._isProfileWidget || this._isFavsWidget) {
              this.setFeatures({ behavior: 'all' });
            }

          }
          return this;
        },

        /**
          * @private
          * @param e Event listener for window resizing
          */
        _fullScreenResize: function() {
          var timeline = getByClass('twtr-timeline', 'div', document.body, function(el) {
            el.style.height = (getViewportHeight() - 90) + 'px';
          });
        },

        /**
          * @public facade
          * @param {int} in seconds
          * convenience method for setting time between each tweet render
          * @return self
          */
        setTweetInterval: function(interval) {
          this.interval = interval;
          return this;
        },

        /**
          * @public
          * @param {string} url
          * sets a url base for the JSONP call
          * useful for future API implementations or moderation platforms
          * @return self
          */
        setBase: function(b) {
          this._base = b;
          return this;
        },

        /**
          * @public
          * @param {string} username
          * used to distinguish a "favs" widget
          * @return self
          */
        setUser: function(username, opt_realname) {
          this.username = username;
          this.realname = opt_realname || ' ';
          if (this._isFavsWidget) {
            this.setBase(favBase + username + '.');
          }
          else if (this._isProfileWidget) {
            this.setBase(profileBase + this.format + '?screen_name=' + username);
          }
          this.setSearch(' ');
          return this;
        },

        /**
          * @public
          * @param {string} username - the owner of the list
          * @param {string} listName - the name of the list
          * return self
          */
        setList: function(username, listname) {
          this.listslug = listname.replace(/ /g, '-').toLowerCase();
          this.username = username;
          this.setBase(listBase + username + '/lists/' + this.listslug + '/statuses.');
          this.setSearch(' ');
          return this;
        },

        /**
          * @public
          * @param {string}
          * sets the profile image source to display in the widget
          * @return self
          */
        setProfileImage: function(url) {
          this._profileImage = url;
          this.byClass('twtr-profile-img', 'img').src = matchUrlScheme(url);
          this.byClass('twtr-profile-img-anchor', 'a').href = 'http://twitter.com/intent/user?screen_name=' + this.username;
          return this;
        },

        /**
          * @public
          * @param {string}
          * sets the main title to display at top of widget
          * @return self
          */
        setTitle: function(title) {
          this.title = title;
          this.widgetEl.getElementsByTagName('h3')[0].innerHTML = this.title;
          return this;
        },

        /**
          * @public
          * @param {string}
          * sets the main caption to display at top of widget (below title)
          * @return self
          */
        setCaption: function(subject) {
          this.subject = subject;
          this.widgetEl.getElementsByTagName('h4')[0].innerHTML = this.subject;
          return this;
        },

        /**
          * @public
          * @param {string}
          * sets the footer text
          * @return self
          */
        setFooterText: function(s) {
          this.footerText = (is.def(s) && is.string(s)) ? s : 'Join the conversation';
          if (this._rendered) {
            this.byClass('twtr-join-conv', 'a').innerHTML = this.footerText;
          }
          return this;
        },

        /**
          * @public
          * @param {string}
          * @return self
          * does double time. sets the search terms, and sets the appropriate
          * hyper reference on bottom anchor if widget has been rendered
          */
        setSearch: function(s) {
          this.searchString = s || '';
          this.search = encodeURIComponent(this.searchString);
          this._setUrl();
          if (this._rendered) {
            var anchor = this.byClass('twtr-join-conv', 'a');
            anchor.href = 'http://twitter.com/' + this._getWidgetPath();
          }

          return this;
        },

        _getWidgetPath: function() {
          if (this._isProfileWidget) {
            return this.username;
          }
          else if (this._isFavsWidget) {
            return this.username + '/favorites';
          }
          else if (this._isListWidget) {
            return this.username + '/lists/' + this.listslug;
          }
          else {
            return '#search?q=' + this.search;
          }
        },

        /**
          * @private
          * @return self
          * creates the proper URL to request data via JSONP
          */
        _setUrl: function() {
          var that = this;

          function cacheBust() {
            // chrome i hate your caching
            return '&' + (+new Date) + '=cachebust';
          }

          function showSince() {
            return (that.sinceId == 1) ? '' : '&since_id=' + that.sinceId + '&refresh=true';
          }

          if (this._isProfileWidget) {
            this.url = this._base + '&callback=' + this._cb +
                       '&include_rts=true' +
                       '&count=' + this.rpp + showSince() + '&clientsource=' + this.source;
          }

          else if (this._isFavsWidget || this._isListWidget) {
            this.url = this._base + this.format + '?callback=' + this._cb + showSince() +
                       '&include_rts=true' +
                       '&clientsource=' + this.source;
          }

          else {
            this.url = this._base + this.format + '?q=' + this.search +
                       '&include_rts=true' +
                       '&callback=' + this._cb +
                       '&rpp=' + this.rpp + showSince() + '&clientsource=' + this.source;
            if (!this.runOnce) {
              this.url += '&result_type=mixed';
            }
          }
          this.url += cacheBust();
          return this;
        },

        /**
          * @private
          */
        _getRGB: function(hex) {
          return hex_rgb(hex.substring(1, 7));
        },

        /**
          * @public
          * @param {object}
          * @param {boolean} important whether to be important style
          * @return self
          * allows implementer to set their own theme.
          * theme object can be passed into contructor, or set here.
          * defaults to default theme properties when not set
          */
        setTheme: function(o, important) {
          var that = this;
          var imp = ' !important';

          var onCreator = ((window.location.hostname.match(/twitter\.com/)) && (window.location.pathname.match(/goodies/)));
          if (important || onCreator) {
            imp = '';
          }
          this.theme = {
            shell: {
              background: function() {
                return o.shell.background || that._getDefaultTheme().shell.background;
              }(),

              color: function() {
                return o.shell.color || that._getDefaultTheme().shell.color;
              }()
            },

            tweets: {
              background: function() {
                return o.tweets.background || that._getDefaultTheme().tweets.background;
              }(),

              color: function() {
                return o.tweets.color || that._getDefaultTheme().tweets.color;
              }(),

              links: function() {
                return o.tweets.links || that._getDefaultTheme().tweets.links;
              }()
            }
          };

          var style = '#' + this.id + ' .twtr-doc, \
                     #' + this.id + ' .twtr-hd a, \
                     #' + this.id + ' h3, \
                     #' + this.id + ' h4, \
                     #' + this.id + ' .twtr-popular {\
            background-color: ' + this.theme.shell.background + imp + ';\
            color: ' + this.theme.shell.color + imp + ';\
          }\
          #' + this.id + ' .twtr-popular {\
            color: ' + this.theme.tweets.color + imp + ';\
            background-color: rgba(' + this._getRGB(this.theme.shell.background) + ', .3)' + imp + ';\
          }\
          #' + this.id + ' .twtr-tweet a {\
            color: ' + this.theme.tweets.links + imp + ';\
          }\
          #' + this.id + ' .twtr-bd, #' + this.id + ' .twtr-timeline i a, \
          #' + this.id + ' .twtr-bd p {\
            color: ' + this.theme.tweets.color + imp + ';\
          }\
          #' + this.id + ' .twtr-new-results, \
          #' + this.id + ' .twtr-results-inner, \
          #' + this.id + ' .twtr-timeline {\
            background: ' + this.theme.tweets.background + imp + ';\
          }';

          if (browser.ie) {
            style += '#' + this.id + ' .twtr-tweet { background: ' + this.theme.tweets.background + imp + '; }';
          }

          twttr.css(style);
          return this;
        },

        /**
          * @public
          * @param {string} classname
          * @param {string} tagname
          * @param optional {bool} whether to return collection or defaults to first match
          * @return HTML Element || Array HTML Elements
          * helper to get elements by classname based on the widget being the context
          */
        byClass: function(c, tag, opt_all) {
          var collection = getByClass(c, tag, byId(this.id));
          return (opt_all) ? collection : collection[0];
        },

        /**
          * @public
          * @return self
          * renders the widget onto an HTML page
          */
        render: function() {
          var that = this;

          if (!TWTR.Widget.hasLoadedStyleSheet) {
            window.setTimeout(function() {
              that.render.call(that);
            }, 50);
            return this;
          }

          this.setTheme(this.theme, this._isCreator);

          if (this._isProfileWidget) {
            classes.add(this.widgetEl, 'twtr-widget-profile');
          }

          if (this._isScroll) {
            classes.add(this.widgetEl, 'twtr-scroll')
          }
          if (!this._isLive && !this._isScroll) {
            this.wh[1] = 'auto';
          }
          if (this._isSearchWidget && this._isFullScreen) {
            document.title = 'Twitter search: ' + escape(this.searchString);
          }
          this.widgetEl.innerHTML = this._getWidgetHtml();
          var timeline = this.byClass('twtr-timeline', 'div');
          if (this._isLive && !this._isFullScreen) {
            var over = function(e) {
              if (that._behavior === 'all') {
                return;
              }
              if (withinElement.call(this, e)) {
                that.pause.call(that);
              }
            };
            var out = function(e) {
              if (that._behavior === 'all') {
                return;
              }
              if (withinElement.call(this, e)) {
                that.resume.call(that);
              }
            };

            this.removeEvents = function() {
              events.remove(timeline, 'mouseover', over);
              events.remove(timeline, 'mouseout', out);
            };
            events.add(timeline, 'mouseover', over);
            events.add(timeline, 'mouseout', out);
          }
          this._rendered = true;
          // call the ready handler
          this._ready();
          return this;
        },

        /**
          * empty placeholder for removing events
          * on live widgets
          */
        removeEvents: function() { },

        /**
          * @private
          * @return {object} theme
          */
        _getDefaultTheme: function() {
          return {
            shell: {
              background: '#8ec1da',
              color: '#ffffff'
            },

            tweets: {
              background: '#ffffff',
              color: '#444444',
              links: '#1985b5'
            }

          };
        },

        /**
          * @private
          * @return {string}
          * builds an HTML string that represents the widget chrome
          */
        _getWidgetHtml: function() {
          var that = this;

          function getHeader() {
            if (that._isProfileWidget) {
              return '<a target="_blank" href="http://twitter.com/" class="twtr-profile-img-anchor"><img alt="profile" class="twtr-profile-img" src="' + defaultAvatar + '"></a>\
                      <h3></h3>\
                      <h4></h4>';
            }
            else {
              return '<h3>' + that.title + '</h3><h4>' + that.subject + '</h4>';
            }
          }

          function isFull() {
            return that._isFullScreen ? ' twtr-fullscreen' : '';
          }

          var logo = isHttps ? 'https://twitter-widgets.s3.amazonaws.com/i/widget-logo.png' : 'http://widgets.twimg.com/i/widget-logo.png';

          if (this._isFullScreen) {
            logo = 'https://twitter-widgets.s3.amazonaws.com/i/widget-logo-fullscreen.png';
          }

          var html = '<div class="twtr-doc' + isFull() + '" style="width: ' + this.wh[0] + ';">\
            <div class="twtr-hd">' + getHeader() + ' \
            </div>\
            <div class="twtr-bd">\
              <div class="twtr-timeline" style="height: ' + this.wh[1] + ';">\
                <div class="twtr-tweets">\
                  <div class="twtr-reference-tweet"></div>\
                  <!-- tweets show here -->\
                </div>\
              </div>\
            </div>\
            <div class="twtr-ft">\
              <div><a target="_blank" href="http://twitter.com"><img alt="" src="' + logo + '"></a>\
                <span><a target="_blank" class="twtr-join-conv" style="color:' + this.theme.shell.color + '" href="http://twitter.com/' + this._getWidgetPath() + '">' + this.footerText + '</a></span>\
              </div>\
            </div>\
          </div>';

          return html;
        },

        /**
          * @private
          * @return self
          * puts the tweet in the dom
          */
        _appendTweet: function(el) {
          this._insertNewResultsNumber();
          insertAfter(el, this.byClass('twtr-reference-tweet', 'div'));
          return this;
        },

        /**
          * @private
          * @return self
          * slides in a rendered tweet
          */
        _slide: function(el) {
          var that = this;
          var height = getFirst(el).offsetHeight;
          if (this.runOnce) {
            new Animate(el, 'height', {
              from: 0,
              to: height,
              time: 500,
              callback: function() {
                that._fade.call(that, el);
              }
            }).start();
          }
          return this;
        },

        /**
          * @private
          * @return self
          * fades in a rendered tweet
          */
        _fade: function(el) {
          var that = this;

          if (Animate.canTransition) {
            el.style.webkitTransition = 'opacity 0.5s ease-out';
            el.style.opacity = 1;
            return this;
          }
          new Animate(el, 'opacity', {
            from: 0,
            to: 1,
            time: 500
          }).start();
          return this;
        },

        /**
          * @private
          * @return self
          * removes the last tweet if it is offscreen
          */
        _chop: function() {
          if (this._isScroll) {
            return this;
          }
          var tweets = this.byClass('twtr-tweet', 'div', true);
          var resultUpdates = this.byClass('twtr-new-results', 'div', true);
          if (tweets.length) {
            for (var i=tweets.length - 1; i >=0; i--) {
              var tweet = tweets[i];
              var top = parseInt(tweet.offsetTop);
              if (top > parseInt(this.wh[1])) {
                removeElement(tweet);
              } else {
                break;
              }
            }


            if (resultUpdates.length > 0) {
              var result = resultUpdates[resultUpdates.length - 1];
              var resultTop = parseInt(result.offsetTop);
              if (resultTop > parseInt(this.wh[1])) {
                removeElement(result);
              }
            }
          }

          return this;
        },

        /**
          * @private
          * @return self
          * Big Facade for chop, append, slide, and fade
          */
        _appendSlideFade: function(opt_element) {
          var el = opt_element || this.tweet.element;
          this
            ._chop()
            ._appendTweet(el)
            ._slide(el);
          return this;
        },

        /**
          * @private
          * @return self
          * generates the HTML for a single tweet item
          */
        _createTweet: function(o) {
          o.timestamp = o.created_at;
          o.created_at = this._isRelativeTime ? timeAgo(o.created_at) : absoluteTime(o.created_at);
          this.tweet = new Tweet(o);
          if (this._isLive && this.runOnce) {
            this.tweet.element.style.opacity = 0;
            this.tweet.element.style.filter = 'alpha(opacity:0)';
            this.tweet.element.style.height = '0';
          }
          return this;
        },

        /**
          * @private
          * @param {Function} callback function that receives the results
          * makes a jsonP call to twitter.com
          */
        _getResults: function() {
          var that = this;

          this.timesRequested++;
          this.jsonRequestRunning = true;

          this.jsonRequestTimer = window.setTimeout(function() {

            if (that.jsonRequestRunning) {
              clearTimeout(that.jsonRequestTimer);
              that.jsonRequestTimer = null;
            }

            that.jsonRequestRunning = false;
            removeElement(that.scriptElement);
            that.newResults = false;
            that.decay();
          }, this.jsonMaxRequestTimeOut);
          TWTR.Widget.jsonP(that.url, function(script) {
            that.scriptElement = script;
          });

        },

        /**
          * @public
          * @return self
          * clears out the tweet space. used internally,
          * but free to use publicly
          */
        clear: function() {
          var tweets = this.byClass('twtr-tweet', 'div', true);
          var results = this.byClass('twtr-new-results', 'div', true);
          tweets = tweets.concat(results);
          each(tweets, function(el) {
            removeElement(el);
          });

          return this;
        },

        _sortByMagic: function(results) {
          var that = this;
          if (this._tweetFilter) {
            if (this._tweetFilter.negatives) {
              results = results.filter(function(el) {
                if (!that._tweetFilter.negatives.test(el.text)) {
                  return el;
                }
              });
            }
            if (this._tweetFilter.positives) {
              results = results.filter(function(el) {
                if (that._tweetFilter.positives.test(el.text)) {
                  return el;
                }
              });
            }
          }
          switch (this._behavior) {
            case 'all':
              this._sortByLatest(results);
              break;
            case 'preloaded':
            default:
              this._sortByDefault(results);
              break;
          };

          if (this._isLive && this._behavior !== 'all') {
            this.intervalJob.set(this.results);
            this.intervalJob.start();
          }

          return this;
        },

        /**
          * @private
          * @return results
          * puts the toptweets for search widget at the top
          */
        _loadTopTweetsAtTop: function(results) {
          var regular = [],
              popular = [],
              arr = [];
          // top tweets
          each(results, function(el) {
            if (el.metadata && el.metadata.result_type && el.metadata.result_type == 'popular') {
              popular.push(el);
            } else {
              regular.push(el);
            }
          });
          var result = popular.concat(regular);
          return result;
        },

        _sortByLatest: function(results) {
          this.results = results;
          this.results = this.results.slice(0, this.rpp);
          this.results = this._loadTopTweetsAtTop(this.results);
          this.results.reverse();
          return this;
        },

        /**
          * @private
          * @return self
          * default sorting method which tracks views and loops
          */
        _sortByDefault: function(results) {
          var that = this;

          var getDater = function(dateString) {
            return new Date(dateString).getTime();
          };

          // merge new results with old
          this.results.unshift.apply(this.results, results);

          each(this.results, function(el) {
            if (!el.views) {
              el.views = 0;
            }
          });

          // sort by date
          this.results.sort(function(a, b) {
            if (getDater(a.created_at) > getDater(b.created_at)) {
              return -1;
            }
            else if (getDater(a.created_at) < getDater(b.created_at)) {
              return 1;
            }
            else {
              return 0;
            }
          });

          // now cut off the oldest
          this.results = this.results.slice(0, this.rpp);

          this.results = this._loadTopTweetsAtTop(this.results);
          var foo = this.results;

          // now sort by views
          this.results = this.results.sort(function(a, b) {
            if (a.views < b.views) {
              return -1;
            }
            else if (a.views > b.views) {
              return 1;
            }
            return 0;
          });

          if (!this._isLive) {
            this.results.reverse();
          }

        },

        /**
          * @private
          * @method prePlay does a pre-check against last result.
          * @param resp the JSON response from twitter JsonP API
          */
        _prePlay: function(resp) {
          if (this.jsonRequestTimer) {
            clearTimeout(this.jsonRequestTimer);
            this.jsonRequestTimer = null;
          }

          if (!browser.ie) {
            removeElement(this.scriptElement);
          }

          if (resp.error) {
            this.newResults = false;
          }

          else if (resp.results && resp.results.length > 0) {
            this.response = resp;

            this.newResults = true;
            this.sinceId = resp.max_id_str;

            this._sortByMagic(resp.results);
            if (this.isRunning()) {
              this._play();
            }

          }

          else if ((this._isProfileWidget || this._isFavsWidget || this._isListWidget) && is.array(resp) && resp.length) {

            this.newResults = true;

            if (!this._profileImage && this._isProfileWidget) {
              var name = resp[0].user.screen_name;
              this.setProfileImage(resp[0].user.profile_image_url);
              this.setTitle(resp[0].user.name);
              this.setCaption('<a target="_blank" href="http://twitter.com/intent/user?screen_name=' + name + '">' + name + '</a>');
            }

            this.sinceId = resp[0].id_str;

            this._sortByMagic(resp);

            if (this.isRunning()) {
              this._play();
            }

          }

          else {
            this.newResults = false;
          }

          this._setUrl();
          if (this._isLive) {
            this.decay();
          }

        },

        /**
          * @private
          * gets the ball rolling with a new widget
          * and resets the interval job
          */
        _play: function() {
          var that = this;
          if (this.runOnce) {
            this._hasNewSearchResults = true;
          }

          if (this._avatars) {
            this._preloadImages(this.results);
          }
          if (this._isRelativeTime && (this._behavior == 'all' || this._behavior == 'preloaded')) {
            each(this.byClass('twtr-timestamp', 'a', true), function(el) {
              el.innerHTML = timeAgo(el.getAttribute('time'));
            });
          }
          if (!this._isLive || this._behavior == 'all' || this._behavior == 'preloaded') {
            each(this.results, function(needle) {
              if (needle.retweeted_status) {
                needle = needle.retweeted_status;
              }

              if (that._isProfileWidget) {
                needle.from_user = needle.user.screen_name;
                needle.profile_image_url = needle.user.profile_image_url;
              }

              if (that._isFavsWidget || that._isListWidget) {
                needle.from_user = needle.user.screen_name;
                needle.profile_image_url = needle.user.profile_image_url;
              }

              needle.id = needle.id_str;

              that._createTweet({
                id: needle.id,
                user: needle.from_user,
                tweet: ify.clean(needle.text),
                avatar: needle.profile_image_url,
                created_at: needle.created_at,
                needle: needle
              });
              var el = that.tweet.element;
              (that._behavior == 'all') ? that._appendSlideFade(el) : that._appendTweet(el);
            });

            if (this._behavior != 'preloaded') {
              return this;
            }

          }

          return this;
        },

        _normalizeTweet: function(needle) {
          var that = this;
          needle.views++;

          if (this._isProfileWidget) {
            needle.from_user = that.username;
            needle.profile_image_url = needle.user.profile_image_url;
          }

          if (this._isFavsWidget || this._isListWidget) {
            needle.from_user = needle.user.screen_name;
            needle.profile_image_url = needle.user.profile_image_url;
          }

          if (this._isFullScreen) {
            needle.profile_image_url = needle.profile_image_url.replace(/_normal\./, '_bigger.');
          }

          needle.id = needle.id_str;

          this._createTweet({
            id: needle.id,
            user: needle.from_user,
            tweet: ify.clean(needle.text),
            avatar: needle.profile_image_url,
            created_at: needle.created_at,
            needle: needle
          })._appendSlideFade();

        },

        _insertNewResultsNumber: function() {
          if (!this._hasNewSearchResults) {
            this._hasNewSearchResults = false;
            return;
          }

          if (this.runOnce && this._isSearchWidget) {
            var newResultsTotal = this.response.total > this.rpp ? this.response.total : this.response.results.length;
            var plural = newResultsTotal > 1 ? 's' : '';
            var moreThan = (this.response.warning && this.response.warning.match(/adjusted since_id/)) ? 'more than' : '';
            var el = document.createElement('div');
            classes.add(el, 'twtr-new-results');
            el.innerHTML = '<div class="twtr-results-inner"> &nbsp; </div>' +
                           '<div class="twtr-results-hr"> &nbsp; </div><span>' + moreThan + ' <strong>' + newResultsTotal + '</strong> new tweet' + plural + '</span>';
            insertAfter(el, this.byClass('twtr-reference-tweet', 'div'));
            this._hasNewSearchResults = false;
          }
        },

        /**
          * @private
          * helps transitions to be smooth
          */
        _preloadImages: function(results) {
          if (this._isProfileWidget || this._isFavsWidget || this._isListWidget) {
            each(results, function(el) {
              var img = new Image();
              img.src = matchUrlScheme(el.user.profile_image_url);
            });
          }

          else {
            each(results, function(el) {
              (new Image()).src = matchUrlScheme(el.profile_image_url);
            });
          }

        },

        // FIXME: This seems like a bug in Occasionally.
        /**
          * @private
          * @return bool
          * tells the job whether to decay
          */
        _decayDecider: function() {
          var r = false;

          if (!this.runOnce) {
            this.runOnce = true;
            r = true;
          }

          else if (this.newResults) {
            r = true;
          }

          return r;
        },

        /**
          * @public
          * @return self
          * starts the cycle
          */
        start: function() {
          var that = this;
          if (!this._rendered) {
            setTimeout(function() {
              that.start.call(that);
            }, 50);
            return this;
          }
          if (!this._isLive) {
            this._getResults();
          }
          else {
            this.occasionalJob.start();
          }
          this._isRunning = true;
          this._hasOfficiallyStarted = true;
          return this;
        },

        /**
          * @public
          * @return self
          * stops the cycle
          */
        stop: function() {
          this.occasionalJob.stop();

          if (this.intervalJob) {
            this.intervalJob.stop();
          }

          this._isRunning = false;
          return this;
        },

        /**
          * @public
          * @return self
          * will pause the scrolling, but not stop polling for new results
          * useful for 'hover' interactions
          */
        pause: function() {
          if (this.isRunning() && this.intervalJob) {
            this.intervalJob.stop();
            classes.add(this.widgetEl, 'twtr-paused');
            this._isRunning = false;
          }

          if (this._resumeTimer) {
            clearTimeout(this._resumeTimer);
            this._resumeTimer = null;
          }

          return this;
        },

        /**
          * @public
          * @return self
          * it's like unpausing
          */
        resume: function() {
          var that = this;

          if (!this.isRunning() && this._hasOfficiallyStarted && this.intervalJob) {
            this._resumeTimer = window.setTimeout(function() {
              that.intervalJob.start();
              that._isRunning = true;
              classes.remove(that.widgetEl, 'twtr-paused');
            }, 2000);
          }

          return this;
        },

        /**
          * @public
          * @return bool
          * whether the widget is running
          */
        isRunning: function() {
          return this._isRunning;
        },

        /**
          * @public facade
          * @return self
          * convenience method to stop the cycle, then clear it out
          * widget can be reused if destroyed
          */
        destroy: function() {
          this.stop();
          this.clear();
          this.runOnce = false;
          this._hasOfficiallyStarted = false;
          this._profileImage = false;
          this._isLive = true;
          this._tweetFilter = false;
          this._isScroll = false;
          this.newResults = false;
          this._isRunning = false;
          this.sinceId = 1;
          this.results = [];
          this.showedResults = [];
          this.occasionalJob.destroy();

          if (this.jsonRequestRunning) {
            clearTimeout(this.jsonRequestTimer);
          }

          classes.remove(this.widgetEl, 'twtr-scroll');
          this.removeEvents();
          return this;
        }
      };
    }();
  })();

  // Support Web Intents
  // http://dev.twitter.com/pages/intents
  var intentRegex = /twitter\.com(\:\d{2,4})?\/intent\/(\w+)/,
      shortIntents = { tweet: true, retweet:true, favorite:true },
      windowOptions = 'scrollbars=yes,resizable=yes,toolbar=no,location=yes',
      winHeight = screen.height,
      winWidth = screen.width;

  function handleIntent(e) {
    e = e || window.event;
    var target = e.target || e.srcElement,
        m, width, height, left, top;

    while (target && target.nodeName.toLowerCase() !== 'a') {
      target = target.parentNode;
    }

    if (target && target.nodeName.toLowerCase() === 'a' && target.href) {
      m = target.href.match(intentRegex);
      if (m) {
        width = 550;
        height = (m[2] in shortIntents) ? 420 : 560;

        left = Math.round((winWidth / 2) - (width / 2));
        top = 0;

        if (winHeight > height) {
          top = Math.round((winHeight / 2) - (height / 2));
        }

        window.open(target.href, 'intent', windowOptions + ',width=' + width + ',height=' + height + ',left=' + left + ',top=' + top);
        e.returnValue = false;
        e.preventDefault && e.preventDefault();
      }
    }
  }

  if (document.addEventListener) {
    document.addEventListener('click', handleIntent, false);
  } else if (document.attachEvent) {
    document.attachEvent('onclick', handleIntent);
  }
  // end Web Intents

})(); // #end application closure
