   function init()
// This function does the initial canvas sizing, preloads the fullsize images
// and registers the javascript event handlers.
      {
       opth = canvasheight;
       optw = canvaswidth;

       calcCanvasDims(opth, optw);
       preloadFullsize();

       for (id = 1; id < 21; id++)
          {
           thumb = document.getElementById('thumb_' + id);

           if (thumb)
              {
               thumb.href        = '';
               thumb.onclick     = new Function('return animationControl(' + id + ', \'start\');');
               thumb.onmouseover = function() {setOutline(this, 'on');};
               thumb.onmouseout  = function() {setOutline(this, 'off');};
              }
          }

       sayings = document.getElementById('testimonials');

       if (sayings)
          {
           sayings.href        = '';
           sayings.onclick     = new Function('return animationControl(' + 0 + ', \'start\');');
           sayings.onmouseover = function() {setOutline(this, 'on');};
           sayings.onmouseout  = function() {setOutline(this, 'off');};
          }

       document.onkeydown = function() {return doNothing(true);};
       window.onresize    = function() {calcCanvasDims(opth, optw);};
      }
//=============================================================================
   function calcCanvasDims(opth, optw)
// This function resizes the canvas.
// 786 = 34 (top of canvas) + 675 (full canvas height)  + 5 (margin to navbar)
//                          + 67 (5 line navbar height) + 5 (margin to bottom)
// 567 = viewport height in Win XP IE8 @ 1024 x 768
// 456 = 567 - 111
// 111 = 34 + 5 + 67 + 5 (the non-canvas portion of 786)
      {
       canvasheight = opth;
       canvaswidth  = optw;

       viewporth = window.innerHeight || document.documentElement.clientHeight || document.getElementsByTagName('body')[0].clientHeight;

       if (viewporth < 786)
          {
           canvasheight = (viewporth <= 567) ? 456 : viewporth - 111;
           canvaswidth  = (900 * canvasheight) / 675;
          }
      }
//=============================================================================
   function preloadFullsize()
// This function preloads the fullsize portfolio images.
      {
       for (i in portfolio)
          {
           if (portfolio[i][0] == 'link')
              {
               preloadimageobject     = new Image();
               preloadimageobject.src = fullpath + portfolio[i][2];
              }
          }
// And the fullsize testimonials image.
       preloadimageobject     = new Image();
       preloadimageobject.src = basepath + 'quotes_fullsize.jpg';
      }
//=============================================================================
   function animationControl(id, action)
