]> mj.ucw.cz Git - gallery.git/blob - gal2/highslide/highslide.js
Gallery2: Experiments with HighSlide
[gallery.git] / gal2 / highslide / highslide.js
1 /** 
2  * Name:    Highslide JS
3  * Version: 4.1.13 (2011-10-06)
4  * Config:  default
5  * Author:  Torstein Hønsi
6  * Support: www.highslide.com/support
7  * License: www.highslide.com/#license
8  */
9 if (!hs) { var hs = {
10 // Language strings
11 lang : {
12         cssDirection: 'ltr',
13         loadingText : 'Loading...',
14         loadingTitle : 'Click to cancel',
15         focusTitle : 'Click to bring to front',
16         fullExpandTitle : 'Expand to actual size (f)',
17         creditsText : 'Powered by <i>Highslide JS</i>',
18         creditsTitle : 'Go to the Highslide JS homepage',
19         restoreTitle : 'Click to close image, click and drag to move. Use arrow keys for next and previous.'
20 },
21 // See http://highslide.com/ref for examples of settings  
22 graphicsDir : 'highslide/graphics/',
23 expandCursor : 'zoomin.cur', // null disables
24 restoreCursor : 'zoomout.cur', // null disables
25 expandDuration : 250, // milliseconds
26 restoreDuration : 250,
27 marginLeft : 15,
28 marginRight : 15,
29 marginTop : 15,
30 marginBottom : 15,
31 zIndexCounter : 1001, // adjust to other absolutely positioned elements
32 loadingOpacity : 0.75,
33 allowMultipleInstances: true,
34 numberOfImagesToPreload : 5,
35 outlineWhileAnimating : 2, // 0 = never, 1 = always, 2 = HTML only 
36 outlineStartOffset : 3, // ends at 10
37 padToMinWidth : false, // pad the popup width to make room for wide caption
38 fullExpandPosition : 'bottom right',
39 fullExpandOpacity : 1,
40 showCredits : true, // you can set this to false if you want
41 creditsHref : 'http://highslide.com/',
42 creditsTarget : '_self',
43 enableKeyListener : true,
44 openerTagNames : ['a'], // Add more to allow slideshow indexing
45
46 dragByHeading: true,
47 minWidth: 200,
48 minHeight: 200,
49 allowSizeReduction: true, // allow the image to reduce to fit client size. If false, this overrides minWidth and minHeight
50 outlineType : 'drop-shadow', // set null to disable outlines
51 // END OF YOUR SETTINGS
52
53
54 // declare internal properties
55 preloadTheseImages : [],
56 continuePreloading: true,
57 expanders : [],
58 overrides : [
59         'allowSizeReduction',
60         'useBox',
61         'outlineType',
62         'outlineWhileAnimating',
63         'captionId',
64         'captionText',
65         'captionEval',
66         'captionOverlay',
67         'headingId',
68         'headingText',
69         'headingEval',
70         'headingOverlay',
71         'creditsPosition',
72         'dragByHeading',
73         
74         'width',
75         'height',
76         
77         'wrapperClassName',
78         'minWidth',
79         'minHeight',
80         'maxWidth',
81         'maxHeight',
82         'pageOrigin',
83         'slideshowGroup',
84         'easing',
85         'easingClose',
86         'fadeInOut',
87         'src'
88 ],
89 overlays : [],
90 idCounter : 0,
91 oPos : {
92         x: ['leftpanel', 'left', 'center', 'right', 'rightpanel'],
93         y: ['above', 'top', 'middle', 'bottom', 'below']
94 },
95 mouse: {},
96 headingOverlay: {},
97 captionOverlay: {},
98 timers : [],
99
100 pendingOutlines : {},
101 clones : {},
102 onReady: [],
103 uaVersion: /Trident\/4\.0/.test(navigator.userAgent) ? 8 :
104         parseFloat((navigator.userAgent.toLowerCase().match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]),
105 ie : (document.all && !window.opera),
106 //ie : navigator && /MSIE [678]/.test(navigator.userAgent), // ie9 compliant?
107 safari : /Safari/.test(navigator.userAgent),
108 geckoMac : /Macintosh.+rv:1\.[0-8].+Gecko/.test(navigator.userAgent),
109
110 $ : function (id) {
111         if (id) return document.getElementById(id);
112 },
113
114 push : function (arr, val) {
115         arr[arr.length] = val;
116 },
117
118 createElement : function (tag, attribs, styles, parent, nopad) {
119         var el = document.createElement(tag);
120         if (attribs) hs.extend(el, attribs);
121         if (nopad) hs.setStyles(el, {padding: 0, border: 'none', margin: 0});
122         if (styles) hs.setStyles(el, styles);
123         if (parent) parent.appendChild(el);     
124         return el;
125 },
126
127 extend : function (el, attribs) {
128         for (var x in attribs) el[x] = attribs[x];
129         return el;
130 },
131
132 setStyles : function (el, styles) {
133         for (var x in styles) {
134                 if (hs.ieLt9 && x == 'opacity') {
135                         if (styles[x] > 0.99) el.style.removeAttribute('filter');
136                         else el.style.filter = 'alpha(opacity='+ (styles[x] * 100) +')';
137                 }
138                 else el.style[x] = styles[x];           
139         }
140 },
141 animate: function(el, prop, opt) {
142         var start,
143                 end,
144                 unit;
145         if (typeof opt != 'object' || opt === null) {
146                 var args = arguments;
147                 opt = {
148                         duration: args[2],
149                         easing: args[3],
150                         complete: args[4]
151                 };
152         }
153         if (typeof opt.duration != 'number') opt.duration = 250;
154         opt.easing = Math[opt.easing] || Math.easeInQuad;
155         opt.curAnim = hs.extend({}, prop);
156         for (var name in prop) {
157                 var e = new hs.fx(el, opt , name );
158                 
159                 start = parseFloat(hs.css(el, name)) || 0;
160                 end = parseFloat(prop[name]);
161                 unit = name != 'opacity' ? 'px' : '';
162                 
163                 e.custom( start, end, unit );
164         }       
165 },
166 css: function(el, prop) {
167         if (el.style[prop]) {
168                 return el.style[prop];
169         } else if (document.defaultView) {
170                 return document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);
171
172         } else {
173                 if (prop == 'opacity') prop = 'filter';
174                 var val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b){ return b.toUpperCase(); })];
175                 if (prop == 'filter') 
176                         val = val.replace(/alpha\(opacity=([0-9]+)\)/, 
177                                 function (a, b) { return b / 100 });
178                 return val === '' ? 1 : val;
179         } 
180 },
181
182 getPageSize : function () {
183         var d = document, w = window, iebody = d.compatMode && d.compatMode != 'BackCompat' 
184                 ? d.documentElement : d.body,
185                 ieLt9 = hs.ie && (hs.uaVersion < 9 || typeof pageXOffset == 'undefined');
186         
187         var width = ieLt9 ? iebody.clientWidth : 
188                         (d.documentElement.clientWidth || self.innerWidth),
189                 height = ieLt9 ? iebody.clientHeight : self.innerHeight;
190         hs.page = {
191                 width: width,
192                 height: height,         
193                 scrollLeft: ieLt9 ? iebody.scrollLeft : pageXOffset,
194                 scrollTop: ieLt9 ? iebody.scrollTop : pageYOffset
195         };
196         return hs.page;
197 },
198
199 getPosition : function(el)      {
200         var p = { x: el.offsetLeft, y: el.offsetTop };
201         while (el.offsetParent) {
202                 el = el.offsetParent;
203                 p.x += el.offsetLeft;
204                 p.y += el.offsetTop;
205                 if (el != document.body && el != document.documentElement) {
206                         p.x -= el.scrollLeft;
207                         p.y -= el.scrollTop;
208                 }
209         }
210         return p;
211 },
212
213 expand : function(a, params, custom, type) {
214         if (!a) a = hs.createElement('a', null, { display: 'none' }, hs.container);
215         if (typeof a.getParams == 'function') return params;    
216         try {   
217                 new hs.Expander(a, params, custom);
218                 return false;
219         } catch (e) { return true; }
220 },
221
222
223 focusTopmost : function() {
224         var topZ = 0, 
225                 topmostKey = -1,
226                 expanders = hs.expanders,
227                 exp,
228                 zIndex;
229         for (var i = 0; i < expanders.length; i++) {
230                 exp = expanders[i];
231                 if (exp) {
232                         zIndex = exp.wrapper.style.zIndex;
233                         if (zIndex && zIndex > topZ) {
234                                 topZ = zIndex;                          
235                                 topmostKey = i;
236                         }
237                 }
238         }
239         if (topmostKey == -1) hs.focusKey = -1;
240         else expanders[topmostKey].focus();
241 },
242
243 getParam : function (a, param) {
244         a.getParams = a.onclick;
245         var p = a.getParams ? a.getParams() : null;
246         a.getParams = null;
247         
248         return (p && typeof p[param] != 'undefined') ? p[param] : 
249                 (typeof hs[param] != 'undefined' ? hs[param] : null);
250 },
251
252 getSrc : function (a) {
253         var src = hs.getParam(a, 'src');
254         if (src) return src;
255         return a.href;
256 },
257
258 getNode : function (id) {
259         var node = hs.$(id), clone = hs.clones[id], a = {};
260         if (!node && !clone) return null;
261         if (!clone) {
262                 clone = node.cloneNode(true);
263                 clone.id = '';
264                 hs.clones[id] = clone;
265                 return node;
266         } else {
267                 return clone.cloneNode(true);
268         }
269 },
270
271 discardElement : function(d) {
272         if (d) hs.garbageBin.appendChild(d);
273         hs.garbageBin.innerHTML = '';
274 },
275 transit : function (adj, exp) {
276         var last = exp || hs.getExpander();
277         exp = last;
278         if (hs.upcoming) return false;
279         else hs.last = last;
280         hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
281         try {
282                 hs.upcoming = adj;
283                 adj.onclick();          
284         } catch (e){
285                 hs.last = hs.upcoming = null;
286         }
287         try {
288                 exp.close();
289         } catch (e) {}
290         return false;
291 },
292
293 previousOrNext : function (el, op) {
294         var exp = hs.getExpander(el);
295         if (exp) return hs.transit(exp.getAdjacentAnchor(op), exp);
296         else return false;
297 },
298
299 previous : function (el) {
300         return hs.previousOrNext(el, -1);
301 },
302
303 next : function (el) {
304         return hs.previousOrNext(el, 1);        
305 },
306
307 keyHandler : function(e) {
308         if (!e) e = window.event;
309         if (!e.target) e.target = e.srcElement; // ie
310         if (typeof e.target.form != 'undefined') return true; // form element has focus
311         var exp = hs.getExpander();
312         
313         var op = null;
314         switch (e.keyCode) {
315                 case 70: // f
316                         if (exp) exp.doFullExpand();
317                         return true;
318                 case 32: // Space
319                 case 34: // Page Down
320                 case 39: // Arrow right
321                 case 40: // Arrow down
322                         op = 1;
323                         break;
324                 case 8:  // Backspace
325                 case 33: // Page Up
326                 case 37: // Arrow left
327                 case 38: // Arrow up
328                         op = -1;
329                         break;
330                 case 27: // Escape
331                 case 13: // Enter
332                         op = 0;
333         }
334         if (op !== null) {hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
335                 if (!hs.enableKeyListener) return true;
336                 
337                 if (e.preventDefault) e.preventDefault();
338         else e.returnValue = false;
339         if (exp) {
340                         if (op == 0) {
341                                 exp.close();
342                         } else {
343                                 hs.previousOrNext(exp.key, op);
344                         }
345                         return false;
346                 }
347         }
348         return true;
349 },
350
351
352 registerOverlay : function (overlay) {
353         hs.push(hs.overlays, hs.extend(overlay, { hsId: 'hsId'+ hs.idCounter++ } ));
354 },
355
356
357 getWrapperKey : function (element, expOnly) {
358         var el, re = /^highslide-wrapper-([0-9]+)$/;
359         // 1. look in open expanders
360         el = element;
361         while (el.parentNode)   {
362                 if (el.id && re.test(el.id)) return el.id.replace(re, "$1");
363                 el = el.parentNode;
364         }
365         // 2. look in thumbnail
366         if (!expOnly) {
367                 el = element;
368                 while (el.parentNode)   {
369                         if (el.tagName && hs.isHsAnchor(el)) {
370                                 for (var key = 0; key < hs.expanders.length; key++) {
371                                         var exp = hs.expanders[key];
372                                         if (exp && exp.a == el) return key;
373                                 }
374                         }
375                         el = el.parentNode;
376                 }
377         }
378         return null; 
379 },
380
381 getExpander : function (el, expOnly) {
382         if (typeof el == 'undefined') return hs.expanders[hs.focusKey] || null;
383         if (typeof el == 'number') return hs.expanders[el] || null;
384         if (typeof el == 'string') el = hs.$(el);
385         return hs.expanders[hs.getWrapperKey(el, expOnly)] || null;
386 },
387
388 isHsAnchor : function (a) {
389         return (a.onclick && a.onclick.toString().replace(/\s/g, ' ').match(/hs.(htmlE|e)xpand/));
390 },
391
392 reOrder : function () {
393         for (var i = 0; i < hs.expanders.length; i++)
394                 if (hs.expanders[i] && hs.expanders[i].isExpanded) hs.focusTopmost();
395 },
396
397 mouseClickHandler : function(e) 
398 {       
399         if (!e) e = window.event;
400         if (e.button > 1) return true;
401         if (!e.target) e.target = e.srcElement;
402         
403         var el = e.target;
404         while (el.parentNode
405                 && !(/highslide-(image|move|html|resize)/.test(el.className)))
406         {
407                 el = el.parentNode;
408         }
409         var exp = hs.getExpander(el);
410         if (exp && (exp.isClosing || !exp.isExpanded)) return true;
411                 
412         if (exp && e.type == 'mousedown') {
413                 if (e.target.form) return true;
414                 var match = el.className.match(/highslide-(image|move|resize)/);
415                 if (match) {
416                         hs.dragArgs = { 
417                                 exp: exp , 
418                                 type: match[1], 
419                                 left: exp.x.pos, 
420                                 width: exp.x.size, 
421                                 top: exp.y.pos, 
422                                 height: exp.y.size, 
423                                 clickX: e.clientX, 
424                                 clickY: e.clientY
425                         };
426                         
427                         
428                         hs.addEventListener(document, 'mousemove', hs.dragHandler);
429                         if (e.preventDefault) e.preventDefault(); // FF
430                         
431                         if (/highslide-(image|html)-blur/.test(exp.content.className)) {
432                                 exp.focus();
433                                 hs.hasFocused = true;
434                         }
435                         return false;
436                 }
437         } else if (e.type == 'mouseup') {
438                 
439                 hs.removeEventListener(document, 'mousemove', hs.dragHandler);
440                 
441                 if (hs.dragArgs) {
442                         if (hs.styleRestoreCursor && hs.dragArgs.type == 'image') 
443                                 hs.dragArgs.exp.content.style.cursor = hs.styleRestoreCursor;
444                         var hasDragged = hs.dragArgs.hasDragged;
445                         
446                         if (!hasDragged &&!hs.hasFocused && !/(move|resize)/.test(hs.dragArgs.type)) {
447                                 exp.close();
448                         } 
449                         else if (hasDragged || (!hasDragged && hs.hasHtmlExpanders)) {
450                                 hs.dragArgs.exp.doShowHide('hidden');
451                         }
452                         hs.hasFocused = false;
453                         hs.dragArgs = null;
454                 
455                 } else if (/highslide-image-blur/.test(el.className)) {
456                         el.style.cursor = hs.styleRestoreCursor;                
457                 }
458         }
459         return false;
460 },
461
462 dragHandler : function(e)
463 {
464         if (!hs.dragArgs) return true;
465         if (!e) e = window.event;
466         var a = hs.dragArgs, exp = a.exp;
467         
468         a.dX = e.clientX - a.clickX;
469         a.dY = e.clientY - a.clickY;    
470         
471         var distance = Math.sqrt(Math.pow(a.dX, 2) + Math.pow(a.dY, 2));
472         if (!a.hasDragged) a.hasDragged = (a.type != 'image' && distance > 0)
473                 || (distance > (hs.dragSensitivity || 5));
474         
475         if (a.hasDragged && e.clientX > 5 && e.clientY > 5) {
476                 
477                 if (a.type == 'resize') exp.resize(a);
478                 else {
479                         exp.moveTo(a.left + a.dX, a.top + a.dY);
480                         if (a.type == 'image') exp.content.style.cursor = 'move';
481                 }
482         }
483         return false;
484 },
485
486 wrapperMouseHandler : function (e) {
487         try {
488                 if (!e) e = window.event;
489                 var over = /mouseover/i.test(e.type); 
490                 if (!e.target) e.target = e.srcElement; // ie
491                 if (!e.relatedTarget) e.relatedTarget = 
492                         over ? e.fromElement : e.toElement; // ie
493                 var exp = hs.getExpander(e.target);
494                 if (!exp.isExpanded) return;
495                 if (!exp || !e.relatedTarget || hs.getExpander(e.relatedTarget, true) == exp 
496                         || hs.dragArgs) return;
497                 for (var i = 0; i < exp.overlays.length; i++) (function() {
498                         var o = hs.$('hsId'+ exp.overlays[i]);
499                         if (o && o.hideOnMouseOut) {
500                                 if (over) hs.setStyles(o, { visibility: 'visible', display: '' });
501                                 hs.animate(o, { opacity: over ? o.opacity : 0 }, o.dur);
502                         }
503                 })();   
504         } catch (e) {}
505 },
506 addEventListener : function (el, event, func) {
507         if (el == document && event == 'ready') {
508                 hs.push(hs.onReady, func);
509         }
510         try {
511                 el.addEventListener(event, func, false);
512         } catch (e) {
513                 try {
514                         el.detachEvent('on'+ event, func);
515                         el.attachEvent('on'+ event, func);
516                 } catch (e) {
517                         el['on'+ event] = func;
518                 }
519         } 
520 },
521
522 removeEventListener : function (el, event, func) {
523         try {
524                 el.removeEventListener(event, func, false);
525         } catch (e) {
526                 try {
527                         el.detachEvent('on'+ event, func);
528                 } catch (e) {
529                         el['on'+ event] = null;
530                 }
531         }
532 },
533
534 preloadFullImage : function (i) {
535         if (hs.continuePreloading && hs.preloadTheseImages[i] && hs.preloadTheseImages[i] != 'undefined') {
536                 var img = document.createElement('img');
537                 img.onload = function() { 
538                         img = null;
539                         hs.preloadFullImage(i + 1);
540                 };
541                 img.src = hs.preloadTheseImages[i];
542         }
543 },
544 preloadImages : function (number) {
545         if (number && typeof number != 'object') hs.numberOfImagesToPreload = number;
546         
547         var arr = hs.getAnchors();
548         for (var i = 0; i < arr.images.length && i < hs.numberOfImagesToPreload; i++) {
549                 hs.push(hs.preloadTheseImages, hs.getSrc(arr.images[i]));
550         }
551         
552         // preload outlines
553         if (hs.outlineType)     new hs.Outline(hs.outlineType, function () { hs.preloadFullImage(0)} );
554         else
555         
556         hs.preloadFullImage(0);
557         
558         // preload cursor
559         if (hs.restoreCursor) var cur = hs.createElement('img', { src: hs.graphicsDir + hs.restoreCursor });
560 },
561
562
563 init : function () {
564         if (!hs.container) {
565         
566                 hs.ieLt7 = hs.ie && hs.uaVersion < 7;
567                 hs.ieLt9 = hs.ie && hs.uaVersion < 9;
568                 
569                 hs.getPageSize();
570                 for (var x in hs.langDefaults) {
571                         if (typeof hs[x] != 'undefined') hs.lang[x] = hs[x];
572                         else if (typeof hs.lang[x] == 'undefined' && typeof hs.langDefaults[x] != 'undefined') 
573                                 hs.lang[x] = hs.langDefaults[x];
574                 }
575                 
576                 hs.container = hs.createElement('div', {
577                                 className: 'highslide-container'
578                         }, {
579                                 position: 'absolute',
580                                 left: 0, 
581                                 top: 0, 
582                                 width: '100%', 
583                                 zIndex: hs.zIndexCounter,
584                                 direction: 'ltr'
585                         }, 
586                         document.body,
587                         true
588                 );
589                 hs.loading = hs.createElement('a', {
590                                 className: 'highslide-loading',
591                                 title: hs.lang.loadingTitle,
592                                 innerHTML: hs.lang.loadingText,
593                                 href: 'javascript:;'
594                         }, {
595                                 position: 'absolute',
596                                 top: '-9999px',
597                                 opacity: hs.loadingOpacity,
598                                 zIndex: 1
599                         }, hs.container
600                 );
601                 hs.garbageBin = hs.createElement('div', null, { display: 'none' }, hs.container);
602                 
603                 // http://www.robertpenner.com/easing/ 
604                 Math.linearTween = function (t, b, c, d) {
605                         return c*t/d + b;
606                 };
607                 Math.easeInQuad = function (t, b, c, d) {
608                         return c*(t/=d)*t + b;
609                 };
610                 
611                 hs.hideSelects = hs.ieLt7;
612                 hs.hideIframes = ((window.opera && hs.uaVersion < 9) || navigator.vendor == 'KDE' 
613                         || (hs.ieLt7 && hs.uaVersion < 5.5));
614         }
615 },
616 ready : function() {
617         if (hs.isReady) return;
618         hs.isReady = true;
619         for (var i = 0; i < hs.onReady.length; i++) hs.onReady[i]();
620 },
621
622 updateAnchors : function() {
623         var el, els, all = [], images = [],groups = {}, re;
624                 
625         for (var i = 0; i < hs.openerTagNames.length; i++) {
626                 els = document.getElementsByTagName(hs.openerTagNames[i]);
627                 for (var j = 0; j < els.length; j++) {
628                         el = els[j];
629                         re = hs.isHsAnchor(el);
630                         if (re) {
631                                 hs.push(all, el);
632                                 if (re[0] == 'hs.expand') hs.push(images, el);
633                                 var g = hs.getParam(el, 'slideshowGroup') || 'none';
634                                 if (!groups[g]) groups[g] = [];
635                                 hs.push(groups[g], el);
636                         }
637                 }
638         }
639         hs.anchors = { all: all, groups: groups, images: images };
640         return hs.anchors;
641         
642 },
643
644 getAnchors : function() {
645         return hs.anchors || hs.updateAnchors();
646 },
647
648
649 close : function(el) {
650         var exp = hs.getExpander(el);
651         if (exp) exp.close();
652         return false;
653 }
654 }; // end hs object
655 hs.fx = function( elem, options, prop ){
656         this.options = options;
657         this.elem = elem;
658         this.prop = prop;
659
660         if (!options.orig) options.orig = {};
661 };
662 hs.fx.prototype = {
663         update: function(){
664                 (hs.fx.step[this.prop] || hs.fx.step._default)(this);
665                 
666                 if (this.options.step)
667                         this.options.step.call(this.elem, this.now, this);
668
669         },
670         custom: function(from, to, unit){
671                 this.startTime = (new Date()).getTime();
672                 this.start = from;
673                 this.end = to;
674                 this.unit = unit;// || this.unit || "px";
675                 this.now = this.start;
676                 this.pos = this.state = 0;
677
678                 var self = this;
679                 function t(gotoEnd){
680                         return self.step(gotoEnd);
681                 }
682
683                 t.elem = this.elem;
684
685                 if ( t() && hs.timers.push(t) == 1 ) {
686                         hs.timerId = setInterval(function(){
687                                 var timers = hs.timers;
688
689                                 for ( var i = 0; i < timers.length; i++ )
690                                         if ( !timers[i]() )
691                                                 timers.splice(i--, 1);
692
693                                 if ( !timers.length ) {
694                                         clearInterval(hs.timerId);
695                                 }
696                         }, 13);
697                 }
698         },
699         step: function(gotoEnd){
700                 var t = (new Date()).getTime();
701                 if ( gotoEnd || t >= this.options.duration + this.startTime ) {
702                         this.now = this.end;
703                         this.pos = this.state = 1;
704                         this.update();
705
706                         this.options.curAnim[ this.prop ] = true;
707
708                         var done = true;
709                         for ( var i in this.options.curAnim )
710                                 if ( this.options.curAnim[i] !== true )
711                                         done = false;
712
713                         if ( done ) {
714                                 if (this.options.complete) this.options.complete.call(this.elem);
715                         }
716                         return false;
717                 } else {
718                         var n = t - this.startTime;
719                         this.state = n / this.options.duration;
720                         this.pos = this.options.easing(n, 0, 1, this.options.duration);
721                         this.now = this.start + ((this.end - this.start) * this.pos);
722                         this.update();
723                 }
724                 return true;
725         }
726
727 };
728
729 hs.extend( hs.fx, {
730         step: {
731
732                 opacity: function(fx){
733                         hs.setStyles(fx.elem, { opacity: fx.now });
734                 },
735
736                 _default: function(fx){
737                         try {
738                                 if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
739                                         fx.elem.style[ fx.prop ] = fx.now + fx.unit;
740                                 else
741                                         fx.elem[ fx.prop ] = fx.now;
742                         } catch (e) {}
743                 }
744         }
745 });
746
747 hs.Outline =  function (outlineType, onLoad) {
748         this.onLoad = onLoad;
749         this.outlineType = outlineType;
750         var v = hs.uaVersion, tr;
751         
752         this.hasAlphaImageLoader = hs.ie && hs.uaVersion < 7;
753         if (!outlineType) {
754                 if (onLoad) onLoad();
755                 return;
756         }
757         
758         hs.init();
759         this.table = hs.createElement(
760                 'table', { 
761                         cellSpacing: 0 
762                 }, {
763                         visibility: 'hidden',
764                         position: 'absolute',
765                         borderCollapse: 'collapse',
766                         width: 0
767                 },
768                 hs.container,
769                 true
770         );
771         var tbody = hs.createElement('tbody', null, null, this.table, 1);
772         
773         this.td = [];
774         for (var i = 0; i <= 8; i++) {
775                 if (i % 3 == 0) tr = hs.createElement('tr', null, { height: 'auto' }, tbody, true);
776                 this.td[i] = hs.createElement('td', null, null, tr, true);
777                 var style = i != 4 ? { lineHeight: 0, fontSize: 0} : { position : 'relative' };
778                 hs.setStyles(this.td[i], style);
779         }
780         this.td[4].className = outlineType +' highslide-outline';
781         
782         this.preloadGraphic(); 
783 };
784
785 hs.Outline.prototype = {
786 preloadGraphic : function () {
787         var src = hs.graphicsDir + (hs.outlinesDir || "outlines/")+ this.outlineType +".png";
788                                 
789         var appendTo = hs.safari && hs.uaVersion < 525 ? hs.container : null;
790         this.graphic = hs.createElement('img', null, { position: 'absolute', 
791                 top: '-9999px' }, appendTo, true); // for onload trigger
792         
793         var pThis = this;
794         this.graphic.onload = function() { pThis.onGraphicLoad(); };
795         
796         this.graphic.src = src;
797 },
798
799 onGraphicLoad : function () {
800         var o = this.offset = this.graphic.width / 4,
801                 pos = [[0,0],[0,-4],[-2,0],[0,-8],0,[-2,-8],[0,-2],[0,-6],[-2,-2]],
802                 dim = { height: (2*o) +'px', width: (2*o) +'px' };
803         for (var i = 0; i <= 8; i++) {
804                 if (pos[i]) {
805                         if (this.hasAlphaImageLoader) {
806                                 var w = (i == 1 || i == 7) ? '100%' : this.graphic.width +'px';
807                                 var div = hs.createElement('div', null, { width: '100%', height: '100%', position: 'relative', overflow: 'hidden'}, this.td[i], true);
808                                 hs.createElement ('div', null, { 
809                                                 filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale, src='"+ this.graphic.src + "')", 
810                                                 position: 'absolute',
811                                                 width: w, 
812                                                 height: this.graphic.height +'px',
813                                                 left: (pos[i][0]*o)+'px',
814                                                 top: (pos[i][1]*o)+'px'
815                                         }, 
816                                 div,
817                                 true);
818                         } else {
819                                 hs.setStyles(this.td[i], { background: 'url('+ this.graphic.src +') '+ (pos[i][0]*o)+'px '+(pos[i][1]*o)+'px'});
820                         }
821                         
822                         if (window.opera && (i == 3 || i ==5)) 
823                                 hs.createElement('div', null, dim, this.td[i], true);
824                         
825                         hs.setStyles (this.td[i], dim);
826                 }
827         }
828         this.graphic = null;
829         if (hs.pendingOutlines[this.outlineType]) hs.pendingOutlines[this.outlineType].destroy();
830         hs.pendingOutlines[this.outlineType] = this;
831         if (this.onLoad) this.onLoad();
832 },
833         
834 setPosition : function (pos, offset, vis, dur, easing) {
835         var exp = this.exp,
836                 stl = exp.wrapper.style,
837                 offset = offset || 0,
838                 pos = pos || {
839                         x: exp.x.pos + offset,
840                         y: exp.y.pos + offset,
841                         w: exp.x.get('wsize') - 2 * offset,
842                         h: exp.y.get('wsize') - 2 * offset
843                 };
844         if (vis) this.table.style.visibility = (pos.h >= 4 * this.offset) 
845                 ? 'visible' : 'hidden';
846         hs.setStyles(this.table, {
847                 left: (pos.x - this.offset) +'px',
848                 top: (pos.y - this.offset) +'px',
849                 width: (pos.w + 2 * this.offset) +'px'
850         });
851         
852         pos.w -= 2 * this.offset;
853         pos.h -= 2 * this.offset;
854         hs.setStyles (this.td[4], {
855                 width: pos.w >= 0 ? pos.w +'px' : 0,
856                 height: pos.h >= 0 ? pos.h +'px' : 0
857         });
858         if (this.hasAlphaImageLoader) this.td[3].style.height 
859                 = this.td[5].style.height = this.td[4].style.height;    
860         
861 },
862         
863 destroy : function(hide) {
864         if (hide) this.table.style.visibility = 'hidden';
865         else hs.discardElement(this.table);
866 }
867 };
868
869 hs.Dimension = function(exp, dim) {
870         this.exp = exp;
871         this.dim = dim;
872         this.ucwh = dim == 'x' ? 'Width' : 'Height';
873         this.wh = this.ucwh.toLowerCase();
874         this.uclt = dim == 'x' ? 'Left' : 'Top';
875         this.lt = this.uclt.toLowerCase();
876         this.ucrb = dim == 'x' ? 'Right' : 'Bottom';
877         this.rb = this.ucrb.toLowerCase();
878         this.p1 = this.p2 = 0;
879 };
880 hs.Dimension.prototype = {
881 get : function(key) {
882         switch (key) {
883                 case 'loadingPos':
884                         return this.tpos + this.tb + (this.t - hs.loading['offset'+ this.ucwh]) / 2;
885                 case 'wsize':
886                         return this.size + 2 * this.cb + this.p1 + this.p2;
887                 case 'fitsize':
888                         return this.clientSize - this.marginMin - this.marginMax;
889                 case 'maxsize':
890                         return this.get('fitsize') - 2 * this.cb - this.p1 - this.p2 ;
891                 case 'opos':
892                         return this.pos - (this.exp.outline ? this.exp.outline.offset : 0);
893                 case 'osize':
894                         return this.get('wsize') + (this.exp.outline ? 2*this.exp.outline.offset : 0);
895                 case 'imgPad':
896                         return this.imgSize ? Math.round((this.size - this.imgSize) / 2) : 0;
897                 
898         }
899 },
900 calcBorders: function() {
901         // correct for borders
902         this.cb = (this.exp.content['offset'+ this.ucwh] - this.t) / 2;
903         
904         this.marginMax = hs['margin'+ this.ucrb];
905 },
906 calcThumb: function() {
907         this.t = this.exp.el[this.wh] ? parseInt(this.exp.el[this.wh]) : 
908                 this.exp.el['offset'+ this.ucwh];
909         this.tpos = this.exp.tpos[this.dim];
910         this.tb = (this.exp.el['offset'+ this.ucwh] - this.t) / 2;
911         if (this.tpos == 0 || this.tpos == -1) {
912                 this.tpos = (hs.page[this.wh] / 2) + hs.page['scroll'+ this.uclt];              
913         };
914 },
915 calcExpanded: function() {
916         var exp = this.exp;
917         this.justify = 'auto';
918         
919         
920         // size and position
921         this.pos = this.tpos - this.cb + this.tb;
922         
923         if (this.maxHeight && this.dim == 'x')
924                 exp.maxWidth = Math.min(exp.maxWidth || this.full, exp.maxHeight * this.full / exp.y.full); 
925                 
926         this.size = Math.min(this.full, exp['max'+ this.ucwh] || this.full);
927         this.minSize = exp.allowSizeReduction ? 
928                 Math.min(exp['min'+ this.ucwh], this.full) :this.full;
929         if (exp.isImage && exp.useBox)  {
930                 this.size = exp[this.wh];
931                 this.imgSize = this.full;
932         }
933         if (this.dim == 'x' && hs.padToMinWidth) this.minSize = exp.minWidth;
934         this.marginMin = hs['margin'+ this.uclt];
935         this.scroll = hs.page['scroll'+ this.uclt];
936         this.clientSize = hs.page[this.wh];
937 },
938 setSize: function(i) {
939         var exp = this.exp;
940         if (exp.isImage && (exp.useBox || hs.padToMinWidth)) {
941                 this.imgSize = i;
942                 this.size = Math.max(this.size, this.imgSize);
943                 exp.content.style[this.lt] = this.get('imgPad')+'px';
944         } else
945         this.size = i;
946         
947         exp.content.style[this.wh] = i +'px';
948         exp.wrapper.style[this.wh] = this.get('wsize') +'px';
949         if (exp.outline) exp.outline.setPosition();
950         if (this.dim == 'x' && exp.overlayBox) exp.sizeOverlayBox(true);
951 },
952 setPos: function(i) {
953         this.pos = i;
954         this.exp.wrapper.style[this.lt] = i +'px';      
955         
956         if (this.exp.outline) this.exp.outline.setPosition();
957         
958 }
959 };
960
961 hs.Expander = function(a, params, custom, contentType) {
962         if (document.readyState && hs.ie && !hs.isReady) {
963                 hs.addEventListener(document, 'ready', function() {
964                         new hs.Expander(a, params, custom, contentType);
965                 });
966                 return;
967         } 
968         this.a = a;
969         this.custom = custom;
970         this.contentType = contentType || 'image';
971         this.isImage = !this.isHtml;
972         
973         hs.continuePreloading = false;
974         this.overlays = [];
975         hs.init();
976         var key = this.key = hs.expanders.length;
977         // override inline parameters
978         for (var i = 0; i < hs.overrides.length; i++) {
979                 var name = hs.overrides[i];
980                 this[name] = params && typeof params[name] != 'undefined' ?
981                         params[name] : hs[name];
982         }
983         if (!this.src) this.src = a.href;
984         
985         // get thumb
986         var el = (params && params.thumbnailId) ? hs.$(params.thumbnailId) : a;
987         el = this.thumb = el.getElementsByTagName('img')[0] || el;
988         this.thumbsUserSetId = el.id || a.id;
989         
990         // check if already open
991         for (var i = 0; i < hs.expanders.length; i++) {
992                 if (hs.expanders[i] && hs.expanders[i].a == a) {
993                         hs.expanders[i].focus();
994                         return false;
995                 }
996         }       
997
998         // cancel other
999         if (!hs.allowSimultaneousLoading) for (var i = 0; i < hs.expanders.length; i++) {
1000                 if (hs.expanders[i] && hs.expanders[i].thumb != el && !hs.expanders[i].onLoadStarted) {
1001                         hs.expanders[i].cancelLoading();
1002                 }
1003         }
1004         hs.expanders[key] = this;
1005         if (!hs.allowMultipleInstances && !hs.upcoming) {
1006                 if (hs.expanders[key-1]) hs.expanders[key-1].close();
1007                 if (typeof hs.focusKey != 'undefined' && hs.expanders[hs.focusKey])
1008                         hs.expanders[hs.focusKey].close();
1009         }
1010         
1011         // initiate metrics
1012         this.el = el;
1013         this.tpos = this.pageOrigin || hs.getPosition(el);
1014         hs.getPageSize();
1015         var x = this.x = new hs.Dimension(this, 'x');
1016         x.calcThumb();
1017         var y = this.y = new hs.Dimension(this, 'y');
1018         y.calcThumb();
1019         this.wrapper = hs.createElement(
1020                 'div', {
1021                         id: 'highslide-wrapper-'+ this.key,
1022                         className: 'highslide-wrapper '+ this.wrapperClassName
1023                 }, {
1024                         visibility: 'hidden',
1025                         position: 'absolute',
1026                         zIndex: hs.zIndexCounter += 2
1027                 }, null, true );
1028         
1029         this.wrapper.onmouseover = this.wrapper.onmouseout = hs.wrapperMouseHandler;
1030         if (this.contentType == 'image' && this.outlineWhileAnimating == 2)
1031                 this.outlineWhileAnimating = 0;
1032         
1033         // get the outline
1034         if (!this.outlineType) {
1035                 this[this.contentType +'Create']();
1036         
1037         } else if (hs.pendingOutlines[this.outlineType]) {
1038                 this.connectOutline();
1039                 this[this.contentType +'Create']();
1040         
1041         } else {
1042                 this.showLoading();
1043                 var exp = this;
1044                 new hs.Outline(this.outlineType, 
1045                         function () {
1046                                 exp.connectOutline();
1047                                 exp[exp.contentType +'Create']();
1048                         } 
1049                 );
1050         }
1051         return true;
1052 };
1053
1054 hs.Expander.prototype = {
1055 error : function(e) {
1056         if (hs.debug) alert ('Line '+ e.lineNumber +': '+ e.message);
1057         else window.location.href = this.src;
1058 },
1059
1060 connectOutline : function() {
1061         var outline = this.outline = hs.pendingOutlines[this.outlineType];
1062         outline.exp = this;
1063         outline.table.style.zIndex = this.wrapper.style.zIndex - 1;
1064         hs.pendingOutlines[this.outlineType] = null;
1065 },
1066
1067 showLoading : function() {
1068         if (this.onLoadStarted || this.loading) return;
1069         
1070         this.loading = hs.loading;
1071         var exp = this;
1072         this.loading.onclick = function() {
1073                 exp.cancelLoading();
1074         };
1075         var exp = this, 
1076                 l = this.x.get('loadingPos') +'px',
1077                 t = this.y.get('loadingPos') +'px';
1078         setTimeout(function () { 
1079                 if (exp.loading) hs.setStyles(exp.loading, { left: l, top: t, zIndex: hs.zIndexCounter++ })}
1080         , 100);
1081 },
1082
1083 imageCreate : function() {
1084         var exp = this;
1085         
1086         var img = document.createElement('img');
1087     this.content = img;
1088     img.onload = function () {
1089         if (hs.expanders[exp.key]) exp.contentLoaded(); 
1090         };
1091     if (hs.blockRightClick) img.oncontextmenu = function() { return false; };
1092     img.className = 'highslide-image';
1093     hs.setStyles(img, {
1094         visibility: 'hidden',
1095         display: 'block',
1096         position: 'absolute',
1097                 maxWidth: '9999px',
1098                 zIndex: 3
1099         });
1100     img.title = hs.lang.restoreTitle;
1101         if (hs.safari && hs.uaVersion < 525) hs.container.appendChild(img);
1102     if (hs.ie && hs.flushImgSize) img.src = null;
1103         img.src = this.src;
1104         
1105         this.showLoading();
1106 },
1107
1108 contentLoaded : function() {
1109         try {   
1110                 if (!this.content) return;
1111                 this.content.onload = null;
1112                 if (this.onLoadStarted) return;
1113                 else this.onLoadStarted = true;
1114                 
1115                 var x = this.x, y = this.y;
1116                 
1117                 if (this.loading) {
1118                         hs.setStyles(this.loading, { top: '-9999px' });
1119                         this.loading = null;
1120                 }       
1121                         x.full = this.content.width;
1122                         y.full = this.content.height;
1123                         
1124                         hs.setStyles(this.content, {
1125                                 width: x.t +'px',
1126                                 height: y.t +'px'
1127                         });
1128                         this.wrapper.appendChild(this.content);
1129                         hs.container.appendChild(this.wrapper);
1130                 
1131                 x.calcBorders();
1132                 y.calcBorders();
1133                 
1134                 hs.setStyles (this.wrapper, {
1135                         left: (x.tpos + x.tb - x.cb) +'px',
1136                         top: (y.tpos + x.tb - y.cb) +'px'
1137                 });
1138                 this.getOverlays();
1139                 
1140                 var ratio = x.full / y.full;
1141                 x.calcExpanded();
1142                 this.justify(x);
1143                 
1144                 y.calcExpanded();
1145                 this.justify(y);
1146                 if (this.overlayBox) this.sizeOverlayBox(0, 1);
1147
1148                 
1149                 if (this.allowSizeReduction) {
1150                                 this.correctRatio(ratio);
1151                         if (this.isImage && this.x.full > (this.x.imgSize || this.x.size)) {
1152                                 this.createFullExpand();
1153                                 if (this.overlays.length == 1) this.sizeOverlayBox();
1154                         }
1155                 }
1156                 this.show();
1157                 
1158         } catch (e) {
1159                 this.error(e);
1160         }
1161 },
1162
1163 justify : function (p, moveOnly) {
1164         var tgtArr, tgt = p.target, dim = p == this.x ? 'x' : 'y';
1165         
1166                 var hasMovedMin = false;
1167                 
1168                 var allowReduce = p.exp.allowSizeReduction;
1169                         p.pos = Math.round(p.pos - ((p.get('wsize') - p.t) / 2));
1170                 if (p.pos < p.scroll + p.marginMin) {
1171                         p.pos = p.scroll + p.marginMin;
1172                         hasMovedMin = true;             
1173                 }
1174                 if (!moveOnly && p.size < p.minSize) {
1175                         p.size = p.minSize;
1176                         allowReduce = false;
1177                 }
1178                 if (p.pos + p.get('wsize') > p.scroll + p.clientSize - p.marginMax) {
1179                         if (!moveOnly && hasMovedMin && allowReduce) {
1180                                 p.size = Math.min(p.size, p.get(dim == 'y' ? 'fitsize' : 'maxsize'));
1181                         } else if (p.get('wsize') < p.get('fitsize')) {
1182                                 p.pos = p.scroll + p.clientSize - p.marginMax - p.get('wsize');
1183                         } else { // image larger than viewport
1184                                 p.pos = p.scroll + p.marginMin;
1185                                 if (!moveOnly && allowReduce) p.size = p.get(dim == 'y' ? 'fitsize' : 'maxsize');
1186                         }                       
1187                 }
1188                 
1189                 if (!moveOnly && p.size < p.minSize) {
1190                         p.size = p.minSize;
1191                         allowReduce = false;
1192                 }
1193                 
1194         
1195                 
1196         if (p.pos < p.marginMin) {
1197                 var tmpMin = p.pos;
1198                 p.pos = p.marginMin; 
1199                 
1200                 if (allowReduce && !moveOnly) p.size = p.size - (p.pos - tmpMin);
1201                 
1202         }
1203 },
1204
1205 correctRatio : function(ratio) {
1206         var x = this.x, 
1207                 y = this.y,
1208                 changed = false,
1209                 xSize = Math.min(x.full, x.size),
1210                 ySize = Math.min(y.full, y.size),
1211                 useBox = (this.useBox || hs.padToMinWidth);
1212         
1213         if (xSize / ySize > ratio) { // width greater
1214                 xSize = ySize * ratio;
1215                 if (xSize < x.minSize) { // below minWidth
1216                         xSize = x.minSize;
1217                         ySize = xSize / ratio;
1218                 }
1219                 changed = true;
1220         
1221         } else if (xSize / ySize < ratio) { // height greater
1222                 ySize = xSize / ratio;
1223                 changed = true;
1224         }
1225         
1226         if (hs.padToMinWidth && x.full < x.minSize) {
1227                 x.imgSize = x.full;
1228                 y.size = y.imgSize = y.full;
1229         } else if (this.useBox) {
1230                 x.imgSize = xSize;
1231                 y.imgSize = ySize;
1232         } else {
1233                 x.size = xSize;
1234                 y.size = ySize;
1235         }
1236         changed = this.fitOverlayBox(this.useBox ? null : ratio, changed);
1237         if (useBox && y.size < y.imgSize) {
1238                 y.imgSize = y.size;
1239                 x.imgSize = y.size * ratio;
1240         }
1241         if (changed || useBox) {
1242                 x.pos = x.tpos - x.cb + x.tb;
1243                 x.minSize = x.size;
1244                 this.justify(x, true);
1245         
1246                 y.pos = y.tpos - y.cb + y.tb;
1247                 y.minSize = y.size;
1248                 this.justify(y, true);
1249                 if (this.overlayBox) this.sizeOverlayBox();
1250         }
1251         
1252         
1253 },
1254 fitOverlayBox : function(ratio, changed) {
1255         var x = this.x, y = this.y;
1256         if (this.overlayBox) {
1257                 while (y.size > this.minHeight && x.size > this.minWidth 
1258                                 &&  y.get('wsize') > y.get('fitsize')) {
1259                         y.size -= 10;
1260                         if (ratio) x.size = y.size * ratio;
1261                         this.sizeOverlayBox(0, 1);
1262                         changed = true;
1263                 }
1264         }
1265         return changed;
1266 },
1267
1268 show : function () {
1269         var x = this.x, y = this.y;
1270         this.doShowHide('hidden');
1271         
1272         // Apply size change
1273         this.changeSize(
1274                 1, {
1275                         wrapper: {
1276                                 width : x.get('wsize'),
1277                                 height : y.get('wsize'),
1278                                 left: x.pos,
1279                                 top: y.pos
1280                         },
1281                         content: {
1282                                 left: x.p1 + x.get('imgPad'),
1283                                 top: y.p1 + y.get('imgPad'),
1284                                 width:x.imgSize ||x.size,
1285                                 height:y.imgSize ||y.size
1286                         }
1287                 },
1288                 hs.expandDuration
1289         );
1290 },
1291
1292 changeSize : function(up, to, dur) {
1293         
1294         if (this.outline && !this.outlineWhileAnimating) {
1295                 if (up) this.outline.setPosition();
1296                 else this.outline.destroy();
1297         }
1298         
1299         
1300         if (!up) this.destroyOverlays();
1301         
1302         var exp = this,
1303                 x = exp.x,
1304                 y = exp.y,
1305                 easing = this.easing;
1306         if (!up) easing = this.easingClose || easing;
1307         var after = up ?
1308                 function() {
1309                                 
1310                         if (exp.outline) exp.outline.table.style.visibility = "visible";
1311                         setTimeout(function() {
1312                                 exp.afterExpand();
1313                         }, 50);
1314                 } :
1315                 function() {
1316                         exp.afterClose();
1317                 };
1318         if (up) hs.setStyles( this.wrapper, {
1319                 width: x.t +'px',
1320                 height: y.t +'px'
1321         });
1322         if (this.fadeInOut) {
1323                 hs.setStyles(this.wrapper, { opacity: up ? 0 : 1 });
1324                 hs.extend(to.wrapper, { opacity: up });
1325         }
1326         hs.animate( this.wrapper, to.wrapper, {
1327                 duration: dur,
1328                 easing: easing,
1329                 step: function(val, args) {
1330                         if (exp.outline && exp.outlineWhileAnimating && args.prop == 'top') {
1331                                 var fac = up ? args.pos : 1 - args.pos;
1332                                 var pos = {
1333                                         w: x.t + (x.get('wsize') - x.t) * fac,
1334                                         h: y.t + (y.get('wsize') - y.t) * fac,
1335                                         x: x.tpos + (x.pos - x.tpos) * fac,
1336                                         y: y.tpos + (y.pos - y.tpos) * fac
1337                                 };
1338                                 exp.outline.setPosition(pos, 0, 1);                             
1339                         }
1340                 }
1341         });
1342         hs.animate( this.content, to.content, dur, easing, after);
1343         if (up) {
1344                 this.wrapper.style.visibility = 'visible';
1345                 this.content.style.visibility = 'visible';
1346                 this.a.className += ' highslide-active-anchor';
1347         }
1348 },
1349
1350
1351
1352
1353 afterExpand : function() {
1354         this.isExpanded = true; 
1355         this.focus();
1356         if (hs.upcoming && hs.upcoming == this.a) hs.upcoming = null;
1357         this.prepareNextOutline();
1358         var p = hs.page, mX = hs.mouse.x + p.scrollLeft, mY = hs.mouse.y + p.scrollTop;
1359         this.mouseIsOver = this.x.pos < mX && mX < this.x.pos + this.x.get('wsize')
1360                 && this.y.pos < mY && mY < this.y.pos + this.y.get('wsize');    
1361         if (this.overlayBox) this.showOverlays();
1362         
1363 },
1364
1365
1366 prepareNextOutline : function() {
1367         var key = this.key;
1368         var outlineType = this.outlineType;
1369         new hs.Outline(outlineType, 
1370                 function () { try { hs.expanders[key].preloadNext(); } catch (e) {} });
1371 },
1372
1373
1374 preloadNext : function() {
1375         var next = this.getAdjacentAnchor(1);
1376         if (next && next.onclick.toString().match(/hs\.expand/)) 
1377                 var img = hs.createElement('img', { src: hs.getSrc(next) });
1378 },
1379
1380
1381 getAdjacentAnchor : function(op) {
1382         var current = this.getAnchorIndex(), as = hs.anchors.groups[this.slideshowGroup || 'none'];
1383         return (as && as[current + op]) || null;
1384 },
1385
1386 getAnchorIndex : function() {
1387         var arr = hs.getAnchors().groups[this.slideshowGroup || 'none'];
1388         if (arr) for (var i = 0; i < arr.length; i++) {
1389                 if (arr[i] == this.a) return i; 
1390         }
1391         return null;
1392 },
1393
1394
1395 cancelLoading : function() {
1396         hs.discardElement (this.wrapper);
1397         hs.expanders[this.key] = null;
1398         if (this.loading) hs.loading.style.left = '-9999px';
1399 },
1400
1401 writeCredits : function () {
1402         this.credits = hs.createElement('a', {
1403                 href: hs.creditsHref,
1404                 target: hs.creditsTarget,
1405                 className: 'highslide-credits',
1406                 innerHTML: hs.lang.creditsText,
1407                 title: hs.lang.creditsTitle
1408         });
1409         this.createOverlay({ 
1410                 overlayId: this.credits, 
1411                 position: this.creditsPosition || 'top left' 
1412         });
1413 },
1414
1415 getInline : function(types, addOverlay) {
1416         for (var i = 0; i < types.length; i++) {
1417                 var type = types[i], s = null;
1418                 if (!this[type +'Id'] && this.thumbsUserSetId)  
1419                         this[type +'Id'] = type +'-for-'+ this.thumbsUserSetId;
1420                 if (this[type +'Id']) this[type] = hs.getNode(this[type +'Id']);
1421                 if (!this[type] && !this[type +'Text'] && this[type +'Eval']) try {
1422                         s = eval(this[type +'Eval']);
1423                 } catch (e) {}
1424                 if (!this[type] && this[type +'Text']) {
1425                         s = this[type +'Text'];
1426                 }
1427                 if (!this[type] && !s) {
1428                         this[type] = hs.getNode(this.a['_'+ type + 'Id']);
1429                         if (!this[type]) {
1430                                 var next = this.a.nextSibling;
1431                                 while (next && !hs.isHsAnchor(next)) {
1432                                         if ((new RegExp('highslide-'+ type)).test(next.className || null)) {
1433                                                 if (!next.id) this.a['_'+ type + 'Id'] = next.id = 'hsId'+ hs.idCounter++;
1434                                                 this[type] = hs.getNode(next.id);
1435                                                 break;
1436                                         }
1437                                         next = next.nextSibling;
1438                                 }
1439                         }
1440                 }
1441                 
1442                 if (!this[type] && s) this[type] = hs.createElement('div', 
1443                                 { className: 'highslide-'+ type, innerHTML: s } );
1444                 
1445                 if (addOverlay && this[type]) {
1446                         var o = { position: (type == 'heading') ? 'above' : 'below' };
1447                         for (var x in this[type+'Overlay']) o[x] = this[type+'Overlay'][x];
1448                         o.overlayId = this[type];
1449                         this.createOverlay(o);
1450                 }
1451         }
1452 },
1453
1454
1455 // on end move and resize
1456 doShowHide : function(visibility) {
1457         if (hs.hideSelects) this.showHideElements('SELECT', visibility);
1458         if (hs.hideIframes) this.showHideElements('IFRAME', visibility);
1459         if (hs.geckoMac) this.showHideElements('*', visibility);
1460 },
1461 showHideElements : function (tagName, visibility) {
1462         var els = document.getElementsByTagName(tagName);
1463         var prop = tagName == '*' ? 'overflow' : 'visibility';
1464         for (var i = 0; i < els.length; i++) {
1465                 if (prop == 'visibility' || (document.defaultView.getComputedStyle(
1466                                 els[i], "").getPropertyValue('overflow') == 'auto'
1467                                 || els[i].getAttribute('hidden-by') != null)) {
1468                         var hiddenBy = els[i].getAttribute('hidden-by');
1469                         if (visibility == 'visible' && hiddenBy) {
1470                                 hiddenBy = hiddenBy.replace('['+ this.key +']', '');
1471                                 els[i].setAttribute('hidden-by', hiddenBy);
1472                                 if (!hiddenBy) els[i].style[prop] = els[i].origProp;
1473                         } else if (visibility == 'hidden') { // hide if behind
1474                                 var elPos = hs.getPosition(els[i]);
1475                                 elPos.w = els[i].offsetWidth;
1476                                 elPos.h = els[i].offsetHeight;
1477                         
1478                                 
1479                                         var clearsX = (elPos.x + elPos.w < this.x.get('opos') 
1480                                                 || elPos.x > this.x.get('opos') + this.x.get('osize'));
1481                                         var clearsY = (elPos.y + elPos.h < this.y.get('opos') 
1482                                                 || elPos.y > this.y.get('opos') + this.y.get('osize'));
1483                                 var wrapperKey = hs.getWrapperKey(els[i]);
1484                                 if (!clearsX && !clearsY && wrapperKey != this.key) { // element falls behind image
1485                                         if (!hiddenBy) {
1486                                                 els[i].setAttribute('hidden-by', '['+ this.key +']');
1487                                                 els[i].origProp = els[i].style[prop];
1488                                                 els[i].style[prop] = 'hidden';
1489                                                 
1490                                         } else if (hiddenBy.indexOf('['+ this.key +']') == -1) {
1491                                                 els[i].setAttribute('hidden-by', hiddenBy + '['+ this.key +']');
1492                                         }
1493                                 } else if ((hiddenBy == '['+ this.key +']' || hs.focusKey == wrapperKey)
1494                                                 && wrapperKey != this.key) { // on move
1495                                         els[i].setAttribute('hidden-by', '');
1496                                         els[i].style[prop] = els[i].origProp || '';
1497                                 } else if (hiddenBy && hiddenBy.indexOf('['+ this.key +']') > -1) {
1498                                         els[i].setAttribute('hidden-by', hiddenBy.replace('['+ this.key +']', ''));
1499                                 }
1500                                                 
1501                         }
1502                 }
1503         }
1504 },
1505
1506 focus : function() {
1507         this.wrapper.style.zIndex = hs.zIndexCounter += 2;
1508         // blur others
1509         for (var i = 0; i < hs.expanders.length; i++) {
1510                 if (hs.expanders[i] && i == hs.focusKey) {
1511                         var blurExp = hs.expanders[i];
1512                         blurExp.content.className += ' highslide-'+ blurExp.contentType +'-blur';
1513                                 blurExp.content.style.cursor = hs.ieLt7 ? 'hand' : 'pointer';
1514                                 blurExp.content.title = hs.lang.focusTitle;
1515                 }
1516         }
1517         
1518         // focus this
1519         if (this.outline) this.outline.table.style.zIndex 
1520                 = this.wrapper.style.zIndex - 1;
1521         this.content.className = 'highslide-'+ this.contentType;
1522                 this.content.title = hs.lang.restoreTitle;
1523                 
1524                 if (hs.restoreCursor) {
1525                         hs.styleRestoreCursor = window.opera ? 'pointer' : 'url('+ hs.graphicsDir + hs.restoreCursor +'), pointer';
1526                         if (hs.ieLt7 && hs.uaVersion < 6) hs.styleRestoreCursor = 'hand';
1527                         this.content.style.cursor = hs.styleRestoreCursor;
1528                 }
1529                 
1530         hs.focusKey = this.key; 
1531         hs.addEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);    
1532 },
1533 moveTo: function(x, y) {
1534         this.x.setPos(x);
1535         this.y.setPos(y);
1536 },
1537 resize : function (e) {
1538         var w, h, r = e.width / e.height;
1539         w = Math.max(e.width + e.dX, Math.min(this.minWidth, this.x.full));
1540         if (this.isImage && Math.abs(w - this.x.full) < 12) w = this.x.full;
1541         h = w / r;
1542         if (h < Math.min(this.minHeight, this.y.full)) {
1543                 h = Math.min(this.minHeight, this.y.full);
1544                 if (this.isImage) w = h * r;
1545         }
1546         this.resizeTo(w, h);
1547 },
1548 resizeTo: function(w, h) {
1549         this.y.setSize(h);
1550         this.x.setSize(w);
1551         this.wrapper.style.height = this.y.get('wsize') +'px';
1552 },
1553
1554 close : function() {
1555         if (this.isClosing || !this.isExpanded) return;
1556         this.isClosing = true;
1557         
1558         hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
1559         
1560         try {
1561                 this.content.style.cursor = 'default';
1562                 this.changeSize(
1563                         0, {
1564                                 wrapper: {
1565                                         width : this.x.t,
1566                                         height : this.y.t,
1567                                         left: this.x.tpos - this.x.cb + this.x.tb,
1568                                         top: this.y.tpos - this.y.cb + this.y.tb
1569                                 },
1570                                 content: {
1571                                         left: 0,
1572                                         top: 0,
1573                                         width: this.x.t,
1574                                         height: this.y.t
1575                                 }
1576                         }, hs.restoreDuration
1577                 );
1578         } catch (e) { this.afterClose(); }
1579 },
1580
1581 createOverlay : function (o) {
1582         var el = o.overlayId;
1583         if (typeof el == 'string') el = hs.getNode(el);
1584         if (o.html) el = hs.createElement('div', { innerHTML: o.html });
1585         if (!el || typeof el == 'string') return;
1586         el.style.display = 'block';
1587         this.genOverlayBox();
1588         var width = o.width && /^[0-9]+(px|%)$/.test(o.width) ? o.width : 'auto';
1589         if (/^(left|right)panel$/.test(o.position) && !/^[0-9]+px$/.test(o.width)) width = '200px';
1590         var overlay = hs.createElement(
1591                 'div', {
1592                         id: 'hsId'+ hs.idCounter++,
1593                         hsId: o.hsId
1594                 }, {
1595                         position: 'absolute',
1596                         visibility: 'hidden',
1597                         width: width,
1598                         direction: hs.lang.cssDirection || '',
1599                         opacity: 0
1600                 },this.overlayBox,
1601                 true
1602         );
1603         
1604         overlay.appendChild(el);
1605         hs.extend(overlay, {
1606                 opacity: 1,
1607                 offsetX: 0,
1608                 offsetY: 0,
1609                 dur: (o.fade === 0 || o.fade === false || (o.fade == 2 && hs.ie)) ? 0 : 250
1610         });
1611         hs.extend(overlay, o);
1612         
1613                 
1614         if (this.gotOverlays) {
1615                 this.positionOverlay(overlay);
1616                 if (!overlay.hideOnMouseOut || this.mouseIsOver) 
1617                         hs.animate(overlay, { opacity: overlay.opacity }, overlay.dur);
1618         }
1619         hs.push(this.overlays, hs.idCounter - 1);
1620 },
1621 positionOverlay : function(overlay) {
1622         var p = overlay.position || 'middle center',
1623                 offX = overlay.offsetX,
1624                 offY = overlay.offsetY;
1625         if (overlay.parentNode != this.overlayBox) this.overlayBox.appendChild(overlay);
1626         if (/left$/.test(p)) overlay.style.left = offX +'px'; 
1627         
1628         if (/center$/.test(p))  hs.setStyles (overlay, { 
1629                 left: '50%',
1630                 marginLeft: (offX - Math.round(overlay.offsetWidth / 2)) +'px'
1631         });     
1632         
1633         if (/right$/.test(p)) overlay.style.right = - offX +'px';
1634                 
1635         if (/^leftpanel$/.test(p)) { 
1636                 hs.setStyles(overlay, {
1637                         right: '100%',
1638                         marginRight: this.x.cb +'px',
1639                         top: - this.y.cb +'px',
1640                         bottom: - this.y.cb +'px',
1641                         overflow: 'auto'
1642                 });              
1643                 this.x.p1 = overlay.offsetWidth;
1644         
1645         } else if (/^rightpanel$/.test(p)) {
1646                 hs.setStyles(overlay, {
1647                         left: '100%',
1648                         marginLeft: this.x.cb +'px',
1649                         top: - this.y.cb +'px',
1650                         bottom: - this.y.cb +'px',
1651                         overflow: 'auto'
1652                 });
1653                 this.x.p2 = overlay.offsetWidth;
1654         }
1655
1656         if (/^top/.test(p)) overlay.style.top = offY +'px'; 
1657         if (/^middle/.test(p))  hs.setStyles (overlay, { 
1658                 top: '50%', 
1659                 marginTop: (offY - Math.round(overlay.offsetHeight / 2)) +'px'
1660         });     
1661         if (/^bottom/.test(p)) overlay.style.bottom = - offY +'px';
1662         if (/^above$/.test(p)) {
1663                 hs.setStyles(overlay, {
1664                         left: (- this.x.p1 - this.x.cb) +'px',
1665                         right: (- this.x.p2 - this.x.cb) +'px',
1666                         bottom: '100%',
1667                         marginBottom: this.y.cb +'px',
1668                         width: 'auto'
1669                 });
1670                 this.y.p1 = overlay.offsetHeight;
1671         
1672         } else if (/^below$/.test(p)) {
1673                 hs.setStyles(overlay, {
1674                         position: 'relative',
1675                         left: (- this.x.p1 - this.x.cb) +'px',
1676                         right: (- this.x.p2 - this.x.cb) +'px',
1677                         top: '100%',
1678                         marginTop: this.y.cb +'px',
1679                         width: 'auto'
1680                 });
1681                 this.y.p2 = overlay.offsetHeight;
1682                 overlay.style.position = 'absolute';
1683         }
1684 },
1685
1686 getOverlays : function() {      
1687         this.getInline(['heading', 'caption'], true);
1688         if (this.heading && this.dragByHeading) this.heading.className += ' highslide-move';
1689         if (hs.showCredits) this.writeCredits();
1690         for (var i = 0; i < hs.overlays.length; i++) {
1691                 var o = hs.overlays[i], tId = o.thumbnailId, sg = o.slideshowGroup;
1692                 if ((!tId && !sg) || (tId && tId == this.thumbsUserSetId)
1693                                 || (sg && sg === this.slideshowGroup)) {
1694                         this.createOverlay(o);
1695                 }
1696         }
1697         var os = [];
1698         for (var i = 0; i < this.overlays.length; i++) {
1699                 var o = hs.$('hsId'+ this.overlays[i]);
1700                 if (/panel$/.test(o.position)) this.positionOverlay(o);
1701                 else hs.push(os, o);
1702         }
1703         for (var i = 0; i < os.length; i++) this.positionOverlay(os[i]);
1704         this.gotOverlays = true;
1705 },
1706 genOverlayBox : function() {
1707         if (!this.overlayBox) this.overlayBox = hs.createElement (
1708                 'div', {
1709                         className: this.wrapperClassName
1710                 }, {
1711                         position : 'absolute',
1712                         width: (this.x.size || (this.useBox ? this.width : null) 
1713                                 || this.x.full) +'px',
1714                         height: (this.y.size || this.y.full) +'px',
1715                         visibility : 'hidden',
1716                         overflow : 'hidden',
1717                         zIndex : hs.ie ? 4 : 'auto'
1718                 },
1719                 hs.container,
1720                 true
1721         );
1722 },
1723 sizeOverlayBox : function(doWrapper, doPanels) {
1724         var overlayBox = this.overlayBox, 
1725                 x = this.x,
1726                 y = this.y;
1727         hs.setStyles( overlayBox, {
1728                 width: x.size +'px', 
1729                 height: y.size +'px'
1730         });
1731         if (doWrapper || doPanels) {
1732                 for (var i = 0; i < this.overlays.length; i++) {
1733                         var o = hs.$('hsId'+ this.overlays[i]);
1734                         var ie6 = (hs.ieLt7 || document.compatMode == 'BackCompat');
1735                         if (o && /^(above|below)$/.test(o.position)) {
1736                                 if (ie6) {
1737                                         o.style.width = (overlayBox.offsetWidth + 2 * x.cb
1738                                                 + x.p1 + x.p2) +'px';
1739                                 }
1740                                 y[o.position == 'above' ? 'p1' : 'p2'] = o.offsetHeight;
1741                         }
1742                         if (o && ie6 && /^(left|right)panel$/.test(o.position)) {
1743                                 o.style.height = (overlayBox.offsetHeight + 2* y.cb) +'px';
1744                         }
1745                 }
1746         }
1747         if (doWrapper) {
1748                 hs.setStyles(this.content, {
1749                         top: y.p1 +'px'
1750                 });
1751                 hs.setStyles(overlayBox, {
1752                         top: (y.p1 + y.cb) +'px'
1753                 });
1754         }
1755 },
1756
1757 showOverlays : function() {
1758         var b = this.overlayBox;
1759         b.className = '';
1760         hs.setStyles(b, {
1761                 top: (this.y.p1 + this.y.cb) +'px',
1762                 left: (this.x.p1 + this.x.cb) +'px',
1763                 overflow : 'visible'
1764         });
1765         if (hs.safari) b.style.visibility = 'visible';
1766         this.wrapper.appendChild (b);
1767         for (var i = 0; i < this.overlays.length; i++) {
1768                 var o = hs.$('hsId'+ this.overlays[i]);
1769                 o.style.zIndex = o.zIndex || 4;
1770                 if (!o.hideOnMouseOut || this.mouseIsOver) {
1771                         o.style.visibility = 'visible';
1772                         hs.setStyles(o, { visibility: 'visible', display: '' });
1773                         hs.animate(o, { opacity: o.opacity }, o.dur);
1774                 }
1775         }
1776 },
1777
1778 destroyOverlays : function() {
1779         if (!this.overlays.length) return;
1780         hs.discardElement(this.overlayBox);
1781 },
1782
1783
1784
1785 createFullExpand : function () {
1786         this.fullExpandLabel = hs.createElement(
1787                 'a', {
1788                         href: 'javascript:hs.expanders['+ this.key +'].doFullExpand();',
1789                         title: hs.lang.fullExpandTitle,
1790                         className: 'highslide-full-expand'
1791                 }
1792         );
1793         
1794         this.createOverlay({ 
1795                 overlayId: this.fullExpandLabel, 
1796                 position: hs.fullExpandPosition, 
1797                 hideOnMouseOut: true, 
1798                 opacity: hs.fullExpandOpacity
1799         });
1800 },
1801
1802 doFullExpand : function () {
1803         try {
1804                 if (this.fullExpandLabel) hs.discardElement(this.fullExpandLabel);
1805                 
1806                 this.focus();
1807                 var xSize = this.x.size,
1808                 ySize = this.y.size;
1809         this.resizeTo(this.x.full, this.y.full);
1810        
1811         var xpos = this.x.pos - (this.x.size - xSize) / 2;
1812         if (xpos < hs.marginLeft) xpos = hs.marginLeft;
1813        
1814         var ypos = this.y.pos - (this.y.size - ySize) / 2;
1815         if (ypos < hs.marginTop) ypos = hs.marginTop;
1816        
1817         this.moveTo(xpos, ypos);
1818                 this.doShowHide('hidden');
1819         
1820         } catch (e) {
1821                 this.error(e);
1822         }
1823 },
1824
1825
1826 afterClose : function () {
1827         this.a.className = this.a.className.replace('highslide-active-anchor', '');
1828         
1829         this.doShowHide('visible');
1830                 if (this.outline && this.outlineWhileAnimating) this.outline.destroy();
1831         
1832                 hs.discardElement(this.wrapper);
1833         
1834         hs.expanders[this.key] = null;          
1835         hs.reOrder();
1836 }
1837
1838 };
1839 hs.langDefaults = hs.lang;
1840 // history
1841 var HsExpander = hs.Expander;
1842 if (hs.ie && window == window.top) {
1843         (function () {
1844                 try {
1845                         document.documentElement.doScroll('left');
1846                 } catch (e) {
1847                         setTimeout(arguments.callee, 50);
1848                         return;
1849                 }
1850                 hs.ready();
1851         })();
1852 }
1853 hs.addEventListener(document, 'DOMContentLoaded', hs.ready);
1854 hs.addEventListener(window, 'load', hs.ready);
1855
1856 // set handlers
1857 hs.addEventListener(document, 'ready', function() {
1858         if (hs.expandCursor) {
1859                 var style = hs.createElement('style', { type: 'text/css' }, null, 
1860                         document.getElementsByTagName('HEAD')[0]), 
1861                         backCompat = document.compatMode == 'BackCompat';
1862                         
1863                 
1864                 function addRule(sel, dec) {
1865                         if (hs.ie && (hs.uaVersion < 9 || backCompat)) {
1866                                 var last = document.styleSheets[document.styleSheets.length - 1];
1867                                 if (typeof(last.addRule) == "object") last.addRule(sel, dec);
1868                         } else {
1869                                 style.appendChild(document.createTextNode(sel + " {" + dec + "}"));
1870                         }
1871                 }
1872                 function fix(prop) {
1873                         return 'expression( ( ( ignoreMe = document.documentElement.'+ prop +
1874                                 ' ? document.documentElement.'+ prop +' : document.body.'+ prop +' ) ) + \'px\' );';
1875                 }
1876                 if (hs.expandCursor) addRule ('.highslide img', 
1877                         'cursor: url('+ hs.graphicsDir + hs.expandCursor +'), pointer !important;');
1878         }
1879 });
1880 hs.addEventListener(window, 'resize', function() {
1881         hs.getPageSize();
1882 });
1883 hs.addEventListener(document, 'mousemove', function(e) {
1884         hs.mouse = { x: e.clientX, y: e.clientY };
1885 });
1886 hs.addEventListener(document, 'mousedown', hs.mouseClickHandler);
1887 hs.addEventListener(document, 'mouseup', hs.mouseClickHandler);
1888
1889 hs.addEventListener(document, 'ready', hs.getAnchors);
1890 hs.addEventListener(window, 'load', hs.preloadImages);
1891 }