/**
 * imZoom
 * =============================================================================
 * charger les liens vers des images dans un diaporama
 * 
 * @author      Erwan Lefèvre <erwan.lefevre@aposte.net>
 * @copyright   Erwan Lefèvre 2009
 * @license     Creative Commons - Paternité 2.0 France - http://creativecommons.org/licenses/by/2.0/fr/
 * @version     2.4.1 2010-06-02
 * @see			http://www.webbricks.org/bricks/imZoom/
 
 * @compatible	au 28 mai 2010, compatibilité assurée pour :
 *				Firefox, Internet Explorer 5.5+, Opéra, Safari, Chrome 
 */





/* exemples d'utilisation :
  
-	mettre en place sur un lien :

		var target = document.getElementById('target');
		imZoom.applyTo(target);
  
-	mettre en place sur une liste de liens :

		var targets = document.getElementById('container').getElementsByTagName('a');
		imZoom.applyTo(targets); //  comme pour un seul lien
  
-	mettre en place sur tous les liens éligibles de la page :

		imZoom.autoApplyInto();
  
-	mettre en place sur tous les liens éligibles d'un élément donné :

		var container = document.getElementById('container');
		imZoom.autoApplyInto(container);
		
-	pour passer des options :

	-	avec imZoom.applyTo() :
		
		var options = {
			opt1 : val1,
			optn : valn
		}
		imZoom.applyTo(target, options); (options en second paramètre)

	-	avec imZoom.autoApplyInto() :
		
		var options = {
			opt1 : val1,
			optn : valn
		}
		imZoom.autoApplyInto(options, container); (options en premier paramètre)

*/





// raccourci pour document.getElementById()
function byId(id) {
	return document.getElementById(id) ;
}





// raccourci pour [element].getElementsByTagName()
function byTN(tagName,container) {
	container = container ? container : document;
	return container.getElementsByTagName(tagName) ;
}





/**
 * trim
 * =============================================================================
 * supprime les blancs au début et à la fin d'une chaine
 * (cette version de la f° est plus rapide qu'une simple regexp)
 *
 * @param       myString       {object}        l'objet source
 * @param       dest         {object}        l'objet de destination
 * @return      void
 *
 */
String.prototype.trim = function () {
    var str = this.replace(/^\s+/, ''),i;
    for (i = str.length - 1; i >= 0; i--) {
        if (/\S/.test(str.charAt(i))) {
            str = str.substring(0, i + 1);
            break;
        }
    }
    return str;
};





/**
 * transfer
 * =============================================================================
 * retourne un objet contenant les propriétés et méthodes de l'objet /dest/,
 * complétées et/ou écrasées par celles de l'objet /source/
 *
 * @param       source       {object}        l'objet source
 * @param       dest         {object}        l'objet de destination
 * @return      {object}
 *
 */
function transfer (source, dest) {
    var prop, transfered={};
    for ( prop in dest ) { transfered[prop] = dest[prop]; }
    for ( prop in source ) { transfered[prop] = source[prop]; }
    return transfered; 
}





/**
 * addEvent()
 * =============================================================================
 * ajoute la fonction /fn/ à la pile de résolution de l'événement /evenType/ de
 * l'objet /obj/
 * 
 * merci à : http://www.scottandrew.com/weblog/articles/cbs-events
 *
 * @param		{Mixed}				obj			window, ou document, ou un élément HTML
 * @param		{String}			evType		type d'event (click, mouseover, mouseout, etc.…)
 * @param		{String}			fn			la fonction à ajouter
 * @param		{Boolean}			useCapture	"useCapture un booléen : true pour la phase de capture, ou false pour la phase de bouillonnement et la cible. On utilise quasiment toujours la valeur false." (cf : http://www.alsacreations.com/article/lire/578-La-gestion-des-evenements-en-JavaScript.html)
 * 
 * @returns		void
 */
function addEvent (obj, evType, fn, useCapture){
	if (obj.addEventListener) { obj.addEventListener(evType, fn, useCapture); }
	else { obj.attachEvent("on"+evType, fn); }
}







/**
 * remEvent()
 * =============================================================================
 * retire la fonction /fn/ de la pile de résolution de l'événement /evenType/ de
 * l'objet /obj/
 * 
 * merci à : http://www.scottandrew.com/weblog/articles/cbs-events
 * 
 * @param		{Mixed}				obj			window, ou document, ou un élément HTML
 * @param		{string}			evType		type d'event (click, mouseover, mouseout, etc.…)
 * @param		{string}			fn			la fonction à supprimer
 * @param		{boolean}			useCapture	"useCapture un booléen : true pour la phase de capture, ou false pour la phase de bouillonnement et la cible. On utilise quasiment toujours la valeur false." (cf : http://www.alsacreations.com/article/lire/578-La-gestion-des-evenements-en-JavaScript.html)
 * 
 * @returns		void
 */
function remEvent(obj, evType, fn, useCapture){
	if (obj.removeEventListener) { obj.removeEventListener(evType, fn, useCapture); }
	else { obj.detachEvent("on"+evType, fn); }
}









/**
 * preventDefault()
 * =============================================================================
 * permet d'annuler l'effet normal d'un événement sur un élément html
 * (revient à faire un "return false" pour cet événement, en passant par le event-handeler)
 *
 * v1.0 - 2010-05-30
 * 
 * @returns		void
 */


function preventDefault(e){
	e = e||event;
	if (e.preventDefault) { e.preventDefault(); }
	else { e.returnValue = false;  }
}