// This function controls the overall portfolio animation and navigation
// sequence.
      {
// We can safely calculate the position of the portfolio canvas just once at
// the beginning - it is used by all subsequent action types.
       canvaspos  = findPos(document.getElementById('canvas'));
       canvasleft = canvaspos[0];
       canvastop  = canvaspos[1];

       if (action == 'start')
// The visitor has clicked on a portfolio thumbnail image - start the portfolio
// animation sequence:
// Put the page into the 'background', add a thumbnail-sized version of the
// selected portfolio item at the thumbnail postion, then grow it to its full
// size. Finally, add the navigation bar.
          {
           disablePage();

           addPic(id);

           growcomplete = 0;
           gcount       = 0;
           growinterval = setInterval(function() {growImage();}, delay);

           flowCheck(function() {return growcomplete == 1;},
                     function() {addNav(id);});
          }

       if (action == 'prev')
// The visitor has clicked on the 'previous' link in the navigation bar - show
// the previous portfolio item:
// Remove the navigation bar, shrink the portfolio item back to thumbnail size
// and remove it, calculate the previous item id, add it to the page then grow
// it to its full size. Finally, add the navigation bar.
          {
           removeNav();

           shrinkcomplete = 0;
           removecomplete = 0;
           addcomplete    = 0;
           growcomplete   = 0;
           scount         = 0;
           gcount         = 0;
           shrinkinterval = setInterval(function() {shrinkImage();}, delay);

           flowCheck(function() {return shrinkcomplete == 1;},
                     function() {removePic();});

           id = calculateNewId(id, action);

           flowCheck(function() {return removecomplete == 1;},
                     function() {addPic(id);});

           flowCheck(function() {return addcomplete == 1;},
                     function() {growinterval = setInterval(function() {growImage();}, delay);});

           flowCheck(function() {return growcomplete == 1;},
                     function() {addNav(id);});
          }

       if (action == 'next')
// The visitor has clicked on the 'next' link in the navigation bar - show the
// next portfolio item:
// Remove the navigation bar, shrink the portfolio item back to thumbnail size
// and remove it, calculate the next item id, add it to the page then grow it
// to its full size. Finally, add the navigation bar.
          {
           removeNav();

           shrinkcomplete = 0;
           removecomplete = 0;
           addcomplete    = 0;
           growcomplete   = 0;
           scount         = 0;
           gcount         = 0;
           shrinkinterval = setInterval(function() {shrinkImage();}, delay);

           flowCheck(function() {return shrinkcomplete == 1;},
                     function() {removePic();});

           id = calculateNewId(id, action);

           flowCheck(function() {return removecomplete == 1;},
                     function() {addPic(id);});

           flowCheck(function() {return addcomplete == 1;},
                     function() {growinterval = setInterval(function() {growImage();}, delay);});

           flowCheck(function() {return growcomplete == 1;},
                     function() {addNav(id);});
          }

       if (action == 'exit')
// The visitor has clicked on the 'exit' link in the navigation bar - end the
// portfolio animation sequence:
// Remove the navigation bar, shrink the portfolio item back to thumbnail size
// and remove it. Finally, bring the page back into the 'foreground'.
          {
           removeNav();

           shrinkcomplete = 0;
           removecomplete = 0;
           scount         = 0;
           shrinkinterval = setInterval(function() {shrinkImage();}, delay);

           flowCheck(function() {return shrinkcomplete == 1;},
                     function() {removePic();});

           flowCheck(function() {return removecomplete == 1;},
                     function() {enablePage();});
          }

       return false;
      }
//=============================================================================
   function findPos(obj)
// Find the left and top positions of an object.
      {
       var curleft = curtop = 0;

       do
          {
	   curleft += obj.offsetLeft;
	   curtop  += obj.offsetTop;
	  } while (obj = obj.offsetParent);

       return [curleft, curtop];
      }
//=============================================================================
   function disablePage()
// This puts the portfolio page into the 'background':
// Reduce opacity, disable links so that they behave just like regular
// text/mages and disable mouseover/mouseout behaviour of the thumbnails.
// Some attributes are stored for restoration by enablePage().
      {
       setOpacity(document.getElementById('container'), 4);

       aobj = document.getElementsByTagName('a');
       for (i = 0; i < aobj.length; i++)
          {
           aobjhref[i]                  = aobj[i].href;
           aobjtitle[i]                 = aobj[i].title;
           aobjonclick[i]               = aobj[i].onclick;
           aobjclass[i]                 = aobj[i].className;
           aobjtextdec[i]               = aobj[i].style.textDecoration;

           aobj[i].href                 = '';
           aobj[i].title                = '';
           aobj[i].onclick              = function() {return doNothing(false);};
           aobj[i].className            = aobj[i].className + ' hoveroff'
           aobj[i].style.textDecoration = 'none';

           if ((aobj[i].id.substr(0, 5) == 'thumb') || (aobj[i].id == 'testimonials'))
              {
               aobj[i].style.cursor = 'default';
               aobj[i].onmouseover  = function() {return doNothing(false);};
               aobj[i].onmouseout   = function() {return doNothing(false);};
               setOutline(aobj[i], 'off');
              }
           else
              {
               aobj[i].style.cursor = 'text';
              }
          }
      }
//=============================================================================
   function setOutline(obj, value)
// This adds/removes an outline to/from an object.
      {
       if (value == 'on')
          {
           obj.style.outline = '3px solid #bc1c00';
          }

       if (value == 'off')
          {
           obj.style.outline = '0px solid #bc1c00';
          }
      }
//=============================================================================
   function setOpacity(obj, value)
// This changes the opacity of an object. The first form works for most
// browsers, the second form is for Internet Explorer.
      {
       obj.style.opacity = value / 10;
       obj.style.filter  = 'alpha(opacity=' + value * 10 + ')';
      }
//=============================================================================
   function doNothing(val)
// This is a dummy function used to neutralise various behaviours.
      {
       return val;
      }
