function Crossfader(ids, paging_dom_id, time_to_fade, time_between_fades) {
  this.start = function() { 
    this.playing = true
    this.transition(this.current_frame) // display current frame

    var thisObj = this // fixes scope issue
    setTimeout(function() {thisObj.tick(thisObj)}, thisObj.time_between_fades)   
  }

  this.stop = function() { this.playing = false }

  this.prev = function() {
    this.current_frame == 0 ? this.current_frame = this.ids.length - 1 : this.current_frame-- 
    this.transition(this.current_frame)
  }

  this.next = function() {
    this.current_frame == this.ids.length - 1 ? this.current_frame = 0 : this.current_frame ++
    this.transition(this.current_frame)
  }  

  this.transition = function(frame) {
    var queue = Effect.Queues.get('global')
 
    // cancel all the current fades
    queue.each(function(e) { e.cancel(); })

    // We've got some scope issues coming up, so let's copy these to private variables temporarily
    var our_ids = this.ids

    // fade out everything that's not invisible and not our 'show' frame
    this.ids.reject(function(e) {
      return (e == our_ids[frame])
    }).each(function(e) {
      if ($(e).getStyle("opacity") > 0.01)
        new Effect.Fade(e)
    })
    
    // fade in our frame
    new Effect.Appear(this.ids[frame], { duration: time_to_fade / 1000.0 })
    
    // update the you-are-here field
    $(this.paging_dom_id).innerHTML = (this.current_frame + 1) + " of " + this.ids.length   
  }


  this.tick = function(thisObj) {
    if (thisObj.playing == true) {
      thisObj.next()
    }
    setTimeout(function() {thisObj.tick(thisObj)}, thisObj.time_between_fades)  
  }

  this.ids = ids
  this.paging_dom_id = paging_dom_id
  this.time_to_fade = time_to_fade
  this.time_between_fades = time_between_fades
  this.current_frame = 0 // prime the pump.

  this.start()
}
 