/** 
 * getKeyCode
 * 
 * retourne le code de la touche pressée, pour l'event /e/.
 *
 * @requires	addEvent
 * 
 * @param		event		e		l'événement courant
 * 
 * @returns		{Integer}
 *
 * =============================================================================
 */
function getKeyCode(e) {
	e = e||event;
	keyCode = e.which || e.keyCode;
	return keyCode;
}

var keyCode;
addEvent(document,'keydown',getKeyCode);





/**
 * setOpacity
 * =============================================================================
 * règle l'opacité d'un élément
 *
 * @param       elem            {element}       l'élément à traiter
 * @param       value           {float}         valeur souhaitée (0=transparent, 1=opaque)
 * @return      string
 *
 */
function setOpacity(elem, value) {
	value = (value == 1)?0.99999:value;

	elem.style.opacity = value;
	elem.style.filter = 'alpha(opacity=' + value*100 + ')';
	elem.style.MozOpacity = value;
	elem.style.KhtmlOpacity = value;
}





/**
 * easeInOut
 * =============================================================================
 * calcule des étapes d'animation douces
 * pompé sur : http://www.hesido.com/web.php?page=javascriptanimation
 *
 * @param       startValue      {float}          valeur de départ (peut être inférieure à celle de fin)
 * @param       endValue        {float}          valeur de fin (peut être supérieure à celle de fin)
 * @param       totalSteps      {integer}        nombre total d'étapes dans l'animation
 * @param       actualStep      {integer}        étapes actuelle de l'animation
 * @param       powr            {float}          "puissance" de la courbe. 1=linéaire et ease-out<1<ease-in. (0.2 et 3 font de belles courbes)
 * @return      integer
 *
 */
function easeInOut(startValue,endValue,totalSteps,actualStep,powr) { 
    var delta = endValue - startValue,
		stepp = startValue+(Math.pow(((1 / totalSteps) * actualStep), powr) * delta); 
    
    //return Math.ceil(stepp) ;
    return stepp;
    }





/** 
 * getPos() 
 * =============================================================================
 * retourne la position (dans la page) de chacun des côtés de l'élément /elem/,
 * dispatché dans un tableau associatif contenant les clés t|b|l|r
 * (la valeur retournée est donnée en pixels)
 * (tient compte des différences de fonctionnement des navigateur)
 *
 * @param           Object          elem            l'élément inspecté
 * @return          Integer         
 * @access          public
 */
function getPos(elem) {
    var pos={'r':0,'l':0,'t':0,'b':0},
		tmp=elem;
    
    // on procède de parent en parent car IE fonctionne comme ça
    // (les autres donnent directement la position par rapport à la page)
    
    do {
        pos.l += tmp.offsetLeft;
        tmp = tmp.offsetParent;
    } while( tmp !== null );
    pos.r = pos.l + elem.offsetWidth;
    
    tmp=elem;
    do {
        pos.t += tmp.offsetTop;
        tmp = tmp.offsetParent;
    } while( tmp !== null );
    pos.b = pos.t + elem.offsetHeight;
    
    return pos;
}





/** 
 * pageDim() 
 * =============================================================================
 * retourne les dimentions de la page
 *
 * @return		{Object}		{'w','h'}
 */
function pageDim() {
	var d = document,
		dE = d.documentElement,
		dB = d.body,
		w, h;
		
	// firefox is ok
	h = dE.scrollHeight;
	w = dE.scrollWidth;
	
	// now IE 7 + Opera with "min window"
	if ( dE.clientHeight > h ) { h  = dE.clientHeight; }
	if ( dE.clientWidth > w ) { w  = dE.clientWidth; }
	
	// last for safari
	if ( dB.scrollHeight > h ) { h = dB.scrollHeight; }
	if ( dB.scrollWidth > w ) { w = dB.scrollWidth; }

	return {'w':w, 'h':h} ;
}





/** scrolled() 
 * =============================================================================
 * retoune les valeurs (horizontale et verticale) de défilement de la fenêtre
 * (en tenant compte du navigateur)
 *
 * @return          Integer         
 *
 * @requires		ieVer.js
 */
function scrolled () {
    var x,y;
    
    // vrais navigateurs
    if ( window.pageXOffset!==undefined) {
        x = window.pageXOffset;
        y = window.pageYOffset;
    }
    // ie
    else {
        x = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : (document.body.scrollLeft?document.body.scrollLeft:0) ;
        y = document.documentElement.scrollTop ? document.documentElement.scrollTop : (document.body.scrollTop?document.body.scrollTop:0) ;
    }
    
    return {'x':x, 'y':y};
}





/** 
 * winDim() 
 * =============================================================================
 * retourne les dimentions intérieurs de la fenêtre
 *
 * @return		{Object}
 */
function winDim() {
	var w,h;
	if ( window.innerWidth ) { // autres que IE
		w = window.innerWidth;
		h = window.innerHeight;
	} else if ( document.documentElement.clientWidth ) { // IE8
		w = document.documentElement.clientWidth;
		h = document.documentElement.clientHeight;
	} else if ( document.body.clientWidth ) { // IE6
		w = document.body.clientWidth;
		h = document.body.clientHeight;
	} else { alert("winDim() n'est pas parvenu à lire les dimentions de la fenêtre"); }

	return {'w':w, 'h':h} ;
}






/**
 * redimArea
 * =============================================================================
 * retourne les mesures d'une surface, après redimentionnement homotétique
 * 
 * @author      Erwan Lefèvre <erwan.lefevre@aposte.net>
 * @copyright   Erwan Lefèvre 2009
 * @license     Creative Commons - Paternité 2.0 France - http://creativecommons.org/licenses/by/2.0/fr/
 * @version     1.1
 */