//=============================================================================
   function addPic(id)
// We start with full picture position equal to thumb position and full picture
// dimensions equal to thumb dimensions.
// NB. Position is relative to canvas!
// NB. id = 0 is the 'What they say' feature!
// We also calculate position & dimension step changes.
      {
       if (id > 0)
          {
           thumbpos = findPos(document.getElementById('thumb_' + id));
          }
       else
          {
           thumbpos = findPos(document.getElementById('testimonials'));
          }

       thumbleft  = thumbpos[0];
       thumbtop   = thumbpos[1];
       fullleft   = thumbleft - canvasleft;
       fulltop    = thumbtop  - canvastop;
       fullwidth  = thumbwidth;
       fullheight = thumbheight;

       leftdelta   = fullleft                    / steps;
       topdelta    = fulltop                     / steps;
       widthdelta  = (canvaswidth  - fullwidth)  / steps;
       heightdelta = (canvasheight - fullheight) / steps;

       fullpic                = document.createElement('img');
       fullpic.id             = 'fullpic';
       
       if (id > 0)
          {
           fullpic.src = fullpath + portfolio[id][2];
           fullpic.alt = portfolio[id][3];
          }
       else
          {
           fullpic.src = basepath + 'quotes_fullsize.jpg';
           fullpic.alt = 'What they say';
          }

       fullpic.style.position = 'absolute';
       fullpic.style.left     = fullleft   + 'px';
       fullpic.style.top      = fulltop    + 'px';
       fullpic.style.width    = fullwidth  + 'px';
       fullpic.style.height   = fullheight + 'px';
       fullpic.style.zIndex   = 100;
       document.getElementById('canvas').appendChild(fullpic);

       addcomplete = 1;
      }
//=============================================================================
   function growImage()
// 'Grow' the full size image from thumbnail size to canvas size and adjust the
// left and top positions to match the canvas.
// NB. fullleft & fulltop are relative to the canvas.
      {
       gcount++;
       end = 0;

       if ((fullleft -= (leftdelta * gcount)) <= 0)
          {
           fullleft = 0;
           end++; 
          }

       if ((fullwidth += (widthdelta * gcount)) >= canvaswidth)
          {
           fullwidth = canvaswidth;
           end++;
          }

       if ((fulltop -= (topdelta * gcount)) <= 0)
          {
           fulltop = 0;
           end++;
          }

       if ((fullheight += (heightdelta * gcount)) >= canvasheight)
          {
           fullheight = canvasheight;
           end++;
          }

       fullpic.style.left   = fullleft   + 'px';
       fullpic.style.width  = fullwidth  + 'px';
       fullpic.style.top    = fulltop    + 'px';
       fullpic.style.height = fullheight + 'px';

       if (end == 4)
          {
           clearInterval(growinterval);
           growcomplete = 1;
          }
      }
//=============================================================================
   function flowCheck(checkProcess, nextProcess)
// This function ensures that individual processes in the animation sequence
// wait for the previous process to complete before they are executed. It 
// checks every 100 milliseconds. 
      {
       if (checkProcess())
          {
	   nextProcess();
	  }

       var flowInterval = setInterval(function()
          {
	   if (checkProcess())
              {
	       clearInterval(flowInterval);
	       nextProcess();
              }
          }, 100);
      }
//=============================================================================
   function addNav(id)
