/*
 * jqDock jQuery plugin
 * Version : 1.2
 * Author : Roger Barrett
 * Date : June 2008
 *
 * Inspired by:
 *   iconDock jQuery plugin
 *   http://icon.cat/software/iconDock
 *   version: 0.8 beta
 *   date: 2/05/2007
 *   Copyright (c) 2007 Isaac Roca & icon.cat (iroca@icon.cat)
 *   Dual licensed under the MIT-LICENSE.txt and GPL-LICENSE.txt
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Dual licensed under the MIT-LICENSE.txt and GPL-LICENSE.txt
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * Change Log :
 * v1.2
 *    - Fixes for Opera v9.5 - many thanks to Rubel Mujica
 * v1.1
 *    - some speed optimisation within the functions called by the event handler
 *    - added positioning of labels (top/middle/bottom and left/center/right)
 *    - added click handler to label (triggers click event on related image)
 *    - added jqDockLabel(Link|Image) class to label, depending on type of current image
 *    - updated demo and documentation for label positioning and clicking on labels
 */
;
(function($){
if(!$.fn.jqDock){ //can't see why it should be, but it doesn't hurt to check

  var jqDock = function(){
    //return an object...
    return {
        version : 1.2
      , defaults : { //can be set at runtime, per menu
          size : 36 //[px] maximum minor axis dimension of image (width or height depending on 'align' : vertical menu = width, horizontal = height)
        , distance : 54 //[px] attenuation distance from cursor
        , coefficient : 1.5 //attenuation coefficient
        , duration : 500 //[ms] duration of initial expansion and off-menu shrinkage
        , align : 'bottom' //[top/middle/bottom or left/center/right] fixes horizontal/vertical expansion axis
        , labels : false //enable/disable display of a label on the current image
        , source : false //function: given context of relevant image element; passed index of image within menu; required to return image source path, or false to use original
        , loader : null //overrides useJqLoader if set to 'image' or 'jquery'
        }
      , useJqLoader : $.browser.opera || $.browser.safari //use jQuery method for loading images, rather than "new Image()" method
      , shrinkInterval : 100 //(ms) the timer interval between each step of the off-menu shrinking
      , docks : [] //array of dock menus
      , X : 0 //mouse position from left
      , Y : 0 //mouse position from top
      //internals to cut down code and ease decision-making (mainly between vertical and horizontal menus)...
      , verthorz : { v: { wh:'height', xy:'Y', tl:'top', lead:'Top', trail:'Bottom', act:'ActualInv' } //Opts.align = left/center/right
                   , h: { wh:'width', xy:'X', tl:'left', lead:'Left', trail:'Right', act:'Actual' } //Opts.align = top/middle/bottom
                   }
      , elementCss : { position:'relative', borderWidth:0, borderStyle:'none', verticalAlign:'top' }
      , vanillaDiv : '<div style="position:relative;margin:0px;padding:0px;border:0px none;background-color:transparent;">'

  /* initDock()
  *  ==========
  * called by the image onload function, it stores and sets image height/width;
  * once all images have been loaded, it completes the setup of the dock menu
  * note: unless all images get loaded, the menu will stay hidden!
  * @context jqDock
  * @param integer (dock index)
  */
      , initDock : function(id){
      //========================================
          var ME = this
            , Dock = this.docks[id] //convenience
            , op = Dock.Opts //convenience
            , off = 0
            , AI = $('a, img', Dock.Menu)
            , i = 0
            , j, el, wh, acc, upad
            , opPre95 = ($.browser.opera && (1*($.browser.version.match(/^(\d+\.\d+)/)||[0,0])[1]) < 9.5) // v1.2 : need to distinguish Opera v9.5
            ;
          // things will screw up if we don't clear text nodes...
          this.removeText(Dock.Menu);
          //set some basic styles on the dock elements, otherwise it won't work
          if(op.orient.vh == 'h'){
            AI.css(this.elementCss);
            if(opPre95 || !$.boxModel){ //Opera (v1.2 : pre v9.5 only), and IE in quirks mode, can't handle floated blocks...
              AI.filter('a').css({lineHeight:0, fontSize:'0px'});
            }else{ //not Opera or IE in quirks mode...
              var hcss = {display:'block'};
              hcss['float'] = 'left'; //don't want any 'reserved word' problems from IE
              AI.filter('img').css(hcss);
            }
          }else{ //vertical docks require a div wrapper around each menu element (v1.2 : set anchors/images to display block)...
            AI.not($('a img', Dock.Menu)).wrap(this.vanillaDiv + '</div>').end().css(this.elementCss).css({display:'block'});
          }
          //resize each image and store various settings wrt main axis...
          while(i < Dock.Elem.length){
            el = Dock.Elem[i++];
            //resize the image to make the minor axis dimension meet the specified 'Opts.size'...
            wh = this.keepProportion(el, op.size, {vh:op.orient.inv, inv:op.orient.vh}); //inverted!
            el.Actual = el.Final = el.Initial = wh[op.vh.wh];
            el.SizeDiff = el[op.vh.wh] - el.Initial; //on main axis!
            el.Img.css(wh); //resize the image to its new shrunken setting
            //remove titles, alt text...
            el.Img.removeAttr('title').attr({alt:''}).parent('a').removeAttr('title');
            //calculate shrinkage step size
            el.ShrinkStep = Math.floor(el.SizeDiff * this.shrinkInterval / op.duration);
            //use inverts because we're after the minor axis dimension...
            Dock[op.vh.inv.wh] = Math.max(Dock[op.vh.inv.wh], op.size + el.Pad[op.vh.inv.lead] + el.Pad[op.vh.inv.trail]);

            el.Offset = off;
            el.Centre = el.Offset + el.Pad[op.vh.lead] + (el.Initial / 2);
            off += el.Initial + el.Pad[op.vh.lead] + el.Pad[op.vh.trail];
          }

          //'best guess' at calculating max 'spread' (main axis dimension - horizontal or vertical) of menu:
          //for each img element of the menu, call setSizes() with a forced cursor position of the centre of the image;
          //setSizes() will set each element's Final value, so tally them all, including user-applied padding, to give
          //an overall width/height for this cursor position; set dock width/height to be the largest width/height found
          i = 0;
          while(i < Dock.Elem.length){
            el = Dock.Elem[i++];
            acc = 0; //accumulator for main axis image dimensions
            upad = el.Pad[op.vh.lead] + el.Pad[op.vh.trail]; //user padding in main axis
            //tally the minimum widths...
            Dock.Spread += el.Initial + upad;
            //set sizes with an overridden cursor position...
            this.setSizes(id, el.Centre);
            //tally image widths/heights (plus padding)...
            j = Dock.Elem.length;
            while(j){
              //note that Final is an image dimension (in main axis) and does not include any user padding...
              acc += Dock.Elem[--j].Final + upad;
            }
            //keep largest main axis dock dimension...
            Dock[op.vh.wh] = Math.max(Dock[op.vh.wh], acc);
          }
          //reset Final for each image...
          while(i){
            el = Dock.Elem[--i];
            el.Final = el.Initial;
          }
          var wrap = [ this.vanillaDiv
                     , '<div class="jqDock" style="position:absolute;top:0px;left:0px;padding:0px;'
                     , 'margin:0px;overflow:visible;height:'
                     , Dock.height, 'px;width:', Dock.width
                     , 'px;"></div></div>'
                     ].join('');
          Dock.Yard = $(Dock.Menu).wrapInner(wrap).find('div.jqDock');
          //let's see if the user has applied any css border styling to div.jqDock...
          $.each([op.vh.lead, op.vh.trail], function(n, v){
              Dock.Borders[v] = ME.asNumber(Dock.Yard.css('border'+v+'Width'));
            });
          //if div.jqDock has a border we need to shift it a bit so the border doesn't get lost...
          if(Dock.Borders[op.vh.lead]){
            Dock.Yard.css(op.vh.tl, Math.ceil(Dock.Borders[op.vh.lead] / 2));
          }
          //shrink all images down to 'at rest' size, and add appropriate identifying class...
          while(i < Dock.Elem.length){
            el = Dock.Elem[i];
            this.changeSize(id, i, el.Final, true); //force
            el.Img.addClass('jqDockMouse'+id+'_'+(i++));
          }
          //show the menu now...
          $(Dock.Menu).show();
          //now that the menu is visible we can set up labels and get label widths...
          if(Dock.Opts.labels){
            $.each(Dock.Elem, function(i){
                ME.setLabel(id, this.Label);
              });
            Dock.Label.hide();
          }
          //bind a mousehandler to the menu...
          Dock.Yard.bind('mouseover mouseout mousemove', function(e){ ME.mouseHandler(e); });
        } //end function initDock()

  /* altImage()
  *  ==========
  * tests to see if an image has an alt attribute that looks like an image path, returning it if found, else false
  * note: context of the image element
  * @context DOM element (image)
  * @return string (image path) or false
  */
      , altImage : function(){
          var alt = $(this).attr('alt');
          return (alt && alt.match(/\.(gif|jpg|jpeg|png)$/i)) ? alt : false;

        } //end function altImage()

  /* removeText()
  *  ============
  * removes ALL text nodes from the menu, so that we don't get spacing issues between menu elements
  * note : this includes text within anchors
  * @context jqDock
  * @param DOM element
  * @recursive
  */
      , removeText : function(el){
      //==========================
          var i = el.childNodes.length
            , j
            ;
          while(i){
            j = el.childNodes[--i];
            if(j.childNodes && j.childNodes.length){
              this.removeText(j);
            }else if(j.nodeType == 3){
              el.removeChild(j);
            }
          }
        } //end function removeText()

  /* asNumber()
  *  ==========
  * returns numeric of leading digits in string argument
  * @context jqDock
  * @param string
  * @return integer
  */
      , asNumber : function(x){
      //=========================
          var r = parseInt(x, 10);
          return isNaN(r) ? 0 : r;
        } //end function asNumber()

  /* keepProportion()
  *  ================
  * returns an object containing width and height, with the one NOT represented by 'dim'
  * being calculated proportionately
  * if horizontal then attenuation is along vertical (x) axis, thereby setting the new
  * dimension for width, so the one to keep in proportion is height; and vice versa for
  * vertical menus, obviously!
  * @context jqDock
  * @param object (element of elements array)
  * @param integer (image dimension)
  * @param object (dock orientation)
  * @return integer (other image dimension)
  */
      , keepProportion : function(el, dim, orient){
      //===========================================
          var r = {}
            , vh = this.verthorz[orient.vh] //convenience
            , inv = this.verthorz[orient.inv] //convenience
            ;
          r[vh.wh] = dim;
          r[inv.wh] = Math.round(dim * el[inv.wh] / el[vh.wh]);
          return r;
        } //end function keepProportion()

  /* deltaXY()
  *  =========
  * translates this.X or this.Y into an offset within div.jqDock
  * note: doing it this way means that all attenuation is against the inital (shrunken) image positions,
  * but it saves having to find every image's offset() each time the cursor moves or an image changes size!
  * @context jqDock
  * @param integer (dock index)
  */
      , deltaXY : function(id){
      //=======================
          var Dock = this.docks[id]; //convenience
          if(Dock.Current !== false){
            var op = Dock.Opts //convenience
              , el = Dock.Elem[Dock.Current] //convenience
              , p = el.Pad[op.vh.lead] + el.Pad[op.vh.trail] //element's user-specified padding
              , off = el.Img.offset()
              ;
            //get the difference between the cursor position and the leading edge of the current image,
            //multiply by the full/shrunken ratio, and add the element's pre-calculated offset within div.jqDock...
            Dock.Delta = Math.floor((this[op.vh.xy] - off[op.vh.tl]) * (p + el.Initial) / (p + el.Actual)) + el.Offset;
            this.doLabel(id, off);
          }
        } //end function deltaXY()

  /* setLabel()
  *  ==========
  * sets up the labels, storing each image's label dimensions
  * @context jqDock
  * @param integer (dock index)
  * @param object (menu element's label settings)
  */
      , setLabel : function(id, label){
      //===============================
          var Dock = this.docks[id] //convenience
            , ME = this
            , pad = {}
            ;
          if(!Dock.Label){ //create the div.jqDockLabel and hide it...
            Dock.Label = $('<div class="jqDockLabel jqDockMouse'+id+'_00 jqDockLabelImage" style="position:absolute;margin:0px;"></div>')
                           .hide().bind('click', function(){ Dock.Elem[Dock.Current].Img.trigger('click'); }).appendTo(Dock.Yard);
          }
          if(label.txt){
            //insert the label text for this image, and find any user-styled padding...
            Dock.Label.text(label.txt);
            $.each(['Top', 'Right', 'Bottom', 'Left'], function(n, v){
                pad[v] = ME.asNumber(Dock.Label.css('padding'+v));
              });
            //store the label dimensions for this image...
            $.each(this.verthorz, function(vh, o){
                label[o.wh] = Dock.Label[o.wh]();
                label[o.wh+'Pad'] = pad[o.lead] + pad[o.trail]; //hold padding separately
              });
          }
        } //end function setLabel()

  /* doLabel()
  *  =========
  * if labels enabled, performs the appropriate action
  * @context jqDock
  * @param integer (dock index)
  * @param string (what action to do) or object (top/left offset of an image)
  */
      , doLabel : function(id, off){
      //============================
          var Dock = this.docks[id]; //convenience
          if(Dock.Opts.labels && Dock.Current !== false){ //only if labels are set and we're over an image
            var el = Dock.Elem[Dock.Current] //convenience
              , L = el.Label //convenience
              , op = Dock.Opts //convenience
              , what = typeof off == 'string' ? off : 'move'
              ;
            switch(what){
              case 'show': case 'hide' : //show or hide...
                Dock.Label[L.txt?what:'hide']();
                break;
              case 'change': //change the label text and set the appropriate dimensions for the current image...
                Dock.Label[0].className = Dock.Label[0].className.replace(/(jqDockLabel)(Link|Image)/, '$1'+(el.Linked ? 'Link' : 'Image'));
                Dock.Label.text(L.txt).css({width:L.width, height:L.height}).hide();
                break;
              default: //move the label...
                //can't avoid extra processing here because we have to get the dock's offsets realtime since simply
                //expanding/shrinking a dock can make scroll bars appear/disappear and thereby affect the dock's position
                var doff = Dock.Yard.offset()
                  , css = { top: off.top - doff.top
                          , left: off.left - doff.left
                          }
                  , splt = op.labels.split('')
                  ;
                //note: if vertically or horizontally centred then centre is based on the IMAGE only
                //(ie without including padding), otherwise, positioning includes anyimage padding
                if(splt[0] == 'm'){
                  css.top += Math.floor((el[op.vh.inv.act] - L.height - L.heightPad) / 2);
                }else if(splt[0] == 'b'){
                  css.top += el[op.vh.inv.act] + el.Pad.Top + el.Pad.Bottom - L.height - L.heightPad;
                }
                if(splt[1] == 'c'){
                  css.left += Math.floor((el[op.vh.act] - L.width - L.widthPad) / 2);
                }else if(splt[1] == 'r'){
                  css.left += el[op.vh.act] + el.Pad.Left + el.Pad.Right - L.width - L.widthPad;
                }
                Dock.Label.css(css);
            }
          }
        } //end function doLabel()

  /* mouseHandler()
  *  ==============
  * handler for all bound mouse events (move/over/out)
  * note: this handles both image and label events
  * note: when moving within a label Opera reports both a mousemove and a mouseover (presumably because the label has been moved?), but the mouseover does not have a relatedTarget!
  * @context jqDock
  * @param object (event)
  * @return null or false
  */
      , mouseHandler : function(e){
      //===========================
          var r = null
            , t = e.target.className.match(/jqDockMouse(\d+)_(\d+)/)
              //on a mouseout from an image onto a label, Opera reports relatedTarget as existing, but with tagName and className as 'undefined'!...
            , rt = !!(e.relatedTarget) && e.relatedTarget.tagName !== undefined
            ;
          if(t){
            r = false; //prevent the event going any further
            var id = 1*t[1] //convenience
              , Dock = this.docks[id] //convenience
              , idx = t[2] == '00' ? Dock.Current : 1*t[2] //note: label events have _00 suffix on the class name
              ;
            this.X = e.pageX;
            this.Y = e.pageY;
            if(e.type == 'mousemove'){
              if(idx == Dock.Current){ //precedence to mouseover/out processing...
                this.deltaXY(id);
                if(Dock.OnDock && Dock.Expanded){
                  this.setSizes(id);
                  this.factorSizes(id);
                }
              }
            }else{
              var rel = rt && e.relatedTarget.className.match(/jqDockMouse(\d+)_(\d+)/);
              //only do something on a mouseover if the current menu element has changed...
              if(e.type == 'mouseover' && (!Dock.OnDock || idx !== Dock.Current)){
                Dock.Current = idx;
                this.doLabel(id, 'change');
                this.deltaXY(id);
                if(Dock.Expanded){
                  this.doLabel(id, 'show');
                }
                if(rt && (!rel || rel[1] != id)){ //came from outside this menu...
                  Dock.Timestamp = (new Date()).getTime();
                  this.setSizes(id);
                  Dock.OnDock = true;
                  this.overDock(id); //sets Expanded when complete
                }
              //only do something on a mouseout if we can tell where we are mousing out to...
              }else if(rt && e.type == 'mouseout'){
                if(!rel || rel[1] != id){ //going outside this menu...
                  Dock.OnDock = false;
                  this.doLabel(id, 'hide');
                  //reset Final dims, per element, to the original (shrunken)...
                  var i = Dock.Elem.length;
                  while((i--)){
                    Dock.Elem[i].Final = Dock.Elem[i].Intial;
                  }
                  this.offDock(id); //clears Expanded and Current when complete
                }
              }
            }
          }
          return r;
        } //end function mouseHandler()

  /* overDock()
  *  ==========
  * checks for completed expansion (if OnDock)
  * if not completed, runs setSizes(), factorSizes(), and then itself on a 60ms timer
  * @context jqDock
  * @param integer (dock index)
  */
      , overDock : function(id){
      //========================
          var Dock = this.docks[id]; //convenience
          if(Dock.OnDock){
            var ME = this
              , el = Dock.Elem //convenience
              , i = el.length
              ;
            while((i--) && !(el[i].Actual < el[i].Final)){}
            if(i < 0){ //complete
              Dock.Expanded = true;
              this.deltaXY(id);
              this.doLabel(id, 'show');
            }else{
              this.setSizes(id);
              this.factorSizes(id);
              setTimeout(function(){ ME.overDock(id); }, 60);
            }
          }
        } //end function overDock()

  /* offDock()
  *  =========
  * called when cursor goes outside menu, and checks for completed shrinking of all menu elements
  * calls changeSize() on any menu element that has not finished shrinking
  * calls itself on a timer to complete the shrinkage
  * @context jqDock
  * @param integer (dock index)
  */
      , offDock : function(id){
      //=======================
          var Dock = this.docks[id]; //convenience
          if(!Dock.OnDock){
            var ME = this
              , done = true
              , i = Dock.Elem.length
              , el, sz
              ;
            while(i){
              el = Dock.Elem[--i];
              if(el.Actual > el.Initial){
                sz = el.Actual - el.ShrinkStep;
                if(sz > el.Initial){
                  done = false;
                }else{
                  sz = el.Initial;
                }
                this.changeSize(id, i, sz);
              }
            }
            //this is here for no other reason than that Opera leaves a 'shadow' residue of the expanded image unless/until Delta is recalculated!...
            this.deltaXY(id);
            if(done){
              //reset everything back to 'at rest' state...
              while(i < Dock.Elem.length){
                el = Dock.Elem[i++];
                el.Actual = el.Final = el.Initial;
              }
              Dock.Current = Dock.Expanded = false;
            }else{
              setTimeout(function(){ ME.offDock(id); }, this.shrinkInterval);
            }
          }
        } //end function offDock()

  /* setSizes()
  *  ==========
  * calculates the image sizes according to the current (translated) position of the cursor within div.jqDock
  * result stored in Final for each menu element
  * @context jqDock
  * @param integer (dock index)
  * @param integer (translated cursor offset in main axis)
  */
      , setSizes : function(id, mxy){
      //=============================
          var Dock = this.docks[id] //convenience
            , op = Dock.Opts //convenience
            , i = Dock.Elem.length
            , el, sz
            ;
          mxy = mxy || Dock.Delta; //if not forced, use current translated cursor position (main axis)
          while(i){
            el = Dock.Elem[--i];
            //if we're within the attenuation distance then sz will be less than the difference between the max and min dims
            //if we're smack on or beyond the attenuation distance then set to the min dim
            //note: set sz to an integer number, otherwise we could end up 'fluttering'
            sz = Math.floor(el.SizeDiff * Math.pow(Math.abs(mxy - el.Centre), op.coefficient) / op.attenuation);
            el.Final = (sz < el.SizeDiff ? el[op.vh.wh] - sz : el.Initial);
          }
        } //end function setSizes()

  /* factorSizes()
  *  =============
  * modifies the target sizes in proportion to 'duration' if still within the 'duration' period following a mouseover
  * calls changeSize() for each menu element (if more than 60ms since mouseover)
  * @context jqDock
  * @param integer (dock index)
  */
      , factorSizes : function(id){
      //===========================
          var Dock = this.docks[id] //convenience
            , op = Dock.Opts //convenience
            , lapse = op.duration + 60
            ;
          if(Dock.Timestamp){
            lapse = (new Date()).getTime() - Dock.Timestamp;
            //Timestamp only gets set on mouseover (onto menu) so there's no point continually checking Date once op.duration has passed...
            if(lapse >= op.duration){
              Dock.Timestamp = 0;
            }
          }
          if(lapse > 60){ //only if more than 60ms have passed since last mouseover
            var f = lapse < op.duration ? lapse / op.duration : 0
              , i = 0 //must go through the elements if logical order
              , el
              ;
            while(i < Dock.Elem.length){
              el = Dock.Elem[i];
              this.changeSize(id, i++, (f ? Math.floor(el.Initial + ((el.Final - el.Initial) * f)) : el.Final));
            }
          }
        } //end function factorSizes()

  /* changeSize()
  *  ============
  * sets the css for an individual image to effect its change in size
  * 'dim' is the new value for the main axis dimension as specified in Opts.vh.wh, so
  * the margin needs to be applied to the inverse of Opts.vh.wh!
  * note: 'force' is only set when called from initDock() to do the initial shrink
  * @context jqDock
  * @param integer (dock index)
  * @param integer (image index)
  * @param integer (main axis dimension of image)
  * @param boolean
  */
      , changeSize : function(id, idx, dim, force){
      //===========================================
          var Dock = this.docks[id] //convenience
            , el = Dock.Elem[idx] //convenience
            ;
          if(force || el.Actual != dim){
            var op = Dock.Opts //convenience
              //vertical menus, or IE in quirks mode, require border widths (if any) of the Dock to be added to the Dock's main axis dimension...
              , bdr = ($.boxModel || op.orient.vh == 'v') ? 0 : Dock.Borders[op.vh.lead] + Dock.Borders[op.vh.trail]
              ;
            //switch image source to large, if (a) it's different to small source, and (b) this is the first step of an expansion...
            if(el.Source[2] && !force && el.Actual == el.Initial){
              el.Img[0].src = el.Source[1];
            }
            if(Dock.OnDock){
              this.deltaXY(id); //recalculate deltaXY
            }
            Dock.Spread += dim - el.Actual; //adjust main axis dimension of dock
            var css = this.keepProportion(el, dim, op.orient)
              , diff = op.size - css[op.vh.inv.wh]
              , m = 'margin' //convenience
              , z = op.vh.inv //convenience
              ;
            //add minor axis margins according to alignment...
            //note: where diff is an odd number of pixels, for 'middle' or 'center' alignment put the odd pixel in the 'lead' margin
            switch(op.align){
              case 'bottom': case 'right' : css[m+z.lead] = diff; break;
              case 'middle': case 'center' : css[m+z.lead] = (diff + diff%2) / 2; css[m+z.trail] = (diff - diff%2) / 2; break;
              case 'top': case 'left': css[m+z.trail] = diff; break;
              default:
            }
            //set dock's main axis dimension...
            Dock.Yard[op.vh.wh](Dock.Spread + bdr);
            //change image size and margins...
            el.Img.css(css);
            //set dock's main axis 'lead' margin (v1.2: make sure that margin doesn't go negative!)...
            Dock.Yard.css('margin'+op.vh.lead, Math.floor(Math.max(0, (Dock[op.vh.wh] - Dock.Spread) / 2)));
            //store new dimensions...
            el.Actual = dim; //main axis
            el.ActualInv = css[op.vh.inv.wh]; //minor axis
            //switch image source to small, if (a) it's different to large source, and (b) this was the last step of a shrink...
            if(el.Source[2] && !force && el.Actual == el.Initial){
              el.Img[0].src = el.Source[0];
            }
          }
        } //end function changeSize()
      }; //end of return object
    }(); //run the function to set up jqDock

  /***************************************************************************************************
  *  jQuery.fn.jqDock()
  *  ==================
  * usage:    $(selector).jqDock(options);
  * options:  see jqDock.defaults (top of script)
  *
  * note: the aim is to do as little processing as possible after setup, because everything is
  * driven from the mousemove/over/out events and I don't want to kill the browser if I can help it!
  * hence the code below, and in jqDock.initDock(), sets up and stores everything it possibly can
  * which will avoid extra processing at runtime, and hopefully give as smooth animation as possible.
  ***************************************************************************************************/
  $.fn.jqDock = function(opts){
    return this.filter(function(){
        //check not already set up and has images...
        var i = jqDock.docks.length;
        while((i--) && this != jqDock.docks[i].Menu){}
        return (i < 0) && ($('img', this).length);
      }).hide() //hide it/them
      .each(function(){
        //add an object to the docks array for this new dock...
        var id = jqDock.docks.length;
        jqDock.docks[id] = { Elem : [] // an object per img menu option
                           , Menu : this //original containing element
                           , OnDock : false //indicates cursor over menu and initial sizes set
                           , Expanded : false //indicates completion of initial menu element expansions
                           , Timestamp : 0 //set on mouseover and used (within opts.duration) to proportion the menu element sizes
                           , width : 0 //width of div.jqDock container
                           , height : 0 //height of div.jqDock container
                           , Spread : 0 //main axis dimension (horizontal = width, vertical = height)
                           , Borders : {} //border widths (main axis) on div.jqDock
                           , Yard : false //jQuery of div.jqDock
                           , Opts : $.extend({}, jqDock.defaults, opts||{}) //options
                           , Current : false //current image index
                           , Delta : 0 //X or Y translated into horizontal or vertical offset within div.jqDock as if all images were unexpanded
                           , Loaded : 0 //count of images loaded
                           , Label : false //jQuery of label container (if Opts.labels is set)
                           };
        var Dock = jqDock.docks[id] //convenience
          , op = Dock.Opts //convenience
          ;
        //set up some extra Opts now, just to save some computing power later...
        op.attenuation = Math.pow(op.distance, op.coefficient); //straightforward, static calculation
        op.orient = ({left:1, center:1, right:1}[op.align]) ? {vh:'v', inv:'h'} : {vh:'h', inv:'v'}; //orientation based on 'align' option
        op.vh = $.extend({}, jqDock.verthorz[op.orient.vh], {inv:jqDock.verthorz[op.orient.inv]}); //main and minor axis internals
        op.loader = (op.loader) && typeof op.loader == 'string' && /^image|jquery$/i.test(op.loader) ? op.loader.toLowerCase() : ''; //image loader override
        op.labels = op.labels === true ? {top:'bc',left:'tr',right:'tl'}[op.align] || 'tc' : (typeof op.labels == 'string' && {tl:1,tc:1,tr:1,ml:1,mc:1,mr:1,bl:1,bc:1,br:1}[op.labels] ? op.labels : false);

        $('img', this).each(function(n){
            //add an object to the dock's elements array for each image...
            var me = $(this)
              , s0 = me.attr('src') //'small' image source
              , s1 = (op.source ? op.source.call(me[0], n) : false) || jqDock.altImage.call(this) || s0 //'large' image source?
              , tx = op.labels ? me.attr('title') || me.parent('a').attr('title') || '' : '' //label text?
              ;
            Dock.Elem[n] = { Img : me //jQuery of img element
                           , Source : [ s0, s1, !(s0 == s1) ] //array : [ small image path, large image path, different? ]
                           , Label : { txt: tx, width: 0, height: 0, widthPad: 0, heightPad: 0 } //label text, dimensions, user-applied padding
                           , Initial : 0 //width/height when fully shrunk; it's important to note that this is not necessarily the same as Opts.size!
                           , Actual : 0 //transitory width/height (main axis)
                           , ActualInv : 0 //transitory width/height (minor axis)
                           , Final : 0 //target width/height
                           , Offset : 0 //offset of 'lead' edge of the image within div.jqDock (including user-padding)
                           , Centre : 0 //'Offset' + 'lead' user-padding + half 'Initial' dimension
                           , Pad : {} //user-applied padding, set up below
                           , Linked : !!me.parent('a').length //image-within-link or not
                           , width : 0 //original width of img element (the one that expands)
                           , height : 0 //original height of img element (the one that expands)
                           };
            $.each(['Top', 'Right', 'Bottom', 'Left'], function(i, v){
                Dock.Elem[n].Pad[v] = jqDock.asNumber(me.css('padding'+v));
              });
          });
        //we have to run a 'loader' function for the images because the expanding image
        //may not be part of the current DOM. what this means though, is that if you
        //have a missing image in your dock, the entire dock will not be displayed!
        //however I've had a few problems with certain browsers: for instance, IE does
        //not like the jQuery method; and Opera was causing me problems with the native
        //method when reloading the page; I've also heard rumours that Safari 2 might cope better with
        //the jQuery method, but I cannot confirm since I no longer have Safari 2.
        //
        //anyway, I'm providing both methods. if anyone finds it doesn't work, try
        //overriding with option.loader, and/or changing jqDock.useJqLoader for the 
        //browser in question and let me know if that solves it.
        var jqld = (!op.loader && jqDock.useJqLoader) || op.loader == 'jquery';
        $.each(Dock.Elem, function(i){
            var me = this
              , iLoaded = function(){
                  //store 'large' width and height...
                  me.height = this.height;
                  me.width = this.width;
                  if(++Dock.Loaded >= Dock.Elem.length){ //check to see if all images are loaded...
                    setTimeout(function(){ jqDock.initDock(id); }, 0);
                  }
                }
              ;
            if(jqld){ //jQuery method...
              $('<img />').bind('load', iLoaded).attr({src:this.Source[1]});
            }else{ //native 'new Image()' method...
              var pre = new Image();
              pre.onload = function(){
                  iLoaded.call(this);
                  pre.onload = function(){}; //wipe out this onload function
                };
              pre.src = this.Source[1];
            }
          });
      })
      .end(); //revert the filter to maintain chaining
  }; //end jQuery.fn.jqDock()

  /***************************************************************************************************
  *  jQuery.jqDock()
  *  ===============
  * usage:    $.jqDock(property);
  * returns:  the jqDock object's property, or null
  * example:  var vsn = $.jqDock('version');
  ***************************************************************************************************/
  $.jqDock = function(x){
    return jqDock[x] ? jqDock[x] : null;
  }; //end jQuery.jqDock()
} //end of if()
})(jQuery);