/** mises à jour :

	-	2010-05-14 :
		-	les arrondis ne sont effectué qu'en toute fin 
		-	les valeurs finales négatives sont ramenées à 0

*/

//compact:true;
		



/**
 * redimArea
 * =============================================================================
 * retourne les mesures /{w,h}/ de /src_w/ et /scr_h/, après redimentionnement homotétique
 * d'après les critères /mesures{max_w, min_w, max_h, max_h}/
 *
 * @param		{Integer}		src_w		largeur de la surface à redimentionner
 * @param		{Integer}		src_h		hauteur de la surface à redimentionner
 * @param		{Object}		mesures		mesures maximales et/ou minimales pour
 *											la largeur et/ou la hauteur
 */
function redimArea (src_w, src_h, mesures) {
	
	// initialisations
		
		// mesures souhaitées
		mesures = typeof mesures == 'object' ? mesures : {};
		var max_w = mesures.max_w,
			min_w = mesures.min_w,
			max_h = mesures.max_h,
			min_h = mesures.max_h,
	
		// calcul du rapport largeur/hauteur de la source
			src_wh = src_w / src_h,
			src_hw = src_h / src_w,
		
		// par défaut, garder les mesures initiales
			height_final = src_h,
			width_final = src_w ;
		
	// redimentionnements
		
		// agrandissement largeur
		if ( width_final < min_w ) {
			width_final = min_w;
			height_final = width_final * src_hw ;
		}
		
		// agrandissement hauteur
		if ( height_final < min_h ) {
			height_final = min_h;
			width_final = height_final * src_wh ;
		}
	
		// réduction largeur
		if ( max_w && (width_final > max_w) ) {
			width_final = max_w;
			height_final = width_final * src_hw ;
		}
	
		// réduction hauteur
		if ( max_h && (height_final > max_h) ) {
			height_final = max_h;
			width_final = height_final * src_wh ;
		}
		
	// valeurs négatives interdites
		width_final = width_final<0 ? 0 : width_final;
		height_final = height_final<0 ? 0 : height_final;
	
	return {
		w : Math.round(width_final),
		h : Math.round(height_final)
	};
}






/**
 * Animate
 * =============================================================================
 * permet d'animer un objet
 *
 * @requires    transfer		http://sitesweb.abondance.free.fr/copyleft/admin/billet-fiche.php?idBillet=21
 * @requires    easeInOut		http://sitesweb.abondance.free.fr/copyleft/admin/billet-fiche.php?idBillet=23
 * @requires    setOpacity		(uniquement pour les animation de la propriété 'opacity' sur des navigateur anciens)
 * @requires    sharp2rgb		(uniquement pour les animation de couleurs)
 * @requires    rgbString2Array	(uniquement pour les animation de couleurs)
 * 
 * @param       options     {object}        les options d'animation (voir détail dans le code source, à la déclaration de this.defSettings)
 * @param       autoStart   {mixed}			indique s'il faut lancer l'animation dès l'instanciation de la classe.
 *											(si autostart est un objet, il sera considéré comme une liste d'options)
 *
 */