// Add the portfolio navigation bar at the bottom of the canvas.
// NB. id = 0 is the 'What they say' feature!
      {
       navbar                = document.createElement('div');
       navbar.id             = 'navbar';
       navbar.style.position = 'absolute';
       navbar.style.top      = canvasheight + 'px';
       navbar.style.width    = canvaswidth  + 'px';
       navbar.style.fontSize = '0.7em';
       navbar.style.zIndex   = 100;
       document.getElementById('canvas').appendChild(navbar);

// Adjust the size of the title/desc container to ensure a comfortable
// reading line length.
       captionwidth = (canvaswidth >= 640) ? 512 : (80 * canvaswidth) / 100;

// Add a container for the title/desc text.
       caption                  = document.createElement('p');
       caption.style.cssFloat   = 'left';
       caption.style.styleFloat = 'left';
       caption.style.width      = captionwidth + 'px';

// Add the title/desc text.
       titlespan                  = document.createElement('span');
       titlespan.style.color      = '#000000';
       caption.appendChild(titlespan);

       if (id > 0)
          {
           title = titlespan.appendChild(document.createTextNode(portfolio[id][3]));
           br    = caption.appendChild(document.createElement('br'));
           desc  = caption.appendChild(document.createTextNode(portfolio[id][4]));
          }
       else
          {
           title = titlespan.appendChild(document.createTextNode('A few nice words from some of our clients'));
          }

       navbar.appendChild(caption);

// Add a container for the website/prev/next/close links.
       navcontainer                  = document.createElement('div');
       navcontainer.style.cssFloat   = 'left';
       navcontainer.style.styleFloat = 'left';
       navcontainer.style.width      = (canvaswidth - captionwidth) + 'px';
       navcontainer.style.textAlign  = 'right';
       navbar.appendChild(navcontainer);

// Add a client website link if available.
       if ((id > 0) && (portfolio[id][5]))
          {
           clientlink                     = document.createElement('a');
           clientlink.href                = portfolio[id][5];
           clientlink.title               = 'Visit ' + portfolio[id][5];
           clientlink.target              = '_blank';
           clientlink.style.verticalAlign = '40%';
           clientlink.style.margin        = '0 20px 0 0';
           clientlink.appendChild(document.createTextNode('website'));
           navcontainer.appendChild(clientlink);
          }

// Don't need previous / next for What they say.
       if (id > 0)
          {
// Add a previous item icon/link (just a faded icon if this is the 1st item).
           previmg               = document.createElement('img');
           previmg.src           = basepath + 'previous.gif';
           previmg.width         = 11;
           previmg.height        = 16;
           previmg.alt           = 'Go to the previous item icon';
           previmg.style.margin  = '0 10px 0 0';

           if (id > minid)
              {
               prevlink              = document.createElement('a');
               prevlink.href         = '';
               prevlink.title        = 'Go to the previous item';
               prevlink.onclick      = new Function('return animationControl(' + id + ', \'prev\');');
               previmg.onmouseover   = function() {setOpacity(this, 10);};   
               previmg.onmouseout    = function() {setOpacity(this, 8);};
               setOpacity(previmg, 8);
               prevlink.appendChild(previmg);
               navcontainer.appendChild(prevlink);
              }
           else
              {
               setOpacity(previmg, 2);
               navcontainer.appendChild(previmg);
              }

// Add a next item icon/link (just a faded icon if this is the last item).
           nextimg               = document.createElement('img');
           nextimg.src           = basepath + 'next.gif';
           nextimg.width         = 11;
           nextimg.height        = 16;
           nextimg.alt           = 'Go to the Next item icon';
           nextimg.style.margin  = '0 10px 0 0';

           if (id < maxid)
              {
               nextlink              = document.createElement('a');
               nextlink.href         = '';
               nextlink.title        = 'Go to the next item';
               nextlink.onclick      = new Function('return animationControl(' + id + ', \'next\');');
               nextimg.onmouseover   = function() {setOpacity(this, 10);};   
               nextimg.onmouseout    = function() {setOpacity(this, 8);};
               setOpacity(nextimg, 8);
               nextlink.appendChild(nextimg);
               navcontainer.appendChild(nextlink);
              }
           else
              {
               setOpacity(nextimg, 2);
               navcontainer.appendChild(nextimg);
              }
          }

// Add an exit icon/link.
       exitlink              = document.createElement('a');
       exitlink.href         = '';
       exitlink.title        = 'Close the slide show';
       exitlink.onclick      = new Function('return animationControl(' + id + ', \'exit\');');
       exitimg               = document.createElement('img');
       exitimg.src           = basepath + 'close.gif';
       exitimg.width         = 16;
       exitimg.height        = 16;
       exitimg.alt           = 'Close the slide show icon';
       exitimg.onmouseover   = function() {setOpacity(this, 10);};   
       exitimg.onmouseout    = function() {setOpacity(this, 8);};
       setOpacity(exitimg, 8);
       exitlink.appendChild(exitimg);
       navcontainer.appendChild(exitlink);

// Set up the event handler for keyboard navigation.
       document.onkeydown = function(e) {return keyAction(e, id);};
      }