function Animate (defSettings, autoStart) {
	
    // options par défaut
    this.defSettings = { 
        "elem" : window.document.body, // element - élement DOM qui sera animé
        "from" : {}, // object - propriétés de style initiales - exemple : {width:'300px', color:'#faa'}
        "to" : {}, // object - propriétés de style finales - exemple : {fontSize:'2em', backgroundColor:'rgb(200,55,0)'}
        "restart" : false, // boolean - si l'animation vient à âtre relancée avant la fin, faut-il la reprendre à zéro
        "ease" : 1, // float - régularité de l'anim. 1=linéaire, et ease-out<1<ease-in
        "duration" : 1000, // integer - durée de l'animation (en millisecondes)
        "frameRate" : 30, // integer - nombre d'étape par seconde dans l'animation
        "onFinish" : false // function - une fonction callback à exécuter à la fin de l'animation
    };
    // chargement des options
    this.defSettings = transfer(defSettings, this.defSettings); 
    
    
    /**
     * guessProp
     * -------------------------------------------------------------------------
     * tente de déterminer la valeur actuelle de la propriété /prop/
     *
     * @param           {string}		prop		le nom de la propriété de style - exemple : 'backgroundColor'
     * @return			{Mixed}
     * 
     */
    this.guessProp = function (prop) {
		var tmp = this.options.elem;
        switch (prop) {
                case 'width': return tmp.offsetWidth+'px';
                case 'height': return tmp.offsetHeight+'px';
					
				// ont recours à d'autres scripts
                case 'top':		return (typeof(getPos)=='undefined') ? '0px' : getPos(tmp,'t')+'px'; 
                case 'bottom':	return (typeof(getPos)=='undefined') ? '0px' : getPos(tmp,'b')+'px'; 
                case 'left':	return (typeof(getPos)=='undefined') ? '0px' : getPos(tmp,'l')+'px'; 
                case 'right':	return (typeof(getPos)=='undefined') ? '0px' : getPos(tmp,'r')+'px'; 
                        
                // valeurs fermes, établies d'après les valeurs par défaut des principaux navigateurs
                case 'borderWidth': return '1px';
                case 'borderColor': return 'rgb(0,0,0)';
                case 'backgroundColor': return 'rgb(255,255,255)';
                case 'color': return 'rgb(0,0,0)';
                case 'opacity': return 1;
                default : return 0;
        }
        return false;
    };
    
    
    
    
    /**
     * readProp
     * -------------------------------------------------------------------------
     * décompose la valeur d'une propriété de style donnée, et retourne le résultat
     * dans un tableau associatif cotenant (selon pertinence) la valeur soumise (.asked),
     * les unités de mesure (.unit), la valeur numérique sans unités (.clean),
     * et un tableau décomposant les valeurs rvb de coloration
     *
     * @param           propValue			{String}		la valeur de la propriété à décomposer
     * @return          {Array}				retourne un tableau associatif décomposant l'intéprétation de la valeur donnée
     * @access          private
     * 
     */
    this.readProp = function (propValue) {
		var decomposed = {},
			isColor;
		
		decomposed.asked = propValue+''; // rappel de la valeur soumise
		decomposed.unit = decomposed.asked.match(/px|em|%/); // unités
		
		decomposed.clean = parseFloat(decomposed.asked); // valeur numérique (çàd sans les unités)           
		
		// gestion des couleurs (seulement si les fonction spécifiques ont été déclarées)
		isColor = decomposed.asked.match(/rgb|(#[a-f0-9]{3,6})/i);
		if ( typeof rgbString2Array=='function' && typeof sharp2rgb=='function' && isColor ) {
			decomposed.clean = decomposed.asked; // pour l'initialisation
			decomposed.rgb = rgbString2Array(sharp2rgb(decomposed.asked)); // conversion des couleurs #ffffff
			if (!decomposed.rgb) { decomposed.rgb = rgbString2Array(decomposed.asked); } // enregistrement des couleurs rgb
		}
        
		return decomposed;    
	};
    
    
    
    
    /**
     * readFromTo
     * -------------------------------------------------------------------------
     * décompose les différentes propriétés listées dans options.from et options.to
     * (soumettre l'une ou l'autre de ces variables comme paramètre de la fonction)
     *
     * @param           set				{Mixed}			les options .to ou .from de l'animation.
     *													accepte de préférence un tableau associatif,
     *													mais peut traiter des chaines proprement rédigée
     * @return          {Object}			retourne un tableau associatif décomposant,
     *										pour chaque propriété déclarée, des mesures
     *										et intéprétations (cf. this.readProp())
     * @access          private
     * 
     */
    this.readFromTo = function (set) {
        
        if (!set) {return false;}
        
        var i, lg, tmp, prop, eachDeclared = {};
        
		// décomposition des déclarations abrégées
		//     (peut-être faudrait-il enlever cette partie, car la syntaxe abrégée
		//     n'est pas très utile et ne colle pas trop aux bonnes pratiques js)
		if ( typeof set == 'string' ) {
			// relevé des différentes propriétés déclarées dans la chaîne
				set = set.trim();
				var declared = set.split(/;/);
				lg = declared.length;
				for (i=0; i<lg; i++) { if (!declared[i]) {declared.splice(i,1);} } // supprimer les lignes vides
				lg = declared.length;
			
			// recomposition de la déclaration sous forme d'objet
				set = {};
				for (i=0; i<lg; i++) {
					tmp = declared[i].split(/:/);
					prop = tmp[0].trim(); // propriété concernée
					set[prop] = tmp[1].trim() ;
				}
		}
		
		// traitement de chacune des propriétés
		for (prop in set) { eachDeclared[prop] = this.readProp(set[prop]+'') ; }
		
        return eachDeclared;
    };
    
    
    /**
     * go
     * -------------------------------------------------------------------------
     * lance l'animation, avec les options /options/
     *
     * @param       options          {Object}       les options pour cette occurence de l'animation
     * 
     */
    this.go = function (options) {
        var tmp, prop;
        
        // chargement des réglages
		this.options = transfer(options?options:{}, this.defSettings);
        
        // préparation de raccourcis
        var st = this.options.elem.style;
        
		// gestion du nombre d'étapes à l'anim
        this.totalFrames = Math.ceil(this.options.duration/1000*this.options.frameRate); // nombre total d'étape dans l'animation
        if (this.options.restart) { this.frameNb = 0; }
        else { this.frameNb = this.framesLeft ? this.framesLeft : 0 ; } // nombre d'étapes déjà effectuées dans l'animation
                
        // décompostions des propriétés de from et to
        this.from = this.readFromTo( this.options.from ? this.options.from : this.defSettings.from );
        this.to = this.readFromTo( this.options.to ? this.options.to : this.defSettings.to );
		
        // compléter les from manquants
        for (prop in this.to) {
			if (!this.from[prop]) {
				if ( st[prop] || st[prop]===0 ) { this.from[prop] = this.readProp(st[prop]); } // par la propriété  de style si déjà définie par js
				if ( !this.from[prop] ) { this.from[prop]=this.readProp(this.guessProp(prop)); } // sinon tâcher de deviner
			}
		}
			   
        // initialiser l'élément animé, tel que déclaré dans .from
        for (prop in this.from) { st[prop] = this.from[prop].clean; }
        
        // lancer le premier frame
        this.next() ;
    };
    
        
    /**
     * frame
     * -------------------------------------------------------------------------
     * exécution d'une étape de l'animation
     * 
     */
    this.frame = function () {
        // raccourci
        var st = this.options.elem.style;
        
        var newVal, coulFrom, coulTo, prop;
        for (prop in this.to) {
				// gestion propriétés de coloration
                if (this.to[prop].rgb) {
                        coulTo = this.to[prop].rgb;
                        coulFrom = this.from[prop].rgb;
                        var r = easeInOut(parseInt(coulFrom[0],10), parseInt(coulTo[0],10), this.totalFrames, this.frameNb, this.options.ease);
                        var g = easeInOut(parseInt(coulFrom[1],10), parseInt(coulTo[1],10), this.totalFrames, this.frameNb, this.options.ease);
                        var b = easeInOut(parseInt(coulFrom[2],10), parseInt(coulTo[2],10), this.totalFrames, this.frameNb, this.options.ease);
                        st[prop] = 'rgb('+Math.round(r)+','+Math.round(g)+','+Math.round(b)+')';
                }
				// gestion des autres types de propriétés
                else {
                        newVal = easeInOut(this.from[prop].clean, this.to[prop].clean, this.totalFrames, this.frameNb, this.options.ease);
						if (this.to[prop].units==='px') { newVal = parseInt(newVal,10); }
                        st[prop] = newVal + this.to[prop].unit;
                        if (prop==='opacity' && typeof setOpacity=='function') { setOpacity(this.options.elem, newVal); } // pour compatibilité
                }
        }
        
        // si anim terminée
        if ( this.frameNb===this.totalFrames ) { 
                if (typeof this.options.onFinish=='function') { setTimeout(this.options.onFinish,1); } // fonction callback
                }
        
        // sinon lancer le frame suivant
        else {
                this.frameNb++;
                this.framesLeft = this.totalFrames - this.frameNb;
                this.next();
        }
    };
        
    /**
     * next
     * -------------------------------------------------------------------------
     * lance le frame suivant
     * 
     */
    this.next = function () {
		this.prog = setTimeout (
			function(){self.frame();},
			1000/this.options.frameRate
		);
	};
        
    /**
     * pause
     * -------------------------------------------------------------------------
     * suspend l'animation
     * 
     */
    this.pause = function () {
		clearTimeout(this.prog);
	};
    
	
	var self = this; // utile dans les méthodes frame et resume, pour pouvoir mettre this dans un setTimeout
	
    if (autoStart) { this.go( typeof autoStart=='object' ? autoStart : {} ); }
}




/**
 * imZoom - v 2.4.1
 * =============================================================================
 * charger les liens vers des images dans un diaporama
 * 
 * @uses		addEvent
 * @uses		byId
 * @uses		byTN
 * @uses		getPos
 * @uses		getKeyCode
 * @uses		pageDim
 * @uses		preventDefault
 * @uses		redimArea
 * @uses		remEvent
 * @uses		scrolled
 * @uses		winDim
 * @uses		Animate
 */
var imZoom= {  
    
    /**
     * gal
     * -------------------------------------------------------------------------
     * liste de lien à traiter dans le diaporama
     */
	gal : [],
    
    /**
     * defaultOpt
     * -------------------------------------------------------------------------
     * tableau associatif des options par défaut
     */
	defaultOpt : {
		screenColor : '#fff',           // couleur du fond
		screenOpacity : 0.6,            // opacité du fond
		zIndex : 1000,					// propriété css zIndex appliquée aux éléments de imZoom
		openDuration : 500,             // durée de l'animation d'ouverture (en millisecondes)
		changeDuration : 150,           // durée de l'animation de redimentionnement (en millisecondes)
		frameRate : 25,                 // nombre d'étapes par seconde dans les animations
		animEase : 2,                   // effet d'animation : 1=linéaire, ease-out<1<ease-in
		onOpen : 0,						// fonction callback, à l'ouveture (à la fin de l'animation)
		onChange : 0,					// fonction callback, au changement d'image (à la fin de l'animation)
		onClose : 0,					// fonction callback, à la fermeture
		showNav : 1,					// indique d'afficher ou non la barre de navigation
		showTitle : 1,					// indique d'afficher ou non le titre du lien
		prevTxt : '&lt;&lt;',			// texte pour le bouton "prev"
		nextTxt : '&gt;&gt;',			// texte pour le bouton "next"
		playTxt : 'diaporama',			// texte pour le bouton "play"
		pauseTxt : 'pause',				// texte pour le bouton "pause"
		slideDelay : 4000,				// temporisation entre deux images dans le diaporama (en millisecondes)
		autoPlay : 0,					// indique de lancer ou non un diaporama dès l'ouverture
		preloaderUrl : 'loading.gif',   // url de l'image indiquant un chargement en cours
		margins : 30,                   // marges autour de l'image
		maxSize : 1                     // indique s'il on peut ou non agrandir l'image au dela de sa taille normale
	},
    
    /**
     * applyTo()
     * -------------------------------------------------------------------------
     * applique l'effet à un élément ou une liste d'éléments (sans vérifier leur éligibilité)
     *
     * @param		mixed		actionners		un élement html (ou un liste d'éléments html) sur lequel mettre en place l'effet
     * @param		object		options			(facultatif) les options à appliquer pour cet (ou ces) élément(s)
     */
	applyTo : function (actionners, options) {
		if (!actionners.length) { actionners = [actionners]; }
		this.gal = actionners;
		var actNb = actionners.length,
			i;
		for (i=0; i<actNb; i++) {
			actionners[i].onclick=function() {
				imZoom.go(this, options);
				return false;
			};
		}
	},  
    
    /**
     * autoApplyInto()
     * -------------------------------------------------------------------------
     * tente de mettre en place l'effet sur tous les liens éligibles à l'intérieur de la page ou d'un élément donné
     *
     * @param		object			options			(facultatif) les options à appliquer pour cet élément
     * @param		htmlElement		container		(facultatif) l'élement au sein duquel on cherchera des liens éligible pour leur appliquer l'effet. (par défaut : document)
     */
	autoApplyInto : function (options, container) {
		var liens = byTN('a', container);
		var nbLiens = liens.length;
		var i, n=0;
		var list=[];
	    for (i=0; i<nbLiens; i++){
				if ( /\.(jpe?g|gif|png|tiff?)/i.test(liens[i].href) ) { list[n]=liens[i]; n++; }
	    }
		imZoom.applyTo(list, options); 
	}, 
    
    /**
     * keyNav()
     * -------------------------------------------------------------------------
     * navigation au clavier
     * ne fonctionne pas sous ie !!!
     */
	keyNav : function (e) {
		// var keyCode = getKeyCode(e); déjà effectué dans getKeyCode.js
		switch (keyCode) {
			case 37 : imZoom.prev(); break; // gauche
			case 39 : imZoom.next(); break; // droite
			case 27 : imZoom.close(); break; // escape
			case 32 : // espace
			case 13 : // entrée
				if (imZoom.prog) { imZoom.pause(); }
				else { imZoom.play(); }
				break; 
		}
		preventDefault(e);
	}, 
    
    /**
     * goTo()
     * -------------------------------------------------------------------------
     * lancer le chargement d'une des images, désignée par son numéro
     * les numéros trop grands renvoient à la dernière image
     * les numéros trop petits renvoient à la première image
     *
     * @param		integer			n			(facultatif) le numéro de l'image à charger (par défaut : la suivante)
     */
	goTo : function (n) {
		n = n>this.gal.length-1 ? 0 : (n<0?this.gal.length-1:n); // boucler en cas de dépassement du nb d'images
		this.active = n;
		this.go(this.gal[n]);
	}, 
    
    /**
     * prev()
     * -------------------------------------------------------------------------
     * lancer le chargement de l'image précédente
     */
	prev : function () {
		imZoom.goTo(imZoom.active-1);
	}, 
    
    /**
     * next()
     * -------------------------------------------------------------------------
     * lancer le chargement de l'image suivante
     */
	next : function () {
		imZoom.goTo(imZoom.active+1);
	}, 
    
    /**
     * progImg()
     * -------------------------------------------------------------------------
     * programme le chargement d'une image
     *
     * @param		integer			imgNb			(facultatif) le numéro de l'image à charger (par défaut : la suivante)
     */
	progImg : function (imgNb) {
		imgNb = imgNb===undefined ? imZoom.active+1 : imgNb;
		imZoom.prog = setTimeout(
			function(){ imZoom.goTo(imgNb); },
			imZoom.options.slideDelay
		);
	},
    
    /**
     * play()
     * -------------------------------------------------------------------------
     * lance un diaporama, à partir de l'image active
     */
	play : function () {
		if (!imZoom.prog) {
			this.progImg();
			byId('izPlay').style.display = 'none';
			byId('izPause').style.display = '';
		}
	},
    
    /**
     * pause()
     * -------------------------------------------------------------------------
     * interromp le diaporama
     */
	pause : function () {
		clearTimeout(imZoom.prog);
		imZoom.prog = 0;
		byId('izPause').style.display = 'none';
		byId('izPlay').style.display = '';
	},
    
    /**
     * callback()
     * -------------------------------------------------------------------------
     * appelle une des fonctions callback passées en options
     */
    callback : function (functionName) {
		if (typeof imZoom.options[functionName]==='function' && byId('izImg')){imZoom.options[functionName]();}
	},
    
    /**
     * change()
     * -------------------------------------------------------------------------
     * lance l'animation de changement d'image
     */
	change : function () {
		this.animCont.go({
			// disparition en fondu
			from:{opacity:1}, to:{opacity:0},
			
			// puis changement d'image et réapparition
			onFinish:function() {
				var img = byId('izImg');
				if (img) { // une condition, des fois qu'on ait fermé imZoom entre temps
					
					// programmer la réapparition de l'image
					img.onload = function() {
						imZoom.setPos(); // pour ajuster la barre de nav à l'image
						var izTitle = byId('izTitle');
						if (izTitle && imZoom.options.showTitle) { izTitle.innerHTML = imZoom.gal[imZoom.active].title; }
						imZoom.animCont.go({
							to:{opacity:1},
							onFinish:function(){
								imZoom.callback('onChange');
								if (imZoom.prog) {
									clearTimeout(imZoom.prog);
									imZoom.prog=0;
									imZoom.progImg();
								}
							}
						});
						
					};
					
					// changer l'image source
					img.src = imZoom.gal[imZoom.active].href; 
					
				}
			}
		});
	},
    
    /**
     * go()
     * -------------------------------------------------------------------------
     * charge l'image voulue, après avoir ouvert imZoom s'il ne l'était déjà
     */
	go : function (actionner, options) {
		
		var i=0,opt; // pointeurs, dans des boucles
		var area; // la zone occupée par le lien
		var loading; // image de preloader
		var loadingPos; // zone dans laquelle centrer le preloader
		var izCont = byId('izCont'); // le conteneur de l'image (existe si imZoom déjà ouvert)
		
		// réservé à l'ouverture initiale :
		if(!izCont) {
			// lecture des options et mise en place des options par défaut
			options = typeof options==='object' ? options : {};
			for (opt in this.defaultOpt ) { if (typeof options[opt]==='undefined') { options[opt]=this.defaultOpt[opt]; } }
			this.options = options; // mémorisation
			
			// relever le n° de l'image qu'on est en train d'ouvrir
			while (this.gal[i]!==actionner && i<this.gal.length) { i++; }
			this.active = i;
		}
		options = this.options;
		
		// déterminer la surface occupée par le lien
		area = actionner.firstChild.nodeType===3 ? actionner : actionner.firstChild; // pas toujours pertinent, mais souvent
			
		// mise en place du preloader
			loading = byTN("body")[0].appendChild(document.createElement("img"));
			loading.id='izLoading';
			loading.src=options.preloaderUrl;
			loading.style.position='absolute';
			loading.style.zIndex = options.zIndex;
			loadingPos = izCont ? izCont : area; // selon qu'imZoom est déjà ouvert ou non, on se réfère à l'actionner ou à la gde image
			loading.style.top = (loadingPos.offsetHeight-loading.offsetHeight)/2 + getPos(loadingPos).t +'px'; // centré dans la hauteur
			loading.style.left = (loadingPos.offsetWidth-loading.offsetWidth)/2 + getPos(loadingPos).l +'px'; // centré dans la largeur
			
		// lancer le chargement de la gde image par ajax, et programmer son affichage
		
			// mémoriser les paramètres
			this.actionner = actionner;
			this.area = area;
			
			// abandonner les éventuelles requêtes en cours
			if (this.xhr) {this.xhr.abort();}
			
			// créer l'objet xhr
				this.xhr = null; 
				if (window.XMLHttpRequest) { this.xhr = new XMLHttpRequest(); } // Navigateurs dignes de ce nom
				else if(window.ActiveXObject){ // Internet Explorer 
					try { this.xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
					catch (e) { this.xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
				}
				else { this.xhr = false; alert("Votre navigateur ne supporte pas les objets XMLHTTPRequest..."); } 
						   
			// lancer la requête xhr
				this.xhr.onreadystatechange=function() {
					if(imZoom.xhr.readyState===4) {
						imZoom.getImgDims();
					} 
				};
				this.xhr.open( 'GET' , this.actionner.href , true ) ;
				this.xhr.send(null) ;
				
			// rendre possible l'annulation
				loading.onclick=function(){imZoom.close();};
	},
	
    
    /**
     * getImgDims()
     * -------------------------------------------------------------------------
     * relève les mesures de l'image et lance son affichage
     */
	getImgDims : function(){
		
		// création d'une image temporaire (invisible)
		var izTmp = byTN('body')[0].appendChild(document.createElement("img"));
		izTmp.id='izTmp';
		izTmp.style.visibility='hidden';
		izTmp.style.postition='absolute';
		izTmp.style.top=izTmp.style.left='0px';
		
		// actions sur cette image :
		izTmp.onclick = this.close;
		
		izTmp.onload = function(){
			// prise des mesures de l'image
			imZoom.imgSize = {
				w : izTmp.offsetWidth,
				h : izTmp.offsetHeight
			};
				
			// suppression de l'image temporaire et du preloader
			var body = byTN("body")[0];
			body.removeChild(byId('izTmp'));
			body.removeChild(byId('izLoading'));
			
			// affichage de l'image dans imZoom
			if(!byId('izCont')) { imZoom.open(); }
			else { imZoom.change(); }
		};
		izTmp.src=this.actionner.href;
	},
	
    
    /**
     * getImgDim()
     * -------------------------------------------------------------------------
     * relève les mesures max pour l'image courante
     */
	getImgDim : function(){
		
		var opt = this.options,
			maxSize = opt.maxSize,
			izMeta = byId('izMeta'),
			imgSize = imZoom.imgSize,
			winDims = winDim(),
			scroll = scrolled(),
			maxSizeDims,
			ret;
			
		// calculer et retourner les dimentions limites pour l'image
		maxSizeDims = {
			w : winDims.w - opt.margins*2,
			h : winDims.h - opt.margins*2
		};
		ret = redimArea (imgSize.w, imgSize.h, {
			max_w : maxSize ? Math.min(maxSizeDims.w,imgSize.w) : maxSizeDims.w,
			max_h : maxSize ? Math.min(maxSizeDims.h-izMeta.offsetHeight,imgSize.h) : maxSizeDims.h-izMeta.offsetHeight
		});
		
		ret.l = Math.ceil((winDims.w - opt.margins - ret.w)/2) + scroll.x;
		ret.t = Math.ceil((winDims.h - opt.margins - ret.h)/2) + scroll.y;
		
		return ret;
	},
    
	
	
    /**
     * setPos()
     * -------------------------------------------------------------------------
     * (re)positionne les éléments de imZoom dans la fenêtre
     */
    setPos : function () {
		
		var izCont = byId('izCont');
		
		// si on a effectivement une image d'ouverte
		if (izCont) {
			// raccourcis
			var izScreen = byId('izScreen'); // je ne prends pas directement le style car ça fait boguer ie
			var izMeta = byId('izMeta');
			var izImg = byId('izImg');
			var winScroll = scrolled();
			var dims = winDim();
			var pageDims = pageDim();
			var opt = imZoom.options;
			var imgPos; // position de l'image, sera déterminté plus loin
			
			// ajustement de l'écran
			// (le redimensionnement de la fenêtre peut modifier les dimensions de la page)
			izScreen.style.width=pageDims.w+'px';
			izScreen.style.height=pageDims.h+'px';
			
			// ajustement du conteneur
			var mes = imZoom.getImgDim();
			izCont.style.width = mes.w + 'px';
			izCont.style.height = mes.h + 'px';
			izCont.style.top = mes.t+'px';
			izCont.style.left = mes.l+'px';
			
			// repositionner la nav
			imgPos = getPos(izImg);
			izMeta.style.top= imgPos.b + 'px';
			izMeta.style.width=izImg.offsetWidth+'px';
			izMeta.style.left=imgPos.l+'px';
		}
    },
    
    /**
     * close()
     * -------------------------------------------------------------------------
     * ferme la imZoom
     */
    close : function () {
		remEvent(document, 'keydown', imZoom.keyNav);
							
		var body = byTN("body")[0];
		var	izMeta = byId('izMeta');
		var	izCont = byId('izCont');
		var	izTmp = byId('izTmp');
		var	izLoading = byId('izLoading');
		
		// arrêter ce qui est en cours
		imZoom.xhr.abort();
		if (imZoom.prog) { imZoom.pause(); }
		
		// détruire les éléments html
		if (izMeta) { body.removeChild(izMeta); }
		if (izTmp) { body.removeChild(izTmp); }
		if (izLoading) { body.removeChild(izLoading); }
        body.removeChild(byId('izScreen'));
		izCont.style.display='none'; // le temps de faire le callback, qui vérifie l'existence de #izImg
		
		// callback
		imZoom.callback('onClose');
		
		// on détruit pour de bon
		body.removeChild(izCont);
    },
    
    /**
     * open()
     * -------------------------------------------------------------------------
     * affiche la grande image dans un calque de premier plan
     */
    open : function () {
		// si on a pas déjà une image ouverte
		if (!byId('izCont')) {
			
			// raccourcis et déclarations de variables
			var winScroll = scrolled();
			var dims = winDim();
			var pageDims = pageDim();
			var opt = imZoom.options;
			var body = byTN("body")[0];
			var actPos = getPos(this.area);
				
			var izScreen; // l'écran modal
			var izMeta; // le conteneur du titre et de la nav
			var izCont; // le conteneur de l'image
			var izImg; // l'image
				
			var animScreen; // programme d'animation de l'écran
			
			// création d'un écran modal
				izScreen = body.appendChild(document.createElement("div"));
				izScreen.id='izScreen';
				izScreen.style.position='absolute';
				izScreen.style.backgroundColor=opt.screenColor;
				izScreen.style.top='0px';
				izScreen.style.left='0px';
				izScreen.style.width=pageDims.w+'px';
				izScreen.style.height=pageDims.h+'px';
				izScreen.onclick = this.close;
				izScreen.style.zIndex = opt.zIndex;
				
			// création d'une div qui contiendra l'image (plus commode, pour de probables développements ultérieurs)
				izCont = body.appendChild(document.createElement("div"));
				izCont.id='izCont';
				izCont.style.position='absolute';
				izCont.style.textAlign='center';
				izCont.style.width='100%';
				izCont.onclick = this.close;
				izCont.style.zIndex = opt.zIndex;
			
			// création de l'image dans la div
				izImg = byId('izCont').appendChild(document.createElement("img"));
				izImg.id='izImg';
				izImg.src=this.actionner.href;
				izImg.style.height='100%';
			
			// création de la barre de nav
				izMeta = body.appendChild(document.createElement("div"));
				izMeta.id = 'izMeta';
				izMeta.style.display = 'none'; // masquée pour le moment
				izMeta.style.position='absolute';
				izMeta.style.zIndex = opt.zIndex;
				if (opt.showTitle) { izMeta.innerHTML = '<span id="izTitle">'+this.gal[this.active].title+'</span>'; }
				if (opt.showNav && this.gal.length>1) {
					izMeta.innerHTML += '' +
					'<span id="izNav">' +
					' <a href="#" id="izPrev" onclick="imZoom.prev();return false;">'+opt.prevTxt+'</a>' +
					' <a href="#" id="izPlay" onclick="imZoom.play();return false;">'+opt.playTxt+'</a>' +
					' <a href="#" id="izPause" style="display:none;" onclick="imZoom.pause();return false;">'+opt.pauseTxt+'</a>' +
					' <a href="#" id="izNext" onclick="imZoom.next();return false;">'+opt.nextTxt+'</a>' +
					'</span>';
				}
			
			// relevé de mesure pour animations
			if ( (opt.showNav||opt.showTitle)) {izMeta.style.display='';}
			var mes = imZoom.getImgDim();
			izMeta.style.display='none';
			
			// animations
				animScreen = new Animate({elem:izScreen,to:{opacity:opt.screenOpacity},from:{opacity:0},duration:opt.openDuration,ease:opt.animEase,frameRate:opt.frameRate},1);
				this.animCont = new Animate({elem:izCont,duration:opt.changeDuration,ease:opt.animEase,frameRate:opt.frameRate});
				this.animCont.go({
						duration:opt.openDuration,
						to:{
							width:mes.w + 'px',
							height:mes.h + 'px',
							top:mes.t+'px',
							left:mes.l+'px'
							},
						from:{
							width:this.area.offsetWidth+'px',
							height:this.area.offsetHeight+'px',
							top:actPos.t+'px',
							left:actPos.l+'px'
						},
						onFinish:function() {
							if ( (opt.showNav||opt.showTitle) && byId('izMeta')) {byId('izMeta').style.display='';}
							imZoom.setPos();
							if (imZoom.options.autoPlay) {imZoom.play();}
							addEvent(document, 'keydown', imZoom.keyNav);
							imZoom.callback('onOpen');
						}
				});
		}
    }
};


// en cas de scroll ou redimentionnement de la fenêtre, repositionner l'agrandissement
addEvent(window, 'resize', imZoom.setPos);
addEvent(window, 'scroll', imZoom.setPos);