//=============================================================================
   function keyAction(e, id)
// Handle the navigation keys.
      {
       keynum = 0;

       if (window.event) // IE
          {
           keynum = window.event.keyCode
          }
       else if (e.which) // The rest
         {
          keynum = e.which
         }

       if (id > 0)
          {
           if ((keynum == 37) && (id > minid)) // Left arrow
              {
               animationControl(id, 'prev');
               return false;
              }

           if ((keynum == 39) && (id < maxid)) // Right arrow
              {
               animationControl(id, 'next');
               return false;
              }
          }

       if (keynum == 27) // ESC
          {
           animationControl(id, 'exit');
          }

       return true;
      }
//=============================================================================
   function removeNav()
// Remove the navigation bar.
      {
       document.getElementById('canvas').removeChild(document.getElementById('navbar'));

// Reset the event handler for keyboard navigation.
       document.onkeydown = function() {return doNothing(true);};
      }
//=============================================================================
   function shrinkImage()
// 'Shrink' the full size image from canvas size to thumbnail size and adjust
// the left and top positions to match the thumbnail.
// NB. fullleft & fulltop are relative to the canvas.
      {
       scount++;
       end = 0;

       if ((fullleft += (leftdelta * scount)) >= (thumbleft - canvasleft))
          {
           fullleft = thumbleft - canvasleft;
           end++; 
          }

       if ((fullwidth -= (widthdelta * scount)) <= thumbwidth)
          {
           fullwidth = thumbwidth;
           end++;
          }

       if ((fulltop += (topdelta * scount)) >= (thumbtop - canvastop))
          {
           fulltop = thumbtop - canvastop;
           end++;
          }

       if ((fullheight -= (heightdelta * scount)) <= thumbheight)
          {
           fullheight = thumbheight;
           end++;
          }

       fullpic.style.height = fullheight + 'px';
       fullpic.style.top    = fulltop    + 'px';
       fullpic.style.width  = fullwidth  + 'px';
       fullpic.style.left   = fullleft   + 'px';

       if (end == 4)
          {
           clearInterval(shrinkinterval);
           shrinkcomplete = 1;
          }
      }
//=============================================================================
   function removePic()
// Remove the full size picture.
      {
       document.getElementById('canvas').removeChild(document.getElementById('fullpic'));

       removecomplete = 1;
      }
//=============================================================================
   function calculateNewId(id, action)
// Calculate the prev/next portfolio id.
      {
       if (action == 'prev')
          {
           for (i = id - 1; i >= minid; i--)
              {
               if ((portfolio[i]) && (portfolio[i][0] == 'link'))
                  {
                   id = i;
                   break;
                  }
              }
          }

       if (action == 'next')
          {
           for (i = id + 1; i <= maxid; i++)
              {
               if ((portfolio[i]) && (portfolio[i][0] == 'link'))
                  {
                   id = i;
                   break;
                  }
              }
          }

       return id;
      }
//=============================================================================
   function enablePage()
// This restores the portfolio page back into the 'foreground':
// Restore link behaviours and increase the page opacity.
// Some attributes were stored by disablePage().
      {
       aobj = document.getElementsByTagName('a');
       for (i = 0; i < aobj.length; i++)
          {
           aobj[i].href                 = aobjhref[i];
           aobj[i].title                = aobjtitle[i];
           aobj[i].onclick              = aobjonclick[i];
           aobj[i].style.textDecoration = aobjtextdec[i];
           aobj[i].className            = aobjclass[i];
           aobj[i].style.cursor         = 'pointer';

           if ((aobj[i].id.substr(0, 5) == 'thumb') || (aobj[i].id == 'testimonials'))
              {
               aobj[i].onmouseover = function() {setOutline(this, 'on');};
               aobj[i].onmouseout  = function() {setOutline(this, 'off');};
              }
          }

       setOpacity(document.getElementById('container'), 10);
       removeFilter(document.getElementById('container'));
      }
//=============================================================================
   function removeFilter(obj)
// This removes the opacity filter to correct ClearType problems in IE.
      {
       if (obj.style.filter && obj.style.removeAttribute)
          {
           obj.style.removeAttribute('filter');
          }
      }

