// ### JAVASCRIPT CODE USED BY ALL PAGES ###

/* most DHTML Library functions (e.g. lines 35-515) written by Adrian Neilson Hall (ANH); on DOMContentLoaded code by Edwards, Millar and Resig with IE adaptation by ANH; various other functions by RSVP Web Developer team - ANH, David Connard, Radu Vissarion, Dennis Moorthi, Lucas Nelson, Neo Li and Simon Kerle */
/* note re setAttribute' method - IE5/6/7 has issues ' with certain attributes, e.g.  'marginwidth' and 'class' */
/* note re 'length' - in an array of 7 items, the length is '7' which is one more than the number corresponding to the last item in the array */

/* a DHTML library */
/* browser/device sniffer code */
var agt = navigator.userAgent.toLowerCase();
var is_opera = (agt.indexOf("opera")!=-1);
var is_ie = ((agt.indexOf('msie')!=-1) && !is_opera);
var ieNum = is_ie ? parseInt(agt.split('msie')[1]) : -1;
var ffNum = agt.indexOf('firefox')!=-1 ? parseFloat(agt.split('firefox/')[1].substring(0,3)) : -1;
var is_ie6down = (is_ie && ieNum<=6);
var is_ie7down = (is_ie && ieNum<=7);
var is_ie8down = (is_ie && ieNum<=8);
var is_webkit = (agt.indexOf('applewebkit')!=-1); // Safari & Google Chrome (/WebKit/i.test(navigator.userAgent))
var is_moz = ((agt.indexOf('mozilla/5')!=-1) && (agt.indexOf('gecko/')!=-1)); // Mozilla
var is_g19 = (is_moz && (agt.indexOf('rv:')!=-1) && (parseFloat(agt.split('rv:')[1].substring(0,3))>=1.9)); // Gecko 1.9+
var iphone = agt.match(/iphone/i) || agt.match(/ipod/i);
var ipad = navigator.userAgent.match(/\biPad\b/);
var smartphone = iphone || agt.match(/android/i) || agt.match(/windows phone os 7/i);
var iOS5 = navigator.userAgent.match(/OS [56789]_[\d_]* like Mac OS X/i);

/* TODO - now redundant; remove from other js docs: */var dhtmlBrowser = document.getElementById;

(function () {
    var ajaxSetupOptions = { traditional:true, headers: { "X-RSVP-Analytics": "off" } };
    if (is_ie) ajaxSetupOptions.contentType = "application/x-www-form-urlencoded; charset=UTF-8";
    $.ajaxSetup(ajaxSetupOptions);
})();

// set an alternate base script domain if we are non-secure (don't want scripts coming from different hosts in secure pages) and if a we're www host request
var scriptDomain = '';
if (location.protocol == 'http:' && location.hostname.indexOf('www.') == 0) {
	var port = (location.port && location.port != '' &&  location.port != 80) ? location.port : null;
	scriptDomain = 'http://resources' + location.hostname.substring(3) + (port ? ':' + port : '');
}

// determine whether browser supports a particular CSS property; for example of usage, see 'var radius' below;
var getStyleProperty = (function(){
  var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'Ms'];
  function getStyleProperty(propName, element) {
    element = element || document.documentElement;
    var style = element.style,
        prefixed;
    // test standard property first
    if (typeof style[propName] == 'string') return propName;
    // capitalize
    propName = propName.charAt(0).toUpperCase() + propName.slice(1);
    // test vendor specific properties
    for (var i=0, l=prefixes.length; i<l; i++) {
      prefixed = prefixes[i] + propName;
      if (typeof style[prefixed] == 'string') return prefixed;
    }
  }
  return getStyleProperty;
})();

var radius = typeof getStyleProperty('borderRadius') == 'string'; // so: if (!radius) {round corners via js...}
var radiusPlus = (!is_moz && radius) || is_g19;
var cols = typeof getStyleProperty('columnCount') == 'string';
var textOverflow = typeof getStyleProperty('textOverflow') == 'string';

/* start ### jQuery ### functions */
			
// for smart phones - iPhone (& iPod touch), android and windows phone os 7 only
$(function() {
	if (smartphone) {
		// for specified pages only, serve globalHandheld.css; (NB: this is required by simulator)
		/*var ids = ['contact_pairedContactHistory','contact_userBasedContact','contact_sendBulkKissReply','contact_sendKiss','contact_sendKissReply','events_index','help_howItWorks','help_datingTips','help_content','help_index','mail_inbox','mail_outbox','profile_display','registration_signUp','registration_createProfile','root_index','root_menu','root_login-input','root_login-success','root_logout-success','root_memberHome','root_error','search_name-search-input','search_search-input','search_search-success','stamps_buy','stamps_contact','success_list','test_index','test_index02','test_index03','test_index04','test_index05','test_index06','test_index07','test_index08','test_index09','test_fixed','user_updateCommunicationSettings','user_updateMatchingSettings','user_updateMobileSettings','user_updateMyDetails','user_updateVisibilitySettings'];
		$.each(ids,function() {
			$('body#'+this).parent().find('link[media="handheld"]').attr({'media':'screen','disabled':false});
			return ($('body').attr('id') != this); // stop running after body id match
		});*/
		$("#useful-links").append("<div class='tac cf st'><a href='/m'>View mobile site</a></div>");

		$(document.head || document.getElementsByTagName('head')[0]).find('link[media="handheld"]').attr({'media':'screen','disabled':false});
		// TEMP (remove when handheld css universal); fix strange font-size issue in white strip
		var hhss = $('head > link[href*="globalHandheld.css"]');
		if (!hhss.length) {
			$('#networkStripTop > .rsvp').css({'font-size':'.75em'});
		}
		/*// for iphone only - download iphone app
		if (iphone) {
			// link to iTunes store:
			var appLink = $('.header a[href*="itunes"]').eq(0);
			// if cookie 'showAppLink' doesn't exist, create it with value 'yes'
			if ($.cookie("showAppLink")==null) {
				$.cookie("showAppLink","yes");
			}
			// if cookie does exist with value 'yes'
			if ($.cookie("showAppLink")=="yes") {
				// show, then embellish link to download iPhone app: wrap in div.m.iphone.app and insert 'close' trigger
				appLink.removeClass('js-h').wrap('<div data-type="iphone" class="box point top m p5" />').before('<strong class="pc l ts crp di p5 mr10">X</strong>');
				// when iphone div [either 'X' or 'download' link] is clicked, kill it and set cookie 'showAppLink' to 'no' 
				$('.header div.iphone.app').click(function() {
					$(this).remove();
					$.cookie("showAppLink","no");
				})
			}
			// if cookie does exist with value 'no'
			else if ($.cookie("showAppLink")=="no") {
				// remove iphone app link
				appLink.remove();
			}
		}*/
		// enable different nav styling if member is logged in
		if (!isMember) {
			$('body').addClass('non-member');
		}
		// shorten too long inputs
		$(':text[size="50"]').attr('size', '30');
	}
});

/*// enable when all layouts are set by global.css;
// serve specific css to iPhone (if the media attribute fails to work as expected; e.g. with emulator)
$(function() {
	if (smartphone) {
		$('link[media="handheld"]').attr('media','screen');
	}
});*/

/* IE9 style fixes */
$(function() {
	if (is_ie && (ieNum==9)) {
		// deal with IE9's inability to apply border-radius to :after content
		$('.type-D > :header:first').addClass('pb5').after($('<span class="db m m0"><span class="db w" style="border-top-left-radius:12px; border-top-right-radius:12px; height:13px"></span></span>'));
	}
});

/* FF3.6- upgrade notice */
$(function() {
	// exclude smart phones, browsers other than FF, and FF4+; also - do not run in iframe
	if (!smartphone && ffNum!=-1 && ffNum<4 && !$('#registration_signUpIframe').length && !$('#root_login-iframe').length) {
		// if cookie 'showUpgradeCall' doesn't exist, or exists with value 'yes'
		if ($.cookie('showUpgradeCall')==null || $.cookie('showUpgradeCall')=="yes") {
			// create a call to upgrade (with browser links and 'close' trigger)
			var upgradeCall = $('<div class="p5 cm m10 ca">' +
				'<div class="fl">' +
					'<img class="fl" src="/images/core/alert_18x18.png" alt="alert" />' +
					'<h4 class="xc mb5">You are using an outdated browser, preventing some features on this site from working.</h4>' +
					'<p class="ml20">For a faster, safer experience, please select one of these browsers to upgrade for free today:</p>' +
				'</div>' +
				'<div class="fr">' +
					'<span class="db xc ar crp">Close [X]</span>' +
					'<a href="http://windows.microsoft.com/en-US/internet-explorer/products/ie/home" target="_new"><img src="/images/ie6/IE.png" alt="Internet Explorer" /></a>' +
					'<a href="http://www.mozilla.com/firefox/" target="_new"><img src="/images/ie6/firefox.png" alt="Mozilla Firefox" /></a>' +
					'<a href="http://www.google.com/chrome/" target="_new"><img src="/images/ie6/chrome.png" alt="Google Chrome" /></a>' +
				'</div>' +
			'</div>');
			// insert call to upgrade before main content; on clicking upgrade, remove it and set cookie value to 'No'
			// cookie must expire in 30 days
			$('#rsvpcontent').before(upgradeCall.click(function() {
				$(this).remove();
				$.cookie('showUpgradeCall', 'no', { expires: 30, path: '/' });
			}));
		}
	}
});

/* on DOM ready, overcome CSS3 deficiencies & ### ROUND CORNERS ### for browsers which can't do it through CSS*/
$(function() {
	if (!radius) {
		setUpRoundCorners($('body'));
		// overcome CSS3 deficiencies
		$('.links > :first-child').addClass('first-child');
		$('.links > dt, .links > span:first-child').next().addClass('first-of-type');
		// enable zebra striping
		$('.zebra tr:odd').addClass('x');
		// for IE7, mimic :after
		if (ieNum==7) {
			$('#rsvpheader .nav').append('<div class="js fill"/>');
		}
	}
	// emulate pseudo-class :target for IE8-
	if (is_ie8down && ($('.light').length || $('.tab-group').length)) {
		// loop through all links with # href, and attach setTarget() on click
		$('a[href*="#"]').click(function() {
			setTimeout(setTarget, 1);
		});
		// light-box in IE7 only: issues with borders of following siblings showing through mask, unless shifted to body
		if (is_ie7down) {
			$('.light').appendTo('body');
		}
		// when clicking any link with # href,
		var setTarget = function() {
			// remove all instances of class 'target'
			$('.target').removeClass('target');
			// if the page's URL has a hash, add class 'target' to any element with id matching that hash
			if (location.hash.substr(1) != "") {
				$(location.hash).addClass('target');
			}
		}
	}
	// fix vaiours css deficiencies in IE7
	/*if (is_ie7down) {
		$('[data-type=tally]').each(function () {
			$(this).text(function (i, t) {
				return '(' + t + ')';
			});
		});		
	}*/
});

/* for IE8-, round corners (& insert title decor) for certain element types, within specified container */
function setUpRoundCorners(container) {
	if (!radius) {
		// add .action class to simplify globalIE.css
		container.find('.actions:not(.v1) > a').each(function() {
			$(this).addClass('action');
		});
		// round all 4 corners (in specified selectors)
		$.each(['.c4','.type-B','.type-C','.type-D','.type-E','.type-G','.box','.important','.action','button','input[type=submit]'], function(i,v) {
			container.find(v).each(function() {
				roundCorners($(this));
			});
		});
		// round top 2 corners only (in specified selectors)
		$.each(['.c2','.type-A','.tabs li'], function(i,v) {
			container.find(v).each(function() {
				roundCorners($(this),['tl','tr']);
			});
		});
		// round single corners
		$.each(['tl','tr','br','bl'], function(i,v) {
			container.find('.c-'+v).each(function() {
				roundCorners($(this),[v]);
			});
		});
		// add title decor
		container.find('.type-D').each(function() {
			doTitleDecor($(this));
		});
	}
}

function roundCorners(node,arr) {
	if (node) {
		// do not proceed if button.nb
		if ((node.is('button') || node.is('input[type=submit]')) && node.hasClass('nb')) {return;}
		// wrap buttons in span.action (cnrs will not sit properly otherwise), then make .action the 'node'
		if ((node.is('button') || node.is('input[type=submit]')) && !node.hasClass('nb')) {
			// if the button is disabled, add class 'x' to parent
			var enabledStr = node.is(':disabled') ? 'x ' : '';
			node.wrap('<span class="action ' + enabledStr + node.attr('class') + '" />');
			node = node.removeAttr('class').parent();
		}
		// add class to set 'position:relative'
		if (!node.hasClass('pr') && !node.hasClass('pa')) {node.addClass('pr');}
		// insert corner spans; note - if no array is passed as a parameter, all 4 will be inserted
		var cnrs = (arr==null) ? ['tl','tr','bl','br'] : arr;
		$.each(cnrs, function(i,v) {
			node.append($('<span class="cnr ' + v + '"/>'));
		});
	}
}

// ### remove corners ### in specified node; e.g. removeCorners($('#at-a-glance'))
// optional - specify an array of corners e.g. removeCorners($('#at-a-glance'),['bl','br']); otherwise all 4 will be removed
function removeCorners(node,arr) {
	if (node) {
		var cnrs = (arr==null) ? ['cnr'] : arr;
		$.each(cnrs, function(i,v) {
			node.find('> .' + v).remove();
		});
	}
}

// ### add decorative elements ### below title of specified element
function doTitleDecor(node) {
	if (node) {
		node.find(':header').eq(0).addClass('mb0').after($('<div class="decor pr"><span class="pa left"></span><span class="pa right"></span></div>'));
	}
}

// ### COLUMNS ###; if browser doesn't understand CSS 'columnCount' property, enable column formation via js instead;
// in each .col-2 (or -3 or -4) node, extract number from class, create column divs and distribute children evenly
$(function() {
	if (!cols) {
		$('[class*="col-"]').each(function() {
			var num = parseInt($(this).attr('class').split('col-')[1]);//.split[0]
			//alert('the number of columns is ' + num + '; the class string is ' + classStr);
			doColumns($(this),num);
		});
	}
});

// ### PROFILE NAVIGATION ### - via conversations or search results 
// shift any purple nav bar which is a child of #rsvpcontent, before #rsvpcontent
// remove purple nav bar if it's empty
$(function() {
	if(!smartphone){
		var pNav = $('#rsvpcontent > nav.p.p5').eq(0);
		pNav.addClass('m30').css({'margin-bottom':-10}).insertBefore($('#rsvpcontent'));
		if (!pNav.children().length || pNav.text().trim()=='') {
			pNav.remove();
		}
	}
});

// ### POP-UP TOOL TIPs ###
$(function() {
	// mytype: create a bigger, brighter tool-tip from title attribute
	if ($('#rsvpheader .nav .mytype-m').length) {
		var mLi = $('#rsvpheader .nav .mytype-m').eq(0);
		var mDiv = mLi.find('div');
		// if there is no div in mytype li
		if (!mDiv.length) {
			// append it, style it, and copy text from title of image
			mDiv = $('<div class="box md pc p2-5 s sh ttn pa r0 mr5 z5 js-h"/>').text(mLi.find('img').attr('title')).appendTo(mLi);
		}
		//mDiv.css({top:mLi.height()+12, right:5, padding:"2px 5px"}); // not needed; now in global.js for non-js-users
		mLi.find('img').removeAttr('title').mouseenter(function() {mDiv.removeClass('js-h');}).mouseleave(function() {mDiv.addClass('js-h');});
	}
	// kampyle: shift link to #rsvpheader (if a subnav is open)
	var subNav = $('#rsvpheader > .nav > ul > li > ul:visible');
	if (subNav.length) {
		$('#kampylink').addClass('pa r0').removeClass('fr').appendTo(subNav.eq(0));
	}
	// remove corners and fix positioning for IE8-
	if (!radius) {
		$('#kampylink').removeClass('pr c4').find('.cnr').remove();
	}
});

// on DOM ready, run tool tip function (below) on body
$(function() {
	doToolTipsIn($('body'));
});

// create a bigger, brighter tool-tip from any conteNt with class 'tip'
function doToolTipsIn(myObject) {
	// firstly, deal with help tips & associated 'more' links, as well as 'edit' links, in struts code
	// shift any element with class 'js-m' (javascript move), into the previous element;
	myObject.find('.js-m').each(function() {
		var prev = $(this).prev();
		// if previous element is a div, append element to that
		if (prev.is('div')) {
			prev.append($(this));
		}
		// if previous element is a fieldset,
		else if (prev.is('fieldset')) {
			// if there is no child div in fieldset, wrap everything except legend in a div
			if (!prev.children('div').length) {
				prev.children().not('legend').wrapAll('<div/>');
			}
			// if element is an edit link, append it to first child of fieldset (either legend or strong.w33)
			if ($(this).is('a:contains("edit")')) {
				prev.children(':first').append($(this).addClass('db n'));
			}
			//otherwise,
			else {
				var pDiv = prev.children('div:last');
				// if last div has child spans, append element to 1st span of last div
				if (pDiv.children('span').length) {
					pDiv.children('span:first').append($(this));
				}
				// otherwise, append element to last div;
				else {
					pDiv.append($(this));
				}
			}
		}
		// remove 'js-m' class, since it also hides those elements initially (to prevent layout jerkiness)
		$(this).removeClass('js-m');
		// ensure that sibling label does not have a fixed width (only if descendent of .type-T)
		if ($(this).parents('div, fieldset, form').hasClass('type-T')) {
			$(this).siblings('label').addClass('wa');
		}
	});
	// new popup tooltip code
	// [a. for all .tip, set 'display:none' in coreJs.css]
	// [b. for all .tip, set padding, background-color and border-radius via class="tip box c" in global.css]
	// 1. insert a question mark icon in front of .tip
	// 2. attach a function to qmark so that on mouseover it shows .tip just to right of qmark
	// (but show on left if insufficent room)
	// 3. for IE7-, insert pointer (created per CSS for recent browsers)
	// 4. for smartphones, show and hide .tip below the qMark on click
	myObject.find('.tip').each(function() {
		var tip = $(this);
		var qmark = $('<span class="qMark di crp" />');
		tip.before(qmark);
		if (!smartphone) {
			tip.appendTo($('body').eq(0)).hide().addClass('point al z7 pa').css({'width':200});
			if (is_ie7down) {
				tip.append('<span class="indicator di"/>');
			}
			var showTip = function() {
				// hide any tips that my be visible
				$('.tip:visible').hide();
				// NB - if a tip won't fit on the right-hand side of a Q mark icon,
				// it should appear above that Q mark
				// if it appears left, it may obscure the associated input field
				// if it appears below, it may obscure the next input field
				var spaceRight = ($('body').width() - qmark.offset().left) > 270;
				var qLeft = spaceRight ? qmark.offset().left + qmark.width() + 30 : qmark.offset().left - 50;
				var qTop = spaceRight ? qmark.offset().top - 20 : qmark.offset().top - (tip.height() + 40);
				var direction = spaceRight ? 'left' : 'bot';
				tip.removeClass('left bot').addClass(direction).css({'left': qLeft, 'top': qTop}).show();
				//'fast' - removed; opacity issues with ':after' content in IE8-9
			}
			qmark.mouseover(showTip).mouseout(function() {
				tip.hide();//'slow'
			});
			// if there are sibling input fields or selects, show tip on focus and hide on blur
			qmark.siblings('input[type=text],input[type=password],select').focus(showTip).blur(function() {
				tip.hide();//'slow'
			});
			qmark.siblings().children('input[type=text],input[type=password],select').focus(showTip).blur(function() {
				tip.hide();//'slow'
			});
		}
		else {
		// if iphone
		// the spans have to be set to display:block initially to force a block-level display when shown, so hide initially
			if (tip.is('span.box')) {
				tip.hide();
			}
			// on clicking the question mark icon, toggle the tip
			qmark.click(function() {
				tip.toggle();
			});
		}
	});
}

// ### TABs & ACCORDION & OPTIONS drop-down ### - show/hide content
$(function() {
	// for IE7 only, insert .indicator (purple arrow) in every .speech, .more, .trigger, a.next, a.prev and .expand h3
	if (is_ie && ieNum==7) {
		$('.speech').append('<div class="indicator"/>');
		$('.point, .trigger, .more, a.next, a.prev, .expand h3, [data-role=select] h4').append('<span class="indicator di"/>');
		// enable show/hide of sibling list on clicking button.trigger (mimic :focus selector)
		$('button.trigger').focus(function() {
			$(this).next().css({'left':0,'margin-top':'1.5em'});
		}).blur(function() {
			$(this).next().css({'left':'-999em'});
		});
	}
	// for smart phones, TABBED.v1 > ACCORDION.v1
	if (smartphone) {
		$('.tabbed.v1').removeClass('tabbed').addClass('accordion');
	}
	// TABS
	// if tabs exist without an accompanying .tabbed, apply a class which will make entire tab area clickable
	$('.tabs').each(function() {
		if ($(this).next().size() > 0 && !$(this).next().hasClass('tabbed')) {
			$(this).addClass('v9');
		}
	});
	// targeted TAB step 1: is there a page anchor in the URL? and if so, what is it?
	var h = location.hash;
	var targetId = (h!='')&&(h!=null) ? (h.indexOf('?')!=-1 ? h.split('?')[0] : h) : null;
	// create NEW tabs
	// for each .tabbed container in page
	$('.tabbed').each(function() {
		var tabbed = $(this);
		// insert a new ul.tabs before .tabbed, then for each child node,
		var tabs = $('<ul class="tabs"/>');
		tabbed.before(tabs).children().each(function(i) {
			// hide its heading
			$(this).find(':header').eq(0).hide();
			// create a tab (a list item) with that heading's text; then on clicking the tab,
			var li = $('<li>' + $(this).find(':header').eq(0).html() + '</li>');
			li.click(function() {
				// if clicked tab is not marked as current
				if (!$(this).hasClass('current')) {
					// remove 'current' class from tab marked as current
					tabs.find('.current').removeClass('current');
					// add the class 'current' to clicked tab
					$(this).addClass('current');
					// hide any .tabbed child node which is showing
					tabbed.children().not(':hidden').hide();
					// show corresponding .tabbed child node
					tabbed.children().eq(i).show();
				}
			// append tab to ul.tabs
			}).appendTo(tabs);
			// targeted TAB step 2: if a page anchor in the URL (hash) corresponds to .tabbed child node or one if its descendents,
			// then mark the corresponding .tabs li as 'current'
			if (targetId && ($(this).is(targetId) || $(this).find(targetId).length)) {
				li.addClass('current');
			}
			// otherwise, hide the .tabbed child node
			else {
				$(this).hide();
			}
		})
		// if no tab is marked as current, make first tab current, and show first .tabbed node
		if (!tabs.find('li.current').length) {
			tabs.find('li').eq(0).addClass('current');
			tabbed.children().eq(0).show();
		}
		// if .tabbed is variant 1
		if (tabbed.hasClass('v1')) {
			// do column layout (25/75) & add numbering to tabs
			tabbed.addClass('w70').prev('ul.tabs').addClass('v1 w30 pr').css({'top':'-10px','left':'-10px','margin-bottom':'-20px'}).children('li').each(function(i) {
				$(this).html((i+1) + '. ' + $(this).html());
			});
			// tweak margins of children of .tabbed, and last item in each
			tabbed.children().addClass('ml5 mb0').each(function() {
				$(this).children(':last').addClass('mb0');
			});
			// for IE8- et al, remove bottom border of last list item
			if (!radius) {
				tabs.children('li:last').css('border-bottom','0');
			}
		}
		// if .tabbed is variant 2, mark associated tabs
		else if (tabbed.hasClass('v2')) {
			tabbed.prev('ul.tabs').addClass('v2');
		}
		// if .tabbed is variant 3, mark associated tabs
		else if (tabbed.hasClass('v3')) {
			tabbed.prev('ul.tabs').addClass('v3');
		}
		// but if .tabbed is not a variant, round tab corners for IE8-
		else if (!radius) {
			tabs.find('li').each(function() {
				roundCorners($(this),['tl','tr']);
			});
		}
		// for IE7 only, insert indicator in each tab
		if (ieNum==7) {
			$('.tabs').children().append('<div class="indicator"/>');
		}
	});
	// TODO - enable opening and closing of tabs when clicking (non-tab) links within the page:
	// loop through every link in page with an anchor
	// inside that loop, loop through every .tabbed
	// if anchor corresponds to id of a node found inside .tabbed
	// on clicking link, show the child node of .tabbed which either has or contains the id, and hide all siblings
	// get its index in array of child nodes of .tabbed
	// and mark its corresponding .tabs li as current and remove 'current' class form all sblings

	// ACCORDION
	// enable all top-level headings within child nodes of .accordion to toggle their associated content; hide content initially
	// note - script will overlook header; perhaps should be adjusted so that only div, li and fieldset are considered
	$('.accordion').each(function () {
		$(this).children().not(':header').each(function() {
			$(this).find(':header').eq(0).addClass('trigger pr').click(function() {
				$(this).siblings().toggleClass('h');
				$(this).toggleClass('down');
			}).siblings().addClass('h');
			// enable indicator (mimic :after) in IE7
			if (ieNum==7) {
				$(this).find(':header').eq(0).append('<div class="indicator"/>');
			}
		})
	});

	// ### OPTIONS drop-down ### nav[data-role=select]
	// 2 varieties - 1) main link + alt links drop-down; 2) nav heading + links drop-down
	// note - jQuery is able to hide an element on DOM ready, even if it doesn't yet exist in page
	$('nav[data-role=select]').each(function () {
		// hide list initially
		$(this).children('ul').eq(0).hide();
	});
	// note - click handler won't work unless tied to 'live' event (element may not exist yet in page) 
	$(document).on('click', 'nav[data-role=select] h4', function () {
		// positioning of ul varies if h4 is a child of .action
		var isAction = $(this).hasClass('action');
		var ulLeft = isAction ? 0 : $(this).position().left - 5;
		var ulTop = $(this).parent().height() + 1;
		// ul's sibling depends on whether or not h4 is a child of .action
		var sib = isAction ? $(this) : $(this).parent();
		sib.siblings('ul').eq(0).addClass('pa').css({'top':ulTop,'left':ulLeft}).toggle();
	});
	
	// .drop - IE7 only - show/hide a list on mouseover via js (via CSS for other browsers)
	// IE7 has difficulty in displaying some absolutely positioned elements beyond the parent's bounding box
	if (is_ie && ieNum===7 && $('.drop').length) {
		// for each .drop,
		$('.drop:has(ul)').each(function () {
			// style and insert a trigger div to show the list on mouseover; 
			// trigger sits beside main link; also insert a down arrow
			var trigger = $('<span class="speech bot pa r0"/>');
			trigger.css({'width':24, 'height':23}).append($('<span class="indicator di"/>').css({'top':7, 'left':7})).appendTo($(this));
			// on mousing over the trigger
			trigger.mouseover(function () {
				// remove any existing clone lists in body
				$('body > ul.clone').remove();
				// clone the sibling list, append it to body, style it and position it just below div.drop
				// base position on width of .drop - trigger.parent() & height of trigger
				var list = trigger.siblings('ul').clone();
				var tLeft = trigger.parent().offset().left;
				var tTop = trigger.offset().top + trigger.height();
				var tWidth = trigger.parent().width() - 5; // substract padding (see below)
				list.appendTo($('body')).addClass('clone nl ar pr5 cm b1p').css({'left':tLeft, 'top':tTop, 'width':tWidth}); //cl b1t
				// on mousing out of list remove it
				// NB - must use mouseleave here rather than mouseout; mouseout will fire when you leave any child of the list
				list.mouseleave(function () {
					$(this).remove();
				});
			});
		});
	}
});

// ### ROTATOR ### (slideshow) - rotate children of specified element at specified interval (in seconds)
// note optional ieVar - if this is passed as '1', non-image grandchildren are hidden at the start of each fade-out
function rotateChildren(element,delay,ieVar) {
	// hide all child divs (except first)
	element.children().not(':first').hide();
	// set specified interval (in seconds)
	setInterval(function() {
		// fade out the visible child over 2 secs,
		element.children().filter(':visible').fadeOut(2000,function() {
			// then fade in the next sibling over 1 sec
			if ($(this).next().length) {
				$(this).next().fadeIn(1000);
			}
			// but if there is no next sibling, fade in the first child
			else {
				element.children().eq(0).fadeIn(1000);
			}
		});
		// for IE, work around lack of opacity change for transparent overlays inside fade objects
		// in IE9, each transparent overlay can be faded in an out separately, but this makes overlays opaque in IE8-
		if (is_ie && ieVar) {
			element.children().filter(':visible').children().not('img').addClass('h');
			element.children().filter(':hidden').children().not('img').removeClass('h');
		}
	},delay*1000);
}

// ### CROSS-FADE rotator ### (slideshow) - similar to function above but with fade-out only
// note - special treatment of nested overlays in IE, is not necessary
function cfRotateChildren(element,delay) {
	// set 'position:absolute' to all child nodes (must be effectively stacked one on top of the next)
	element.children().addClass('pa');
	// hide all child divs (except first)
	element.children().not(':first').hide();
	// mark first div as current (with top stack order)
	element.children(':first').addClass('curr z5');
	// set specified interval (in seconds)
	setInterval(function() {
		// set div.curr as current div and its next sibling as next div; go back to 1st child when last is reached 
		var currDiv = element.children('.curr');
		var nextDiv = currDiv.next().length ? currDiv.next() : element.children().eq(0);
		// show next div 
		nextDiv.show();
		// fade out the current child over 2 secs, then pass classes 'curr z5' to next div
		currDiv.fadeOut(2000,function() {
			$(this).removeClass('curr z5');
			nextDiv.addClass('curr z5');
		});
	},delay*1000);
}


// ### AUTO-ROTATE ### children of any element with class .rotator, at 7 second interval
$(function() {
	$('.rotator').each(function() {
		rotateChildren($(this),7);
	});
});

/* distribute child nodes of designated element evenly, into designated number of columns */
function doColumns(element,colNum) {
	// initialise new columns array
	var columns = new Array;
	// find child nodes of designated element
	var nodes = element.children();
	// divide the number of child nodes by 'colNum', then round this up
	var nodesPerCol = Math.ceil(nodes.length/colNum);
	// determine appropriate width class for the columns (w50, w33 etc)
	var classStrA = 'w' + Math.floor(100/colNum);
	// for each column (i.e. loop 'columnNum' times), -
	var nodeCount = 0;
	for (var i=0; i<colNum; i++) {
		// mark the last column,
		var classStrB = (i==colNum-1) ? ' last' : '';
		// insert column within the designated element,
		var newCol = $('<div class="js ' + classStrA + classStrB + '"/>');
		element.append(newCol);
		// then append the appropriate number ('nodesPerCol') of child nodes to the column;
		// note 1: after each loop through a column's nodes, we need to start at NEXT item in 'nodes' array, not first
		// note 2: total number of nodes may not be exactly divisible by the number of columns,
		// hence the need to test for the existence of nodes[nodeCount+j] (in last column);
		for (var j=0; j<nodesPerCol; j++) {
			if (nodes[nodeCount+j]) {
				newCol.append(nodes[nodeCount+j]);
			}
		}
		nodeCount += nodesPerCol;
	}
	// fix ordered list items (otherwise, numbering either disappears or restarts within each col)
	if (element.is('ol') && !element.is('.nl')) {
		element.addClass('nl').find('li').each(function(n) {
			$(this).html((n+1) + '. ' + $(this).html());
		});
	}
}

// for IE7-, fix various CSS shortcomings:
// 1. insert quotation marks inside the 'q' tag
// 2. and :after content
$(function() {
	if (is_ie7down) {
		if ($('q').length) {
			// if not .q-m or .q-o or .q-x etc, just add text quote marks
			$('q').not("[class*='q-']").each(function() {
				$(this).text('"' + $(this).text() + '"');
			});
			// otherwise, add image quote marks
			$(".q-x").each(function() {
				$(this).html('<img src="/images/home/quote_left_19x14.png" /> ' + $(this).html() + ' <img src="/images/home/quote_right_19x14.png" />');
			});
			$(".q-m.l").each(function() {
				$(this).html($(this).html() + ' <img src="/images/profile/quote_right.gif" />');
			});
			$(".q-o.l").each(function() {
				$(this).html($(this).html() + ' <img src="/images/rsvip/quote_marks_gold_right.png" />');
			});
		}
		$('.close').append(' [x]');
		$('.req').append('<strong class="rc ml5">*</strong>');
		// hide masthead ad when hovering over .more in fairfax header
		$('#networkStripTop .fairfax .more').mouseover(function() {
			$('#mastheadAd').hide();
		}).mouseout(function() {
			$('#mastheadAd').show();
		})
		// insert 'f' content (facebook logo) in .action.fb, .fb-l and .fb-s
		$('.action.fb').append('<span class="xl di pa">f</span>');
		$('.fb-l, .fb-s').append('<span class="wc st di pa">f</span>');
	}
});

// ### FAIRFAX HEADER TRACK CODE ### - run on DOM ready
// add a click handler to each fairfax link in header
$(function() {
	$('#networkStripTop .fairfax a').each(function() {
		$(this).click(function() {
			var url = $(this).attr('href');
			$(this).attr('href', url + '?s_cid=ws-top');
		})
	})
});

// ### FaceBook like iframe ###
$(function() {
	var extraAtributes = {
		'style':'border:none; overflow:hidden; width:90px; height:21px;',
		'allowTransparency':'true'
	};
	doIframe('http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Ffacebook.com%2FRSVP.com.au&layout=button_count&show_faces=true&width=90&action=like&amp;colorscheme=light&amp;height=20',90,21,$('#facebook-like')[0],null,extraAtributes);
});

// rsvp-579
// rsvpfooter removed in rsvp-809
$(function () {
    var prep = function (link, type) {
		var linkHref = link.attr("href");
		var anchor = '';
		if (linkHref.indexOf('#') != -1) {
			anchor = linkHref.substring(linkHref.indexOf('#'));
			linkHref = linkHref.replace(anchor, '');
		}
        var srid = link.text().replace(/[\s\-]/g, '_').replace(/[^A-Za-z0-9_]/g, '');
        return linkHref.replace(/(\?|$)/, function (g0) {
            var add = g0 != "?";
            return "?s_wid=" + type + ":" + encodeURIComponent(srid) + (add?'':'&') + anchor;
        });
    };
    $("#member-dock a[href]").attr("href", function () { return prep($(this), "dock"); });
});

/* redundant: function trackCode(myLink,myCode) {
	myLink.href += '?s_cid=' + myCode;
}*/

// ### ADD TO/REMOVE FROM FAVOURITES ###
$(function () {
	var h2Content = $('#rsvpcontent > h2').text().trim();
	var isFavouritesPage = h2Content == 'Favourites';
	var isBlockedOrIgnoredPage = h2Content == 'Blocked' || h2Content == 'Hidden';
    var isChatWindow = $('#chat-wrapper').length > 0;

	if (!isMember || isChatWindow) {return;}
	var allFavLinks = $('a[href^="/favourites/add"],a[href^="/favourites/delete"]');
    // remove redirect URLs, we don't need these in a JS environment, and they can make the URLs different
    allFavLinks.attr("href", function() { return $(this).attr("href").replace(/&redirectUrl=.*(&|$)/, ''); } );
    $(document).on('click', allFavLinks.selector, function(e) {
		var self = $(this);
		var href = self.attr("href");
		var favLinks = $('a[href^="' + href + '"]');
		var favParent = favLinks.parent();
		favLinks.text("loading...").siblings('p.di.m0').hide();
		if (self.data("loading") !== true) {
			self.data("loading", true);
      // nb. we remove any !input from the link, no need for confirmation with ajax undo...
			jsonCall(href.replace(/!input/, ''), "get", null, function () {
				var add = /\/favourites\/add\.action/.test(href);
					if (isFavouritesPage) {
						// this is special functionality for this one page only, which
						// masks the favourite entry with a grey box, allowing user to reinstate favourite easily
						if (add) {
							self.parents('li.box').removeClass('x xc');
							// if opacitiy is adjusted using class, change this to removeClass():
							self.parents('li.box').children('div.main,div.photo').css('opacity', '');
							self.parents('div.box').children('[data-close="overlay"]').hide('fast');
						}
						else {
							self.parents('li.box').addClass('x xc');
							// consider using a new 40% opacitiy class instead, so we can alpha filter for lower IE versions?
							self.parents('li.box').children('div.main, div.photo').css('opacity', '0.4');
							self.parents('div.box').prepend($('<span data-close="overlay" class="close fr">close</span>').css('right', '-5px'));
						}
					}	
					if (isBlockedOrIgnoredPage && add) {
						// trash the entire area, as add to favourites removes block & ignore records...
						self.parents('li.box').remove();
					}
					else {
						var queryString = href.substring(href.indexOf("?"));
						var favHref = (add ? "/favourites/delete.action" : "/favourites/add.action") + tomcatSessionArgs + queryString;
						var frag = $('<a/>', {href: favHref, title: (add ? 'Remove from favourites' : 'Add to favourities')});
						if(!favParent.hasClass('tool-tip')) {
							if (add) {
								favParent.empty().append(frag).append($('<p/>').addClass('di m0').append(
										' In <a href="/contact/contacts.action' + tomcatSessionArgs + '?contactType=FAVOURITE">favourites</a> ('
								).append(frag.clone().text('remove')).append(')'));
							} else {
								favParent.empty().append(frag.text('Add to favourites'));
							}
						} else {
							self.replaceWith(frag.removeAttr('title'));
							favParent.find("span.speech").text(add ? "Remove from favourites" : "Add to favourites");
						}
						favParent.find('a[href^="/favourites/"]').removeData("loading");
					}
			},null,null);
		}
		return false;
	});

	if (isFavouritesPage) {
		$(document).on('click', '[data-close="overlay"]', function (e) {
			$(this).parents('li.box').remove();
		});
	}
});

// write style sheet to hide any cookie-collapsed items
var collapseCookie = $.cookie('collapse');
if (collapseCookie) {
	// write style sheet to hide everything referred to in the collapse cookie
	var idsToHide = collapseCookie.split(',');
	if (idsToHide && idsToHide.length > 0) {
		var idToHideStr = '<style type="text/css" media="screen">';
		for (var i=0; i < idsToHide.length; i++) {
			var idToHide = idsToHide[i];
			if (!idToHide || idToHide == '') {
				idToHide = 'blah';
			}
			idToHideStr += '#' + idToHide + ' .collapse';
			if (i+1 < idsToHide.length) {
				idToHideStr += ',';
			}
		}
		idToHideStr += ' { display:none; }';
		idToHideStr += '</style>';
		document.writeln(idToHideStr);
	}
}

function collapseSectionAndRemember() {
	var link = $(this);
	var parent = link.parent();
	var collapseSection = parent.children(":first") ;
	while (collapseSection && !collapseSection.hasClass('collapse')) {
			collapseSection = collapseSection.next();
	}
	var stateMarker = parent.children(":first");
	while (stateMarker && !stateMarker.hasClass('marker')) {
			stateMarker = stateMarker.next();
	}
	if (collapseSection) {
		var collapseCookieVal = $.cookie('collapse');
		if (!collapseCookieVal)
			collapseCookieVal = '';

		var itemId = parent.attr('id');

		while (collapseCookieVal.indexOf(itemId+',') > -1)
			collapseCookieVal = collapseCookieVal.replace(itemId+',', '');

		if (collapseSection.is(':hidden'))
		{
			collapseSection.show('fast');
			if (stateMarker)
				stateMarker.html('hide');//collapse
		}
		else
		{
			collapseSection.hide('fast');
			if (stateMarker)
				stateMarker.html('show');//expand
			collapseCookieVal = itemId + ',' + collapseCookieVal;
		}
		$.cookie('collapse', collapseCookieVal, { expires: 30 });
	}
}

function addCollapseWithRemember(myEl) {
	if (myEl) {
		var txt = collapseCookie && collapseCookie.indexOf(myEl.attr('id') + ',') > -1 ? 'show' : 'hide';
		myEl.find('h2').eq(0).addClass('crp').click(collapseSectionAndRemember).after($('<a class="js st fr t2 crp marker">' + txt + '</a>').click(collapseSectionAndRemember));
	}
}

// look for particular class names which trigger style changes which can not or should not be given effect through CSS
$(function() {
	// for IE8-, enable pseudo-class last-child with .type-M & .type-O
	if (is_ie8down) {
		$(".type-M, .type-O").each(function() {
			var chNodes = $(this).children().not('.cnr');
			var chNum = chNodes.length-1;
			chNodes.eq(chNum).addClass('last-child');
		})
	}
	// enable large-disc numbering of ordered lists with class 'type-U':
	// remove the default numbering, then insert a span in each li to hold the large-disc number
	$("ol.type-U").addClass('nl ch-ca').each(function() {
		$(this).find("li").each(function(i) {
			$(this).prepend($('<span class="fl p l st ac mr5">' + (i+1) + '</span>'))
		});
	});
});


function ajaxSubmit(form, successCallback, errorCallback) {
    if (!form || form.is(":not(form)"))
        throw "call this with a real form";

    // if more than one form provided, ajaxify all of them
    if (form.size() > 1) {
        form.each(function() { ajaxSubmit($(this), successCallback); });
	    return;
    }

    var clearFieldErrors = function () {
        var errorDivs = form.find(".error:has(input, select, textarea)");
        errorDivs.removeClass("error");
        errorDivs.find("ul").remove();
    };

    var getFormContainer = function(e, name) {
        var container = e;
        var firstDivOrFieldset = null;
        do {
            container = container.parent();
            if (container.attr("id") == name)
            	return container;
            if (container.is("div,fieldset"))
            	firstDivOrFieldset = container;
        } while (container != null && !container.parent().is("form"));
        if (firstDivOrFieldset)
        	return firstDivOrFieldset;
        return container;
    };

    var addFieldErrors = function (fieldErrors) {
        clearFieldErrors();

        // add new errors
        $.each(fieldErrors, function (n, v) {
            var field = form.find("[name='" + n + "']");

            // this should rarely happen. only for hidden fields, stuff on the form actions querystring
            if (field.size() == 0) {
//                log("no field for field error: " + n + ": " + inspect(v, null, true));
                return;
            }
            if (field.size() > 1) {
                // take the first, this could happen for fields with the same name, ie. checkbox lists, radios, dates, etc...
                field = field.eq(0);
            }

            var container = getFormContainer(field, n);

            container.addClass("error");
            var errorUl = $("<ul/>");
            $.each(v, function (i, v) {
                var error = $("<li/>");
                error.html(v);
                errorUl.append(error);
            });
            errorUl.hide();
            var errorAfter = container.find(".error-after");
            if (errorAfter.length)
                errorAfter.after(errorUl);
            else
                container.prepend(errorUl);
            errorUl.fadeIn("slow");
        });
    };

    var serialiseForm = function () { return form.serialize(); };

    var disableFormFields = function () {
        form.find(":input").attr("disabled", "disabled");
    };

    var enableFormFields = function () {
        form.find(":input").removeAttr("disabled");
    };

    var createNotification = function (className, where, html) {
        var ret = form.find("." + className);
        if (ret.length == 0) {
            ret = $("<div/>", { "class": className + " mt5 cf", html: html });
            switch (where) {
                case "before": form.prepend(ret); break;
                case "after": form.append(ret); break;
                default: alert("bad");
            }
        }
        ret.addClass("ajax").hide();
        return ret;
    };
    var progress = createNotification("progress", "after", "<strong>Working...</strong>");
    var failure = createNotification("error", "before"); // action error, server failure or couldn't contact server
    var success = createNotification("success", "before");
    var timeout = createNotification("timeout", "after", "<strong>This task is taking longer than expected</strong>"); // servers busy, very slow connection...
    var clearStatusDivs = function () {
        $().add(success).add(failure).add(progress).add(timeout)
                .hide().removeClass('h').find(".ajax").remove();
    };

    // remove any pre-existing handler before adding this one...
    form.unbind('submit.ajaxSubmit');
    form.bind('submit.ajaxSubmit', function (e) {
        e.preventDefault();

        var values = serialiseForm();

        form.addClass("ajax");

        var error = function (json, textStatus, errorThrown) {
            enableFormFields();
            resetSubmitted();

            clearStatusDivs();
            if (textStatus == "timeout") {
                timeout.show();
            }

            if (errorCallback) errorCallback(json);
        };

        var callback = function (json, textStatus, xhr) {
            clearStatusDivs();
            if (json.resultCode != "error" && !json.hasErrors) {
                if (json.resultCode == "input")
                    enableFormFields();

                if (json.hasActionMessages) {
	                var ul = $("<ul class='ajax'/>");
	                $.each(json.actionMessages, function (i, v) {
		                ul.append($("<li/>").html(v));
	                });
	                success.append(ul);
                    success.show();
                }

                if (successCallback) {
                    // give the callback access to some stuff
                    var exposed = {
                        form: form, // the $(form) object
                        enable: enableFormFields, // function
                        disable: disableFormFields, // function
                        json: json, // returned by the action
                        textStatus: textStatus,
                        xhr: xhr
                    };
                    successCallback.apply(form.get(0), [exposed]);
                }
            }
            else {
                if (json.hasFieldErrors)
                    addFieldErrors(json.fieldErrors);

                error(json, null, null);
                if (json.hasActionErrors) {
                    failure.append($("<h4 class='ajax'>Sorry!</h4>"));
	                var errorUl = $("<ul class='ajax'/>");
	                $.each(json.actionErrors, function (i, v) {
		                errorUl.append($("<li/>").html(v));
	                });
                    failure.append(errorUl);
	                failure.show();
                }
            }
        };

        var timeoutCallback = function () {
            enableFormFields();
            progress.hide();
            timeout.show();
        };

        clearFieldErrors();
        disableFormFields();

        clearStatusDivs();
        progress.show();

        jsonCall(form.attr("action"), form.attr("method"), values, callback, error, timeoutCallback);
    });
}

function jsonCall(url, method, data, success, error, timeoutCallback) {
    // make a copy of the data map so we can add a value to it
    var copy;
    if (data == null || typeof data == "string") {
        copy = data == null ? "" : data;
        if (copy.length > 0)
            copy += "&";
        copy += "ajax=true&json=true";  // support both json params...

        if (userName && userName.length > 0) {
            // embed clientUserName - in case a JSON-action wants to verify that this hasn't changed...
            copy += '&clientUserName=' + userName
        }
    }
    else {
        if (userName && userName.length > 0) {
            // embed clientUserName - in case a JSON-action wants to verify that this hasn't changed...
            copy = { ajax : true, json : true, clientUserName : userName };
        }
        else {
            copy = { ajax : true, json : true };
        }
        $.each(data, function (n, v) { copy[n] = v; });
    }

    // provide a generic handler for a login resultCode
    var successWrapper = function (data, textStatus, xhr) {
        if (data && data.resultCode == "login") {
            // no valid session - see if there's an error handler defined and if so, use it...  otherwise, just pop an alert
            if (error)
                error(data, textStatus, xhr);
            else
                alert(data.actionErrors != null && data.actionErrors.length > 0 ? data.actionErrors : 'You must be logged in to continue');
        }
        else if (success)
            return success(data, textStatus, xhr);
    };

    // check for JSON parsererrors, and provide a generic error handler for connection resets / etc. if
    // none has otherwise been defined.
    var errorWrapper = function (data, textStatus, xhr) {
        if (textStatus == "parsererror")
            alert('Failed to parse response');
        else if (error)
            error(data, textStatus, xhr);
        else {
            var message = data.hasActionErrors ? data.actionErrors.join("\n") : 'Unexpected error, please try again later.';
            alert(message);
        }
    };

    var timer = setTimeout(function () { if (timeoutCallback) timeoutCallback(); }, 30000);

    $.ajax({
        url: url,
        data: copy,
        success: successWrapper,
        error: errorWrapper,
        dataType: "json",
        type: method,
        complete: function () { clearTimeout(timer); }
    });
}

/* end ### jQuery ### functions */

// write style sheet to hide nested lists in top nav on hover
document.write('<style type="text/css" media="screen">@import "'+scriptDomain+'/css/coreJs.css?v=' + rsvpTag + '";</style>');

/* CHANGE OPACITY of an object (fade-in and fade-out) */
var fadeInTimeout;
var fadeOutTimeout;
var fadeInInterval;
var fadeOutInterval;
var fadeObject;
var styleObject = (is_ie8down) ? '.filters.alpha' : '.style';
var opacityObject = (is_moz) ? '.MozOpacity' : '.opacity';
var opacityStep = (is_ie8down) ? 5 : 0.05; // number of opacity gradations to move each time (1/20th of the scale)
var fullOpacity =  (is_ie8down) ? 100 : 1;
var myInterval = 50; // interval between opacity changes, in milliseconds (50 = 1/20th sec)
var myTimeoutIn;
var myTimeoutOut;
var mySwitch;
var rotation = true;

function fadeIn() {
	// if there is a function to switch fade object or its properties (between fade out and in), run that;
	if (mySwitch!=null) eval(mySwitch + '()');
	// if fadeOut is still running, stop it and reduce opacity to 0
	if (fadeOutInterval) {
		clearInterval(fadeOutInterval);
		eval('fadeObject' + styleObject + opacityObject + ' = "0"');
	}
	// set an interval between opacity increases and fade object in;
	fadeInInterval = setInterval(increaseOpacity,myInterval);
	// set a delay for fade out function, then call it;
	fadeOutTimeout = setTimeout(fadeOut,myTimeoutOut);
}

function fadeOut() {
	// set an interval between opacity decreases and fade object out;
	fadeOutInterval = setInterval(decreaseOpacity,myInterval);
	// set a delay for fade in function, then call it;
	fadeInTimeout = setTimeout(fadeIn,myTimeoutIn);
}

function increaseOpacity() {
	var newOpacity = eval('fadeObject' + styleObject + opacityObject) ;
	if (newOpacity < fullOpacity) {
		// note - we need parseFloat here because the starting opacity is set as a string; see setup function
		newOpacity = parseFloat(newOpacity) + opacityStep;
		eval('fadeObject' + styleObject + opacityObject + ' = newOpacity');
	}
	else {
		clearInterval(fadeInInterval);
	}
}

function decreaseOpacity() {
	var newOpacity = eval('fadeObject' + styleObject + opacityObject);
	// if auto-rotation is off, skip fade
	if (rotation && (newOpacity>0)) {
		newOpacity -= opacityStep;
		eval('fadeObject' + styleObject + opacityObject + ' = newOpacity');
	}
	else {
		clearInterval(fadeOutInterval);
		// if auto-rotation is off, return opacity to full
		if (!rotation) {
			eval('fadeObject' + styleObject + opacityObject + ' = fullOpacity');
		}
	}
}

function stopFade() {
	clearTimeout(fadeOutTimeout);
	clearTimeout(fadeInTimeout);
}

/* ### EXAMPLE CALL ### a function like 'setupSuccessStories' at line 14773 should be set up in your page to call fadeIn();
note - lines between opacity setting and fadeIn() call, are optional; */

/* for simple, "one-off" fade outs: */
// NB - values for  myInterval, styleObject, opacityObject are set above;
var singleFadeObject;
var singleFadeOutInterval;

// set object to be faded, initial opacity, interval between opacity decreases and fade function;
function singleFadeOut(myElement) {
	singleFadeObject = myElement;
	eval('singleFadeObject' + styleObject + opacityObject + ' = fullOpacity');
	singleFadeOutInterval = setInterval(singleDecreaseOpacity,myInterval);
}

// keep reducing opacity until it hits zero, then clear interval between opacity decreases & hide element
function singleDecreaseOpacity() {
	var newOpacity = eval('singleFadeObject' + styleObject + opacityObject);
	if (newOpacity>0) {
		newOpacity -= opacityStep;
		eval('singleFadeObject' + styleObject + opacityObject + ' = newOpacity');
	}
	else {
		clearInterval(singleFadeOutInterval);
		singleFadeObject.className += ' js h';
	}
}

var statesA = new Array('New South Wales','Victoria','Queensland','Western Australia','South Australia','Northern Territory', 'Tasmania');
var statesB = new Array('NSW','VIC','QLD','WA','SA','NT','TAS');

// in a specified string, abbreviate first instance of any name from a specified array, then truncate string
function abbreviateSpecial(myString,myArrayName) {
	var longNames = eval(myArrayName + 'A');
	var shortNames = eval(myArrayName + 'B');
	for (i=0, y=longNames.length; i<y; i++) {
		if (myString.indexOf(longNames[i])!=-1) {
			myString = myString.replace(longNames[i],shortNames[i]);
			myString = myString.split(shortNames[i])[0] + shortNames[i];
			break;
		}
	}
	return myString;
}

// create a column layout within given element and assign particular elements by id to each column
// group elements by id or class name, separating id/class names by comma and column groups by a a pipe, like so:
// assignToColumns(content,'phone-pic,how-it-works,new-icons|what-is-it,checklist|send-feedback,closing-comments');
function assignToColumns(myElement,myLayout) {
	var groups = myLayout.split('|');
	for (var i=0, y=groups.length; i<y; i++) {
		// create a class name appropriate to width of column
		var width = Math.floor(100/(y));
		var col = $('<div/>').addClass('js w' + width);
		$(myElement).append(col);
		var group = groups[i].split(',');
		for (var j=0, z=group.length; j<z; j++) {
			var nodePresent = $('#' + group[j])[0] || $(myElement).find('.' + group[j])[0];
			if (nodePresent) {
				var node = $('#' + group[j])[0] ? $('#' + group[j])[0] : $(myElement).find('.' + group[j])[0];
				col.append($(node));
			}
		}
	}
}

/* a function to be called on resize only; if there are existing .col divs in target element, before 'splitTagsToColumns()' runs, mark them for later removal by 'cleanUp()' */
function prelimColumnCleanUp(element) {
	$(element).find('div.col').addClass('obsolete');
	// remove obsolete divs
	cleanUp();
}

/* a function to overcome the difficulty that IE and Opera have in retaining the formatting of an ordered list when it is split into columns; loop through list items, inserting sequential number in each */
function renumberList(element,startNum) {
	$(elemement).find('li').each(function(i, el){
		$(el).before($('<span/>', {'id': 'num'}).text(i+startNum + '. '));
	});
}

/* ### VERTICAL ALIGNMENT ### */

// ### of image to parent container ###
// within specified element, align all images to middle of their parent container
// NB - assumes parent has no top or bottom padding or border
function vAlignImgs(myElement) {
	$(myElement).find('img').each(function(i, el){
		vAlignToMid(el, el.parentNode.offsetHeight);
	});
}

// vertically align an image to the middle of specified height, by setting a top margin
// NB - first ensure image has loaded (and is at least a third of the specified height)
function vAlignToMid(myImg,myHeight) {
	var newImg = new Image();
	newImg.src = myImg.src;
	var nHeight = newImg.height;
	if ((nHeight < (myHeight-1)) && (nHeight > (myHeight/3))) { //&& newImg.complete
		var myDiff = myHeight - nHeight;
		myImg.style.marginTop = Math.floor(myDiff/2) + 'px';
	}
}

/* ### of text to accompanying image ### */
/* 	in semantic mark-up, enable VERTICAL ALIGNMENT of text and image to the MIDDLE of each other;
 the shorter of the 2 will be aligned to the middle of the other;
 handles multiple text/image pairs within a targeted element;
 for details, see http://rsvpintranet/wiki/index.php/Coding_Style_-_HTML#dynamic_vertical_alignment_of_text.2Fimage_pairs */
function middleAlign(element,rowTag,imgPos) {
	// find number of rows
	var rowNum = (rowTag==0) ? 1 : $(element).find(rowTag).length;
	if (rowNum != 0) {
		// insert table
		var table = $('<table/>').addClass('js valign');
		element.append(table);
		// position table immediately after the last image/text pair
		if (rowTag!=0) {
			var lastTag = $(element).find(rowTag + ':last');
			lastTag.after(table);
		}
		// insert 'tbody' (NB: required by IE only; otherwise table won't display)
		var tbody = $('<tbody/>');
		table.append(tbody);
		// insert table rows; for each text/image pair,
		for (var j=0; j<rowNum; j++) {
			// create a table row
			var currRow = $('<tr/>').append('<td></td><td></td>');
			tbody.append(currRow);
			var tCells = currRow.find('td');
			// append images
			var imgs = $(element).find('img:first');
			// if there's an image, append the image (or its parent node, if that's appropriate)
			if (imgs.length) {
				var imgTag = imgs[0].parentNode.tagName.toLowerCase();
				var img = ((imgTag==rowTag)||(rowTag==0)) ? imgs[0] : imgs[0].parentNode;
				$(tCells[imgPos]).append($(img));
			}
			// append text
			var nodes = (rowTag==0) ? null : $(element).find(rowTag);
			var node = (rowTag==0) ? element : nodes[j];
			var tableStart = (node.innerHTML.indexOf('<table')==-1) ? '<TABLE' : '<table';
			var text = (rowTag==0) ? node.innerHTML.split(tableStart)[0] : node.innerHTML;
			var cellNumT = (imgPos==0) ? 1 : 0;
			tCells[cellNumT].innerHTML = text;
			// mark the empty child node for removal and transfer its id to the new table row
			if (rowTag!=0) {
				$(node).addClass('obsolete');
				if ($(node).attr('id')) {
					currRow.attr('id', $(node).attr('id'));
				}
			}
			// remove original text (now duplicated) immediately, if there is no text/image container
			else {
				var startNum = node.innerHTML.indexOf(tableStart);
				node.innerHTML = node.innerHTML.substring(startNum,node.innerHTML.length);
			}
		}
	}
	// remove obsolete divs
	cleanUp();
}

/* remove obsolete nodes; create #black-hole, shift any element with class name 'obsolete' into it, then obliterate it */
function cleanUp() {
	$('.obsolete').remove();
}

// remove any single node, any comma separated set of nodes or any array of nodes
function removeNode() {
	var nodes = (removeNode.arguments[0].length!=null) ? removeNode.arguments[0] : removeNode.arguments;
	$.each(nodes, function(){ $(this).remove() });
}

/* scrolling plugin */
$.fn.setScrollingFor = function(options) {
	// global settings
	var settings = {
		element: 'li',
		cssClass: '',
		autoplay: false,
		vertical: false
	};
	// slide it function - uses jquery animation to change left position of .inner list element
	var slide = function () {
		// local variables
		var self = this,
			leftArrow = $(self).siblings('.l0'),
			rightArrow = $(self).siblings('.r0'),
			topArrow = $(self).siblings('.t0'),
			bottomArrow = $(self).siblings('.b0'),
			inner = $(self).siblings('.inner'),
			outer = inner.parent(),
			scrollTo = (inner.width() > outer.width()) ? inner.width() - outer.width() : inner.width(),
			scrollToTop = (inner.height() > outer.height()) ? inner.height() - outer.height() : inner.height(),
			durLeft = -inner.position().left * 5,
			durTop = -inner.position().top * 5,
			durRight = (scrollTo + inner.position().left) * 5,
			durBottom = (scrollTo + inner.position().top) * 5;

		// if cursor hovers left arrow scroll .inner to the left
		if ($(self).hasClass('l0')) {
			if (rightArrow.is(':hidden')) {
				rightArrow.show();
			}
			inner.animate({
				left: '0'
			}, durLeft, function(){
				$(self).hide();
			});
		// scroll .inner to the right
		} else if ($(self).hasClass('r0')) {
			if (leftArrow.is(':hidden')) {
				leftArrow.show();
			}
			inner.animate({
				left: '-' + scrollTo + 'px'
			}, durRight, function(){
				$(self).hide();
			});
		// scroll .inner to the top
		} else if ($(self).hasClass('t0')) {
			if (bottomArrow.is(':hidden')) {
				bottomArrow.show();
			}
			inner.animate({
				top: '0'
			}, durTop, function(){
				$(self).hide();
			});
		// scroll .inner to the bottom
		} else if ($(self).hasClass('b0')) {
			if (topArrow.is(':hidden')) {
				topArrow.show();
			}
			inner.animate({
				top: '-' + scrollToTop + 'px'
			}, durBottom, function(){
				$(self).hide();
			});
		}
	};
	// scroll to specific position
	var slideTo = function(inner, position){
		// local variables
		var leftArrow = inner.siblings('.l0'),
			rightArrow = inner.siblings('.r0');
		if(leftArrow.is(':hidden')) leftArrow.show();
		if(rightArrow.is(':hidden')) rightArrow.show();
		inner.animate({
			left: '-' + position + 'px'
		}, 1000);
	};
	// stop animation on mouse out
	var stop = function(){
		$(this).siblings('.inner').stop(true, false); 
	};
	return this.each(function() {
		// avoid running it twice!
		if ($(this).hasClass('outer oh pr')) return;

		// If options exist, lets merge them
		// with our default settings
		if (options) { 
			$.extend(settings, options);
		}

		// local variables
		var self = this, wrap,
			item = $(self).find('> ' + settings.element),
			width = item.size() * item.outerWidth(true),
			height = item.size() * item.outerHeight(true),
			topHeight = parseInt($(self).parent().css('max-height')) || $(self).parent().height();

		// if scrolling list is smaller than the wrapping container do not apply the script
		if (width <= $(self).outerWidth(true) || (settings.vertical && height <= topHeight)) {
			$(self).addClass('outer oh pr');
			return;
		}

		// add setScrollingFor, .inner classes to list
		// calculate & apply width or height if vertical
		$(self).removeClass('oa scroll').addClass('js inner pa z5');

		// wrap list with .outer div
		// add left & right or top & bottom arrows
		wrap = $(self).wrap('<div class="js outer oh pr" />').parent().addClass(settings.cssClass);
		if (settings.vertical) {
			$(self).height(height);
			wrap.height(topHeight)
				.prepend('<div class="js arrow pa z6 crp t0" style="display: none" />')
				.append('<div class="js arrow pa z6 crp b0" />');
		} else {
			$(self).width(width);
			wrap.prepend('<div class="js arrow pa z6 crp l0" style="display: none" />')
				.append('<div class="js arrow pa z6 crp r0" />');
		}

		// bind slide & stop events while cursor hovers arrows
		$(wrap).find('> .arrow').hover(slide, stop);

		// autoplay
		if(settings.autoplay){
			var i = 0;
			var interval = setInterval(function(){
				i++;
				slideTo($(self), i*item.outerWidth(true));
				// clear interval if last element or mouseover on arrow
				if((i + 1) >= item.size()) clearInterval(interval);
				$(wrap).find('> .arrow').mouseover(function(){
					clearInterval(interval);
				})
			}, 2000);
		}
	});
};

/* ### AJAX ### */

// request then import XML content from another page; (NB - the 1st function calls the 2nd);
// returns true if the request was got underway, false otherwise
function requestXML(myURL,myResponseFunction,myParam,post,postData) {
	/* - initialise 'requestedPage'  - for requesting an html page from the web server, either through the 'XMLHttpRequest' object
	 (Mozilla, Opera, Safari, IE7 et al) or through 'ActiveXObject' (IE 5x/6);
	IE-specific code below adapted from  http://www-128.ibm.com/developerworks/web/library/wa-ajaxintro1.html and
	 http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx;
		note 1 - the more specific version name of Msxml in latter is preferred, because it is provided by IE developers and enforces the use of the latest most
		stable version of MSXML - 6, if it is available; note also - IE7 understands window.XMLHttpRequest;
		note 2 - fall back to the generic version of Msxml, rather than version 3, because otherwise this script fails in IE6 on older OSs, e.g. Win98;
		note 3 - the version-independent ProgID is always bound to MSXML 3; note also - the "Microsoft" namespace is actually older and is only implemented in
		MSXML3 for legacy support */
	var requestedPage = false;
    var retVal = false;
    /*@cc_on @*/
	/*@if (@_jscript_version >= 5)
	try {
	  requestedPage = new ActiveXObject("Msxml2.XMLHTTP");
	} catch (e) {
	  try {
	    requestedPage = new ActiveXObject("Microsoft.XMLHTTP");
	  } catch (e2) {
	    requestedPage = false;
	  }
	}
	@end @*/
	if (!requestedPage && typeof XMLHttpRequest != 'undefined') {
	  requestedPage = new XMLHttpRequest();
	}
	if (window.XMLHttpRequest||window.ActiveXObject) {
		try {
			// if requested page is ready, call speicifed response function (to import content from it)
			requestedPage.onreadystatechange = function() {
				// readyState 4 is request completed
				if (requestedPage && (requestedPage.readyState == 4)) {
					// 200 and 204 are the only response codes that should be coming back
					// for some reason, IE seems to gives a 1223 in place of 204
					// 304 was included initially, but has never been seen, so now removed - ANH 121207
					var success = requestedPage.status == 200 || requestedPage.status == 204 || requestedPage.status == 1223;
					var content = requestedPage.responseText;
					myResponseFunction(success,content,myParam);
				}
			};
			// use http method 'POST' or 'GET' to open the page at specified URL
			// set async flag to true; note - 'requestedPage' is defined above
			if (post) {
				requestedPage.open("POST", myURL, true);
				requestedPage.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
				requestedPage.setRequestHeader("Content-length", postData.length);
				requestedPage.setRequestHeader("Connection", "close");
				requestedPage.send(postData);
        retVal = true;
        }
			else {
				requestedPage.open("GET",myURL,true);
				// don't send any data to the requested page
				requestedPage.send(null);
        retVal = true;
			}
		}
		catch (ex) {
			// alert(ex);
		}
	}
  return retVal;
}

// write the desired portion of the requested X(H)TML to the specified element;
function importXML() { // content,element,startText,endText
	var content =  importXML.arguments[0];
	var element = importXML.arguments[1];
	var startText = (importXML.arguments[2]) ? importXML.arguments[2] : null;
	var endText = (importXML.arguments[3]) ? importXML.arguments[3] : null;
	// write whole page if start text is 'null';
	element.innerHTML += (!startText) ? content : content.split(startText)[1].split(endText)[0];
}

/* in a given text string, replaces line breaks and/or line breaks+hyphens, with a space */
function removeLineBreaks(myString) {
	var brs = new Array('<br>','<br/>','<br />','<BR>','<BR/>','<BR />');
	for (var i=0; i<brs.length; i++) {
		var br = '-' + brs[i];
		while (myString.indexOf(br) != -1) {
			myString = myString.replace(br,'');
		}
		while (myString.indexOf(brs[i]) != -1) {
			myString = myString.replace(brs[i],'');
		}
	}
	return myString;
}

function removeHTMLTags(myString) {
	var strInputCode = myString;
	/*
	This line is optional, it replaces escaped brackets with real ones,
	i.e. < is replaced with < and > is replaced with >
	*/
	strInputCode = strInputCode.replace(/&(lt|gt);/g, function (strMatch, p1) {
		return (p1 == 'lt')? '<' : '>';
	});

	return strInputCode.replace(/<\/?[^>]+(>|$)/g, '');
}

/* in a given text string, remove 'strong' tags */
function menuFormat(myString) {
	var strings = new Array('<strong>','<STRONG>','</strong>','</STRONG>');
	for (var i=0; i<strings.length; i++) {
		while (myString.indexOf(strings[i]) != -1) {
			myString = myString.replace(strings[i],'');
		}
	}
	return myString;
}

/* in a given text string, removes all leading spaces */
function leftTrim(myString) {
	while (myString.substring(0,1) == ' ') {
		myString = myString.substring(1, myString.length);
	}
	return myString;
}

/* in a given text string, removes all spaces */
function noSpace(myString) {
	while (myString.indexOf(' ')!=-1) {
		myString = myString.replace(' ','');
	}
	return myString;
}

/* pops up a new window containing the page at specified URL, with specified width and height */
function popUp(url,myWidth,myHeight) {
	// name current window, in case there are links in the popup window (which should target the current window)
	window.name = 'launcher';
	var windowType = 'height=' + myHeight + ',width=' + myWidth;
	var newwindow = window.open(url, 'newWin', windowType);
	if (window.focus) {newwindow.focus()}
	return false;
}

// divide the text string in 'myElement', if it is longer than 'maxNum' characters, into 2 chunks
// first chunk will have a hyphen after the 'breakPoint' (number) character
// second chunk will shift to the next line
function wrap(myElement,maxNum,breakPoint) {
	var text = myElement.innerHTML;
	// trim space character from the end if it exists
	if (text.substring(text.length-1,text.length)==' ') {
		text = text.substring(0,text.length-1);
	}
	if (text.length > maxNum) {
		myElement.className+= ' js wrap';
		myElement.innerHTML = text.substring(0,breakPoint) + '-<br />' + text.substring(breakPoint,text.length);
	}
}

// truncate the text string in 'myElement', if it is longer than 'maxNum' characters
// replace any characters after maxNum with trailing dots
function truncate(myElement,maxNum) {
	var text = myElement.innerHTML;
	// trim space character from the end if it exists
	if (text.substring(text.length-1,text.length)==' ') {
		text = text.substring(0,text.length-1);
	}
	if (text.length > maxNum) {
		// add title, class and trailing dots
		myElement.title = text;
		myElement.className+= ' js truncated';
		myElement.innerHTML = text.substring(0,maxNum-1) + '...';
	}
}

String.prototype.trim = function () {
  return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}

/* match a set of elements.  Input may be a single or a list of JQuery selectors */
function matchHeight() {
    var args=matchHeight.arguments;
    var maxHeight = -1;
    // args may be a single jquery selector, or a list of these...  hence the double loop.
    $.each(args, function(i,v) {
        $.each($(v), function (ii, vv) {
            var thisHeight = $(vv).height();
            if (thisHeight > maxHeight)
                maxHeight = thisHeight;
        });
    });

    // now, we've got a max height, set it...
    $.each(args, function(i,v) {
        $.each($(v), function (ii, vv) {
            var thisHeight = $(vv).height();
            if (thisHeight < maxHeight) {
                $(vv).height(maxHeight);
            }
        });
    });
}

/* end DHTML library */

/* note re MENUS & NAV HIGHLIGHTING -
- initially the function 'showActiveLis' looks at the <h1> in #rsvpcontent to determine which tabs to highlight in the nav;
- if the h1 has NO title attribute, the primaryTitle is the text string which precedes the '_' in the body's id and the secondaryTitle is the text of <h1>;
- if the h1 HAS a title attribute AND a colon, the primaryTitle is the text string which precedes the ':' and the secondaryTitle is the text string which follows it; the attribute will look something like this 'title="events : nsw"';
- if the h1 HAS a title attribute but NO colon, the secondaryTitle is null and the title attribute will give the primaryTitle only, like so 'title="home"'; this applies to menu tabs which have no sub-menu;
- this approach allows us to display in the browser a different set of headings to the ones which trigger nav highlighting (e.g. 'registration' is displayed as the text of the page's <h1> heading, but 'join now' is the corresponding tab in the main menu);
- NB: a title attribute with 2 text strings separated by a colon is required, even if only one menu tab differs from the corresponding body id or page heading ;
*/

/* initialise global variables */
var header;
var nav;
var content;
var aside;
var footer;
var primaryTitle; // the text on the top level tab in the nav which is to be highlighted; corresponds to site section
var secondaryTitle; // the text on the 2nd level tab in the nav which is to be highlighted; corresponds to page heading
//var init = false;

/* set values for global variables (above); then call title setting and tab highlighting functions */
function initRSVP() {
	if (arguments.callee.done) return;
	arguments.callee.done = true;
	setTitles();
	header =$(document.body).find('div.header')[0];
	nav = $(document.body).find('div.nav')[0];
	aside = $(document.body).find('div.aside')[0];
	footer = $(document.body).find('div.footer')[0];
	showActiveLis();
	enableAltStyles();
}

// set primary and secondary titles - needed for tab highlighting and site tracking
function setTitles() {
	if (!primaryTitle) {
		content = $('#rsvpcontent')[0];
		var h1 = $(content).find('h1');
		// if h1 has a title attribute
		h1.filter(':[title!=""]').attr('title', function(i, t){
			// if the title has a colon
			if (t && t.indexOf(':') != -1) {
				// primaryTitle is the text string left of the colon; secondaryTitle is the text string right of the colon
				primaryTitle = t.split(' : ')[0];
				secondaryTitle = t.split(' : ')[1];
			}
			else {
				// otherwise, primaryTitle is the whole title
				primaryTitle = t;
			}
		});
		// if h1 has NO title attribute
		// NB: following syntax does not work in FF 3.6 (and below): if(h1.is(':[title=""]'))
		if (h1.attr('title')=='undefined' || h1.attr('title')==null) {
			// if there's a code.site-section, its title is primaryTitle
			var ssCode = $(content).find('code.site-section[title!=""]').attr('title');
			if (ssCode) {
				primaryTitle = ssCode.title;
			}
			else {
				// otherwise, primaryTitle is in the body tag's id, left of underscore, & secondaryTitle is h1's text
				primaryTitle = $(document.body).attr('id').split('_')[0];
				if (h1) { // && h1.text()!==null && h1.text()!==''
					secondaryTitle = h1.text();
				}
			}
		}
	}
}

/* if the page has a main menu (div.nav), highlight the list items that correspond to the page & section titles */
// NB - recoding in jQuery syntax now complete:
function showActiveLis() {
	var nav = $('#rsvpheader > .nav').eq(0);
	var hhss = $('head > link[href*="globalHandheld.css"]');
	if (nav) {
		// for smartphone, create a breadcrumbs div
		if (smartphone && hhss.length) {
			var bCrumbs = $('<div class="breadcrumbs m cf"/>');
			nav.append(bCrumbs);
		}
		// loop through all the list items in the main menu
		nav.find('li').each(function() {
			var li = $(this);
			// strip out text of any sup tag within the a tag child of the list item
			var a = li.children('a').eq(0);
			var sups = a.children('sup');
			var text = sups.length ? a.text().split(sups.eq(0).text())[0].trim().toLowerCase() : a.text().toLowerCase();
			// if the list item is top level (not a sub-nav)
			if (li.parent().parent().hasClass('nav')) {
				// if its text matches the primaryTitle, (NB: li.mailbox may also include a number - unread messages)
				var pt = primaryTitle.toLowerCase();
				// hide the 'help' top nav if user is logged in
				// but allow help sub-menu to be displayed (by hiding li > a)
				var noHelp = false;
				if (isMember && li.children('a').text()==='Help') {
					li.css({'margin':0, 'padding':0, 'width':0});
					li.children('a').hide();
					noHelp = true;
				}
				if (((text.indexOf('mailbox')!=-1) && (pt=='mailbox')) || text==pt) {
					// mark that item as 'current'
					li.addClass('js current');
					// for smartphones,
					if (smartphone && hhss.length) {
						// clone the current item's link and append to breadcrumbs
						a.clone().appendTo(bCrumbs);
						// if current item has a nested list, rebadge it, shift it and show it on clicking new 'go to' element
						var subNav = li.children('ul');
						if (subNav.length) {
							$('<span class="trigger down fr pc crp m b1m mr10 z6 pr">go to</span>').click(function() {
								subNav.addClass('subnav nl m p5 b1p z5 pa r0').appendTo(bCrumbs.addClass('pr')).toggle('slow');
								$(this).toggleClass('b1p').toggleClass('b1m');
							}).prependTo(bCrumbs);
						}
					}
					// insert a marker inside the main menu and position it over the current list item
					if (!smartphone && !noHelp) {
						// NB: it appears Optimost is setting display for #rsvpcontainer to 'none' then back to 'block' for some reason
						// this returns a '0' for position() and width() queries on all child elements
						if ($('#rsvpcontainer').css('display')=='none') {
							$('#rsvpcontainer').css('display','block');
						}
						var mLeft = li.position().left+(li.outerWidth()/2)-9;
						nav.append($('<span class="marker pa db z5"/>').css('left',mLeft));
					}
				}
				// for IE7, shift mail count outside the parent to prevent truncation
				if ((ieNum==7) && li.hasClass('mailbox')) {
					var mSup = li.find('sup').eq(0);
					mSup.appendTo(li).css({'left':'385px','top':'-5px'});
				}
			}
			else {
				// if the list item is a sub-nav and its text matches the secondaryTitle, mark that item as 'current'
				if (secondaryTitle && (text==secondaryTitle.toLowerCase())) {
					li.addClass('js current');
					// for smartphones, clone the current item's link and append to breadcrumbs
					if (smartphone && hhss.length) {
						a.clone().appendTo(bCrumbs).before('<span class="xmc"> &gt; </span>');
					}
				}
			}
		});
		// for smart phones only, ### restructure menu ###
		if (smartphone && hhss.length) {
			// shift 'stamps remaining' link, [data-type=expiry] and a help link into #rsvpfooter
			var rFoot = $('#rsvpfooter').addClass('s');
			var pNav = $('.header nav[data-type=profile]').eq(0);
			var helpClone = pNav.find('a[href*="/help/index"]').clone().addClass('db dn st m5 pt5 btd');
			var srLink = $('#networkStripTop > .rsvp > a[href*="user/purchaseHistory"]').eq(0).addClass('dn st ml5');
			var expiry = pNav.find('[data-type=expiry]').eq(0);
			var buyClone = expiry.next('a').clone().addClass('dn mr5');
			var expiryClone = expiry.clone().addClass('st ml5').prepend(buyClone);
			rFoot.prepend(srLink,expiryClone,helpClone);
			// if current tab has a nested list, copy and insert at start of #rsvpfooter; add 'Go to' heading
			nav.find('li.current > ul').eq(0).clone().prependTo(rFoot).prepend('<h4 class="bbd mb5 pb5">Go to:</h4>');
			// shift logout link into #rsvpheader
			$('#networkStripTop > .rsvp > a:contains("Logout")').addClass('wc st mt10 mr10 fr').removeClass('bl1x').prependTo($('#rsvpheader'));
			// shift login link above .nav
			nav.find('li.login a').addClass('wc st mt10 mr10 fr').prependTo($('#rsvpheader'));
			// shift .join and .full-menu into ul.fl
			nav.find('li.join,li.full-menu').appendTo(nav.children('ul.fl'));
			// rebadge ul.fr and shift into #rsvpcontent
			nav.find('ul.fr').hide().removeClass('fr').addClass('sections mb0 z5 pa al').appendTo($('body'));
			var currNav = nav.find('li.current').eq(0);
			nav.find('li.full-menu a').click(function(e) {
				e.preventDefault();
				// on clicking 'sections' toggle ul.sections (as an overlay)
				$('ul.sections').css('top',bCrumbs.offset().top);
				$('ul.sections').toggle('slow');
				$(this).parent().toggleClass('current');
				currNav.toggleClass('current');
				return false;
			});
			/*/ on clicking ul.sections, hide it, and shift 'current' class back to current nav
			$('ul.sections').click(function() {
				$(this).hide('slow');
				currNav.toggleClass('current');
				nav.find('li.full-menu').toggleClass('current');
			});*/
		}
	}
}

/* gets the charcode of the key pressed for an onkeypress event - assumes an event object is in scope from the event callback!
  you should check the result for a zero value, zero means it was a non-printable character and you should not process it. */
function getOnKeypressEventCharCode(evt) {
	// IE does not send the keypress events at all for those characters, and when FF does, it puts zero in the charCode
	// @see http://www.quirksmode.org/js/keys.html
	var myCharCode;
	if (evt) {
		myCharCode = evt.charCode;
	}
	else {
		myCharCode = event.keyCode;
	}
	return myCharCode;
}

/* ### START FORM FUNCTIONS ### */

/* alert the user if the number of characters entered equals the maximum length set for the input field or text area */
function limitEntry(evt) {
	// don't alert the user if it looks like they did not press a printable character
	var charCode = getOnKeypressEventCharCode(evt);
    var self = $(this);
	if (charCode == 0)
		return;
	// the character limit is either the value of the 'maxlength' attribute for an input field,
	// or the number following 'maxlength_' in the class name of a text area
	var limit = (self.attr('maxlength')) ? self.attr('maxlength') : self.attr('class').split(' ')[0].split('maxlength_')[1];
	if (limit <= 0)
		return;
	// ### following block only concerns step 3 rego & profile update:
	// if there is a sibling element with class 'count',
	// write the difference between the limit and the current no. of characters entered
	// to the span inside .count
	var counts = self.parent().find('.count span').text((limit-(countUtf8Bytes(self.val())+1)>= 0) ? limit-(countUtf8Bytes(self.val())+1) : 0);

	// end step 3 rego & profile update code ###
	// if the character limit is reached, alert the user
	if ((countUtf8Bytes(self.val()) == limit)&&(limit>16)) {
		alert('Your text has reached the ' + limit + ' character limit.');
	}
	// in a textarea, prevent user from typing additional text beyond the limit
    setTimeout(function () { // timeout is so that the event has time to alter the field
        if (countUtf8Bytes(self.val()) > limit) {
            self.val(truncateUtf8(self.val(), limit));
        }
    }, 1);
}

// this is intended to replicate server behaviour
function countUtf8Bytes(s) {
    // first cut some unicode characters down to their ascii equivalents
    s = s.replace(/[\u2019\u2018]/, "'");
    s = s.replace(/[\u2013\u2014]/, "-");
    s = s.replace(/[\u201C\u201D]/, "\"");

    var encoded = encodeURIComponent(s);

    // these are crappy microsoft quote marks and will be reduced to 1 byte in the string converter
    encoded = encoded.replace(/%E2%80%9[89CD]/gi, 'q');

    // the rest get as many bytes as their encoded strings indicate
    encoded = encoded.replace(/%[0-9A-F][0-9A-F]/gi, 'x');

    return encoded.length;
}

function truncateUtf8(s, maxlength) {
    var ret = s;
    while (countUtf8Bytes(ret) > maxlength)
        ret = ret.substring(0, Math.min(maxlength, ret.length - 1));
    return ret;
}

/* prevent double submission of forms by users - disable each form after first click on submit button */
var submitted = false;
function disableDoubleSubmit(e) {
	var allowSubmit = !submitted;
	if (!allowSubmit && e)
	{
		try {
		  e.stopPropagation();
		  e.preventDefault();
		} catch (ex) {}

	}
	submitted = true;

	//alert('allowSubmit=' + allowSubmit);
    return allowSubmit; // for IE-compatibility - try/catch for the same reason
}

function resetSubmitted() {
	//alert('resetSubmitted');
	submitted = false;
}

/* loop through all 'input' and 'textarea' elements; if a 'maxlength' attribute is specified in an input field or the class name of a text area contains the string 'maxlength_', attach the function 'limitBox' - to be triggered on key up; NB - 'maxlength' is not an allowable attribute in 'textarea' in XHTML */
function attachFormFunctions() {
	$('input, textarea').each(function(i, el){
		if ($(el).attr('maxlength') || $(el).filter('[class*="maxlength_"]').length) {
			// figure out what the maxlength would be...
			var limit = $(el).attr('maxlength') ? $(el).attr('maxlength') : $(el).attr('class').split('maxlength_')[1];
			if (limit > 0) {
				$(el).keypress(limitEntry);
			}
		}
	});
	// attach 'disableDoubleSubmit' function to every to be triggered on submit
	// **unless** the form has the 'no-disable' class - in which case it's up to that page to deal with it
	$('form:not(.no-disable)').submit(disableDoubleSubmit);
	// enable styling of legends (and fieldsets) in all forms with class 'type-Q':
	if ($('.type-Q').length) {
		// exclude smart phones from remainder of function, because type-Q layouts don't work in small viewports
		if (smartphone) {return;}
		// a) mark each .type-Q fieldset or fieldset.type-Q, to remove border
		$(".type-Q fieldset,fieldset.type-Q").addClass('nb');
		// b) convert each 'legend' to 'strong' tag,
		$(".type-Q legend").each(function() {
			var classStr = ($(this).parent().hasClass('v2')) ? 'w25' : 'w33';
			// if there is no sibling div, wrap all siblings in div
			if (!$(this).siblings('div').length) {
				$(this).siblings().wrapAll('<div/>');
			}
			$(this).replaceWith('<strong class="js ' + classStr + '">' + $(this).html() + '</strong>')
		});
	}
}
/* ### END FORM FUNCTIONS ### */

/* ### CREATE AN IFRAME ### */
// 5 possible parameters are: 0 - src, 1 - width, 2 - height, 3 - node to append to, 4 - node to insert before, 5 - map of additional attributes
// NB: parameters 3 & 4 above are optional
function doIframe() {
	var newIframe;
	if (is_ie8down) {
		var iframeHtml = '<iframe frameborder="0" marginwidth="0" marginheight="0" width="' + doIframe.arguments[1] + '" height="' + doIframe.arguments[2] + '"';
		var n;
		for (n in doIframe.arguments[5]) {
			iframeHtml = iframeHtml + ' ' + n + '="' + doIframe.arguments[5][n] + '"';
		}
		iframeHtml = iframeHtml + '></iframe>';
		newIframe = document.createElement(iframeHtml);
	}
	else {
		newIframe = document.createElement('iframe')
		newIframe.setAttribute('frameborder',0);
		newIframe.setAttribute('marginwidth',0);
		newIframe.setAttribute('marginheight',0);
		newIframe.setAttribute('width',doIframe.arguments[1]);
		newIframe.setAttribute('height',doIframe.arguments[2]);
		if (doIframe.arguments[5]) {
			var n;
			for (n in doIframe.arguments[5]) {
				newIframe.setAttribute(n, doIframe.arguments[5][n]);
			}
		}
	}
	newIframe.setAttribute('src',doIframe.arguments[0]);
	newIframe.setAttribute('scrolling','no');
    newIframe.onLoad = setAdTimer();
    if (doIframe.arguments[3]) {
		var node = doIframe.arguments[3];
		node.appendChild(newIframe);
		if (doIframe.arguments[4]) {
			node.insertBefore(newIframe,doIframe.arguments[4]);
		}
	}
}

/* ### IMPORT ADS ### */

var bust = Math.floor(1000000*Math.random());
var adLoadDelay = 100;
var adArr = new Array();
var adLoadTimer = null;

function doIFrameAd(parentNodeName, ifWidth, ifHeight, adUrl) {
    var parentNode = $('#' + parentNodeName)[0];
    if (parentNode) {
        var iframeAd = {url:adUrl, ifWidth:ifWidth, ifHeight:ifHeight, parentNodeName:parentNodeName};
        adArr.push(iframeAd);
    }
}

function doLocalAd(parentNodeName, adHTML) {
	var parentNode = $('#' + parentNodeName)[0];
	if (parentNode) {
        var localAd = {innerHTML:adHTML, parentNodeName:parentNodeName};
        adArr.push(localAd);
    }
}

function setAdTimer() {
    if (adLoadTimer == null) {
        var randomisedDelay = adLoadDelay + (adLoadDelay/2 * (0.5-Math.random()));
        adLoadTimer = window.setTimeout('loadSingleAd();', randomisedDelay);
    }
}

function loadSingleAd() {
    adLoadTimer = null;
    if (adArr.length > 0) {
        var ad = adArr[0];
        adArr = adArr.slice(1);
        var parentNode = $('#' + ad.parentNodeName)[0];
        // double check that the target node still exists
        // as there is a possibility that JS will have removed it
        // from our DOM prior to this method actually getting called
        if (parentNode) {
            if (ad.innerHTML) {
                parentNode.innerHTML = ad.innerHTML;
            }
            else if (ad.url) {
                doIframe(ad.url, ad.ifWidth, ad.ifHeight, parentNode);
            }
        }
        setAdTimer();
    }
}

function getURLParam(name) {
    try {
        name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
        var results = new RegExp("[\\?&]"+name+"=([^&#]*)").exec(window.location.href);
        if(results) {
          return unescape(results[1]);
        }
    }
    catch (e) {
        // eh
    }
  return false;
}

var dfpTileNumber = 1;
var dfpCacheBustVal = Math.floor(1000000*Math.random());
function getDFPAdUrl(ifWidth, ifHeight, otherTagVals)
{
    initRSVP();

    // enforce appropriate case on these tags...
    var primaryTitleText = !primaryTitle || primaryTitle == null ? 'na' : adTagFormat(primaryTitle).toLowerCase();
    var secondaryTitleText = !secondaryTitle || secondaryTitle == null ? 'na' : adTagFormat(secondaryTitle).toLowerCase();

    var zoneStr = '';
    if (primaryTitleText != 'na')
        zoneStr += '/' + primaryTitleText;

    if (secondaryTitleText != 'na')
        zoneStr += '/' + secondaryTitleText;

    var siteStr;
    var sizeStr = ';sz=' + ifWidth + 'x' + ifHeight;
    var adCallParam = getURLParam("adcall");
    var adCallSize = getURLParam("adcallsz");
    if (adCallParam && (!adCallSize || sizeStr == (';sz=' + adCallSize)))
    {
        siteStr = 'onl.adtester';
        var adCallKeyword = getURLParam('adcallkw');
        if (adCallKeyword)
        {
            sizeStr = ';adcallkw=' + adCallKeyword + sizeStr;
        }
    }
    else
    {
        siteStr = 'onl.rsvp';
    }

    var isWestnet = false;
    try
    {
        // track certain cobrands directly
        isWestnet = document.location.href.toLowerCase().indexOf("westnet") > -1;
    }
    catch (e)
    { /*alert(e);*/
    }

    var skinStr = '';
    try
    {
        // track certain cobrands directly
        if (isWestnet)
        {
            skinStr = ';skin=westnet';
        }
    }
    catch (e)
    { /*alert(e);*/
    }

    var tileStr = ';tile=' + dfpTileNumber++;
    var bustStr = ';ord=' + dfpCacheBustVal;

    var catStr = '';
    if (primaryTitleText != 'na')
        catStr = ';cat=' + primaryTitleText.toLowerCase();
    var subCatStr = '';
    if (secondaryTitleText != 'na')
        subCatStr = ';subcat=' + secondaryTitleText.toLowerCase();

    var targettingAttrsStr = targAttr ? targAttr.toLowerCase() : "";
    if (targettingAttrsStr.length > 0)
    {
        if (!targettingAttrsStr.indexOf(';') == 0)
            targettingAttrsStr = ';' + targettingAttrsStr;
    }

    // this one isn't usually defined...
    var pageTargAttrStr = "";
    try
    {
        pageTargAttrStr = pageTargAttr ? pageTargAttr.toLowerCase() : "";
        if (pageTargAttrStr.length > 0)
        {
            if (!pageTargAttrStr.indexOf(';') == 0)
                pageTargAttrStr = ';' + pageTargAttrStr;
        }
    }
    catch (e)
    {
        // eh, expected when not defined...
    }

    var targettingLocnStr = targLocn ? targLocn.toLowerCase() : "";
    if (targettingLocnStr.length > 0)
    {
        if (!targettingLocnStr.indexOf(';') == 0)
            targettingLocnStr = ';' + targettingLocnStr;
    }

    var otherTagValsStr = otherTagVals ? otherTagVals : '';
    if (otherTagValsStr.length > 0 && !otherTagValsStr.indexOf(';') == 0)
        otherTagValsStr = ';' + otherTagValsStr;

    // for some ad classes, we go via an intermediary file located our own domain, so that we can dynamically resize the IFRAME without cross site scripting issues
    var useIntermediary = false;
    var isLeaderboard = ifWidth == 728 && ifHeight == 90;
    var isMRec = ifWidth == 300 && ifHeight == 250;
    if (isLeaderboard)
    {
        sizeStr += ',468x60';
        useIntermediary = true;
    }
    else if (isMRec)
    {
        useIntermediary = true;
    }

    var adUrl = 'http://ad-apac.doubleclick.net/adi/' + siteStr + zoneStr + catStr + subCatStr + pageTargAttrStr + targettingAttrsStr + targettingLocnStr + otherTagValsStr + skinStr + sizeStr + tileStr + ';dcopt=ist' + bustStr;

    if (useIntermediary)
    {
        adUrl = '/adIM.html?ad=' + escape(adUrl.replace('/adi', '/adj'));
        if (isMRec)
        {
            // give MRec a white background, the default is purple which looks odd
            adUrl += "&bg=f";
        }
        if (isWestnet && isLeaderboard)
        {
            // westnet has a different colour header
            adUrl += "&bg=F4EAF3";
        }
    }
    return adUrl;
}

function getFFXBTHTML(otherRestrictors) {
	var localBust = Math.floor(1000000*Math.random());
	initRSVP();
	// enforce appropriate case on these tags...
	var primaryTitleText = !primaryTitle || primaryTitle == null ? 'na' : adTagFormat(primaryTitle);
	var secondaryTitleText = !secondaryTitle || secondaryTitle == null ? 'na' : adTagFormat(secondaryTitle);
	var areaTag = "DATING.RSVP." + primaryTitleText + "." + secondaryTitleText;

	var areaStr = "/AREA=" + areaTag.toUpperCase();

	var otherRestrictorsStr = otherRestrictors ? otherRestrictors : "";
	if (otherRestrictorsStr.length > 0 && !otherRestrictorsStr.indexOf('/') == 0)
		otherRestrictorsStr = "/" + otherRestrictorsStr;

	var fcam_segvars='/CCID=1' + areaStr + '/BT=1' + targLocn + otherRestrictorsStr + '/acc_random='+localBust;
	return '<img src="http://direct.fairfax.com.au/vserver' + fcam_segvars + '" style="display:none"></img>';
}

function adTagFormat(tagStr) {
	// start by treating the same as the menu...
	tagStr = menuFormat(tagStr);
	// next, strip all &amp; etc. characters
	var re = /&(\w+;)/g
	while (tagStr.match(re))
		tagStr = tagStr.replace(re, '');
	// strip all single quotes...
	re = /'/g
	while (tagStr.match(re))
		tagStr = tagStr.replace(re, '');
	// covert all whitespace down to a single "_" character...
	re = /\s+/g
	while (tagStr.match(re))
		tagStr = tagStr.replace(re, '_');
	return tagStr;
}

// initialise global variables for ad divs (and an array)
// note - in certain site sections and/or pages, ad positions within .aside may be changed
var fAds;
var mlac;
var pAds;
var mrac;
var nAd;
var adDivs;

// if .aside exists as a child of body, shift all footer ads there, otherwise tweak layout of footer ads
function doAdLayout() {
	fAds = $(footer).find('div.ads')[0];
	mlac = $('#multiLinkAdContainer')[0];
	pAds = $('#partner-ads')[0];
	mrac = $('#mrecAdContainer')[0];
	nAd = $('#networkAd')[0];

	adDivs = new Array(mrac,pAds,mlac,nAd);
	// don't proceed if ads or aside don't exist or if on mytype or gifts landing pages
	if (aside && fAds && !$('#mytype_index')[0] && !$('#gifts_index')[0]) {
		// shift all .footer ads into .aside
		$([mrac,pAds,mlac,nAd]).each(function(i, el){
			if($(el) && $(el).parent().is($(fAds))){
				$(aside).append($(el));
			}
		});
		// remove .ads node
		removeNode(fAds);
	}
	if ((!aside && pAds) || ($('#mytype_index')[0] && pAds) || ($('#gifts_index')[0] && pAds)) {
		// shift #multiLinkAds & #partner-ads into new div.wrap (1st child of .ads), unless #partner-ads is outside .ads
		if (fAds && mlac && $(pAds).parent().is($(fAds))) {
			var wrap = $('<div/>').addClass('js wrap');
			$(fAds).find(':first-child').before(wrap);
			wrap.append(pAds);
			wrap.append(mlac);
		}
	}
	// shift some ads within .aside
	if (aside && mrac && $(mrac).parent().is($(aside)) && $('#success-stories')[0]) {
		$(mrac).before($('#success-stories'));
	}
	if (aside && mlac && $(mlac).parent().is($(aside)) && $('#whats-new')[0]) {
		$(mlac).before($('#whats-new'));
	}
}

/* END AD FUNCTIONS */
function attachBlankTargets(parent) {
	// loop through all <a> elements with class 'ext',
	$(parent).find('a.ext').each(function(i, el){
		// attach the target attribute with value '_blank'
		$(el).attr('target', '_blank');
		if ($(el).attr('href').indexOf('clickext.jsp') > -1) {
			// append to click counting page for external links a special URL flag to actually trigger recording of the counting
			// this is only done by JS later on, so that robots who follow the links (even though they shouldn't) don't actually
			// boost the counters
			$(el).attr('href', function(j, h){
				return h + (h.indexOf('?') == -1 ? '?' : '&') + 'c=' + bust;
			});
		}
	});
}

/* attach target='_blank' or alert function or title evaluation to various nodes */
function attachTarget() {
  attachBlankTargets(document.body);
	// loop through all <a> elements in networkStripTop
	$('#networkStripTop').find('a').click(function(){
		return link(this, 'top');
	});
	// loop through all <a> elements in networkStripBot
	$('#networkStripBot').find('a').click(function(){
		return link(this, 'tail');
	});
	// loop through all nodes with class 'alert' in #rsvpcontent and attach a function which alerts the title attribute value on click
	$('#rsvpcontent .alert[title]').click(function() { alert(this.title); });
	// loop through all elements with class 'function' in #rsvpcontent,
	$(content).find('.function').click(function(){
		eval(this.title);
		// ignore hyperlink
		if ($(this).is('a')) {
			return false;
		}
	});

	// loop through all buttons with class 'print',
	$(content).find('button.print').click(printPage);
}

function printPage() {
	window.print();
}

function link(anchor,source) {
    if (anchor.href.indexOf("?") == -1) {
		var ahref = anchor.href
		var ahrefAnc = '';
		if (ahref.indexOf('#') != -1) {
			ahrefAnc = ahref.substring(ahref.indexOf('#'));
			ahref = ahref.replace(ahrefAnc, '');
		}
		anchor.href =  " " + ahref + "?s_wid=WS:rsvp:" + source + ahrefAnc;
    }
    return true;
}

// Returns anchor from link string
function returnHash(a_sLink){
   var hashSection = a_sLink.match(/#\w.*/);
    if (hashSection != null && hashSection.length)
        hashSection = ('' + hashSection).substr(1);
    return hashSection;
}

function rescrollToAnchor() {
    var anchorLoc = returnHash(window.location.href);
    if (anchorLoc != null)
        try { window.location.href = '#' + anchorLoc; } catch (e) { /* eh */ }
}

/* find the position of any element in current page (or relative to the supplied parent); note - returns an array of 2 items */
function findPos(obj, stopAtParent) {
	var curleft = curtop = 0;
	if (obj && obj.offsetParent) {
		curleft = obj.offsetLeft
		curtop = obj.offsetTop
		while (obj = obj.offsetParent) {
			if (obj == stopAtParent) {
				break;
			}
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
		}
	}
	return [curleft,curtop];
}

// create and close a ### LIGHTBOX ### with specified content and width and height
// fadeIn and fadeOut rates should remain constant across all lightboxes
$.fn.doLightbox = function(options) {
	// global settings
	var settings = {
		element: undefined,
		elements: [],
		content: undefined,
		slideshow: false,
		mask: true,
		open: false,
		height: null,
		width: null,
		zIndex: 100,
		boxClass: undefined,
		onload: function(){ },
		onunload: function(){ }
	};
	// set dimensions of both mask and lightbox
	var setDimensions = function(box, mask){
		// set top position of box as half the window height less half the lightbox height, plus window scrollTop
		var bTop = $(window).scrollTop() + Math.floor(iphone ? 10 : !settings.height ? 100 : ($(window).height()/2 - settings.height/2)),
			// set left position of box as half the window width less half the lightbox width
			bLeft = iphone ? 10 : ($(window).width()/2 - settings.width/2),
			// set width & height
			bWidth = iphone ? 'auto' : settings.width + 'px';
			bHeight = iphone || !settings.height ? 'auto' : settings.height + 'px';

		// apply top, left, width and height to lightbox
		box.css({
			'top': bTop + 'px',
			'left': bLeft + 'px',
			'width': bWidth,
			'height': bHeight
		});

		// CSS 'height:100%' doesn't cover whole window, so set mask height through js:
		if (smartphone) {
			mask.height(Math.max($(document.body).height(), $(window).height(), $(window).scrollTop() + box.outerHeight() + 40));
		} else {
			mask.height(Math.max($(document.body).height(), $(window).height()));
		}
		// width:100% won't fill viewport in iPad; need to set at iPad's default landscape pixel width
		if (ipad) {
			//mask.width(Math.max($(document.body).width(), $(window).width()));
			mask.width(1024);
		}
	};
	// show and fade in, both mask and lightbox
	var show = function(box, mask){
		if (is_ie8down) {
		// for IE8-, set initial opacity to 0 (setting through CSS has no effect)
			mask.css("filter","alpha(opacity=0)").animate({opacity:.75},400).show();
			box.css("filter","alpha(opacity=0)").animate({opacity:1},400).show();
		} else {
			mask.fadeIn(400);
			box.fadeIn(400);
		}
	};
	// hide and fade out, both mask and lightbox
	var close = function(){
		doLightbox.close();
	};
	// function to replace the thumb src with full image src
	var imgSrcReplace = function(src){
		return (typeof src !== 'undefined') ? src.replace(/([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)_2\.jpg/,"$1_$2_$3_$4_1.jpg") : false;
	};
	var slideshow = function(el, i){
		var prev = $('<a href="#" class="arrow prev" title="previous">previous</a>'),
			next = $('<a href="#" class="arrow next" title="next">next</a>'),
			goTo = i;
			size = settings.elements.length,
			imgsrc = imgSrcReplace($(el).find('img').attr('src')),
			image = $('<img/>', { src: imgsrc, load: function () {} }),
			content = (typeof settings.content == 'object') ? settings.content : $('<div/>',{'class':'ac'});

		content.empty().append(image);
		// insert previous and next arrow buttons
		image.before(prev).after(next);
		// hide if the clicked one is first or last image
		content.find('> .arrow:hidden').show();
		if(i == 0) prev.hide();
		if(i == (size-1)) next.hide();
		content.find('> .arrow').click(function(){
			$(this).siblings(':hidden').show();
			// find out if clicked or next was clicked and replace the src of image
			goTo = $(this).hasClass('prev') ? goTo-1 : goTo+1;
			image.attr('src', imgSrcReplace($(settings.elements[goTo]).find('img').attr('src')));
			if(goTo == 0) prev.hide();
			if(goTo == (size-1)) next.hide();
			return false;
		});
		return content;
	};
	// if there is no element attached to the function, create a virtual div
	var element = (this.length !== 0) ? this : $('<div />');
	return element.each(function(i, el) {
		// unbind & bind when content has changed
		if($(el).hasClass('lightboxed')){
			$(el).removeClass('lightboxed').unbind();
		}

		// If options exist, lets merge them
		// with our default settings
		if (options) { 
			$.extend(settings, options);
		}
		settings.elements.push(this);
		$(el).addClass('lightboxed').click(function(){
			// create semi-transparent mask (to cover page) and lightbox (to sit above mask, at centre)
			// create a 'close' span for lightbox, at top right corner
			var self = el,
				mask = $('<div class="mask" class="js"/>').css({'z-index': settings.zIndex}),
				box = $('<div class="lightbox" class="js"/>').css({'z-index': settings.zIndex + 1}),
				inner = $('<div class="inner"/>'),
				closeX = $('<span class="close">close</span>'),
				closeD = $('<span class="du crp">close</span>');

			// set active element
			settings.element = el;

			// insert content in .inner, then .inner and .close spans in #lightbox
			inner.append(settings.content);
			box.append(inner).append(closeX);
			if(smartphone) box.append(closeD);
			// insert mask and lightbox in body, and hide both
			if(settings.mask)
				$(document.body).append(mask.hide());
			if (settings.boxClass)
				box.addClass(settings.boxClass);
			$(document.body).append(box.hide());

			// apply top, left, width and height to lightbox
			setDimensions(box, mask);

			// show and fade in, both mask and lightbox
			show(box, mask);

			// attach prev, next buttons if slideshow enabled
			if(settings.slideshow) inner.append(slideshow(self, i));

			// hide and fade out, both mask and lightbox
			// define onclick function which conceals the lightbox, then attach to mask and close span:
			$.each([mask[0], closeX[0], closeD[0]], function(i, el){
				$(el).click(close);
			});

			// add 'target="blank"' to any a.ext
			attachBlankTargets(box);

			//callback
			settings.onload(settings.content, $(settings.element));
			box.bind("lightboxClose", function () {
				//callback
				settings.onunload(settings.content, $(settings.element));
			});

			return false;
		});

		// open when loaded
		if(settings.open) $(el).click();
	});
};
// map old function, extend the plugin to show lightbox onload
// usage doLightbox(obj), obj = { content, width, height, zIndex, onload } 
var doLightbox = function(options){
	var settings = { open: true };
	if (options) { 
		$.extend(settings, options);
	}
	$.fn.doLightbox(settings);
}
// add generic close function
// hide and fade out, both mask and lightbox
// call like so doLightbox.close();
doLightbox.close = function(){
	$(".lightbox").triggerHandler("lightboxClose");
	$('.mask, .lightbox').animate({opacity:0},'slow',function() {$(this).remove();});
	return false;
}

/* //a simple ajax load using function above:
// on clicking 'More on topic X' link, create a lightbox and insert the main content of the hyperlinked page
$("#registration-form a:contains('More on topic X')").click(function(e) {
	e.preventDefault();
	var tempDiv = $('<div class="temp"/>');
	tempDiv.load($(this).attr("href") + " #rsvpcontent > *", null, function(responseText,textStatus) {
		if (textStatus == 'success') {
			doLightbox({
				content: tempDiv.html(),
				width: 645,
				height: 360
			});
			// tweak lightbox here - extra buttons, add scrolling etc
		}
		else {
			location.href = $(this).attr("href");
		}
	});
});
*/

// turn specified node into a ### POPUP .box ### with specified width
// hide node then show popup next to specified link on click
// offset node from link by specified no. of pixels (left & top)
function popupNotice(myLink,myNode,myWidth,offsetL,offsetT) {
	// shift node into body (removes issues with IE7), then hide it
	$(document.body).append($(myNode).hide());
	// style as mid-cream box with a left pointer, and insert a 'close' trigger
	$(myNode).attr('class','box point left cm b1t al pa').append($('<span class="js close">Close</span>').click(function () {
		$(myNode).toggle();
	}));
	// move the next two lines to the click event, to ensure correct placement of node:
	// set width, top and left for node (position at right of link)
	//$(myNode).css({'width': myWidth, 'left': $(myLink).offset().left + $(myLink).width() + offsetL, 'top': $(myLink).offset().top + offsetT});
	// round corners for IE
	if (!radius) {
		roundCorners($(myNode));
	}
	// on clicking specified link, set the left and top position of the node, and toggle it
	$(myLink).click(function() {
		// set width, top and left for node (position at right of link)
		$(myNode).css({'width': myWidth, 'left': $(myLink).offset().left + $(myLink).width() + offsetL, 'top': $(myLink).offset().top + offsetT});
		$(myNode).toggle();
		// prevent link being followed
		return false;
	});
}

/* in all browsers - hide embedded media (audio/video) when mousing over certain menu tabs,
 to prevent obscuring of child menu lists;
 note - an iframe will not sit above an embedded movie, no matter what the z-index set */
function addToggleMediaVisibility() {
	if (nav) {
		for (var i=0; i<nav.childNodes.length; i++) {
			var node = nav.childNodes[i];
			if (node.nodeName.toLowerCase()=='li') {
				// when mousing over a menu tab,
				node.onmouseover=function() {
					// hide media objects in page (if they would obscure the current submenu)
					setMediaVisibility(this, false);
				}
				// when mousing out of a menu tab,
				node.onmouseout=function() {
					// show the hidden media objects
					setMediaVisibility(this, true);
				}
			}
		}
	}
}

function setMediaVisibility(myNode, vis) {
	var navText = $(myNode).find('a').text();
	var mediaEmbedded = ($('#media-player-object')[0] || $('#media-player-embed')[0]);
	var navAffected = ((navText=='My Home')||(navText=='Search')||(navText=='eMagazine')||(navText=='Mailbox'));
  var zineArticle = $('#zine_articleDisplay')[0];
	// if media is currently embedded in the page and the currently moused over menu tab is 1 of the 4 named above,
	if (mediaEmbedded&&navAffected&&!zineArticle) {
		// hide the #media div which contains embedded media - works in Mozilla & IE
		var newVis = vis ? 'visible' : 'hidden';
		var oldVis = $('#media').css('visibility');
		if (newVis != oldVis) {
			$('#media').css('visibility', newVis);
		}
	}
}

/* ### TODO ### currently used only in photo_modifyMyPhoto.js & applied to ALL IE browsers; if only needed by IE6, remove */
function disableOtherButtons(btn) {
	var self = $(btn);
	self.closest('form').find('button').each(function(i, el){
		if (!$(el).is(self) && ($(el).attr('type') != 'button')) {
			$(el).prop('disabled', true);
		}
	});
}

/* check or uncheck all checkboxes within a given element */
function toggleAllCheckboxes(myElement) {
	$(myElement).find(':checkbox').prop('checked', function(i, c){ return !c });
}

function openSubscribePrompt() {
	var sPrompt = $('#subscribe-prompt');
	var pos = $(this).offset();
	// if trigger is close to right edge of content, shift prompt inward
	var sLeft = pos.left > 470 ? pos.left-90 : pos.left;
	sPrompt.css({'left': sLeft + 'px', 'top': (pos.top - 30) + 'px'});
	sPrompt.filter('.h').removeClass('h');
}

function closeSubscribePrompt() {
	$('#subscribe-prompt:not(.h)').addClass('h');
}

// disable links with class 'subscriber-only'
function handleSubscriberOnlyLinks() {
	var soLinks = $(content).find('a.subscriber-only');
	if(soLinks.length){
		$(content).append($('<span />', {'id': 'subscribe-prompt', 'class': 'js di w pa b1x p5 ac z5 h'}).text('RSViP members only'));
		soLinks.attr('href', '#').hover(openSubscribePrompt, closeSubscribePrompt).click(function(){
			this.blur();
			return false;
		})
	}
}

function goBack() {
	window.history.back();
}

// disable links with class 'go-back'; send to previous page
function handleGoBackLinks() {
	$(content).find('a.go-back').click(function(){
		goBack()
		return false;
	});
}

/* ### SUCCESS STORIES ROTATOR - common to guest home & top100 ### */
var stories;
var	counter;
var currentStory;
var reverse = false;

var successStoriesSetup = false;

function setupSuccessStories() {
	if ($('#success-stories').length && !successStoriesSetup) {
		// set delays before running fadeIn & fadeOut functions, in milliseconds
		myTimeoutIn = 1000; //1 sec
		myTimeoutOut = 7000; //7 secs
		// set name of function to be called at start of fadeIn function (switches the content or property/ies of fadeObject);
		mySwitch = 'rotateStories';
		// set element to be faded in and out
		fadeObject = $('#success-stories')[0];
		// set initial opacity of fadeObject to zero;
		// NB - if starting opacity is set as a numeric value rather than as a string, 'increaseOpacity' function fails in Mozilla
		eval('fadeObject' + styleObject + opacityObject + ' = "0"');
		// optional function call - add previous & next spans
		doNextPrevious();
		// all set, now away we go
		fadeIn();
		successStoriesSetup = true;
	}
}

function doNextPrevious() {
	if (!$(fadeObject).find('span.next').length) {
		stories = $(fadeObject).find('div.section');
		counter = stories.length-1;
		var bot = $(fadeObject).find('b.bot:first');
		bot.before($('<span/>').addClass('js next').text('next').click(changeStory));
		bot.before($('<span/>').addClass('js previous').text('previous').click(changeStory));
	}
}

// hide current story & show next (or previous) story
// replace text of 'h2 strong' with 'h3' text;
function rotateStories() {
	if (stories[0]) {
		if (currentStory!=null) {
			$(currentStory).removeClass('js open');
		}
		counter = (reverse) ? counter-1 : counter+1;
		if (counter==stories.length) {
			counter=0;
		}
		if (counter<0) {
			counter=stories.length-1;
		}
		$(stories[counter]).addClass('js open');
		currentStory = stories[counter];
		var strong = $('#success-stories h2 strong').eq(0);
		var currHead = $(currentStory).find('h3') ? $(currentStory).find('h3') : $(currentStory).find('h4');
		if (strong.length && currHead) {
			strong.html(currHead.html());
		}
	}
}

// determine whether to go to next or previous story
// if auto-rotation is on, turn it off (clear timeouts)
function changeStory() {
	reverse = $(this).text() == 'previous';
	if (rotation) {
		rotation=false;
		stopFade();
	}
	rotateStories();
}
/* end #success-stories rotator */

var instSearch;
var iskSuggestions = 'e.g. cycling';

// write suggestions to #keyword-input and remove on clicking in field or on submitting form
function writeKeywordSuggestions() {
	if ($('#instant-search-form').length) {
		instSearch = $('#instant-search-form');
		var iskInput = $('#keyword-input');
		if (iskInput.length) {
			if (iskInput.val() == '' || iskInput.val() == iskSuggestions) {
				iskInput.attr({'class': 'js example', 'value': iskSuggestions}).mousedown(clearISKSuggestions);
				var button = instSearch.find('button:first');
				var parentForm = button.closest('form');
				if (parentForm.length) {
					// bah - would like to use onsubmit here, but doesn't work...
					// http://alexking.org/blog/2003/10/01/javascript-onsubmit-handler
					parentForm.click(ignoreISKSuggestions);
				}
			}
		}
	}
}

function replaceLegends() {
	// loop through all the legends in #instant-search-form
	var mh = $('#root_memberHome').length;
	instSearch.find('legend').each(function(i, el){
		// 1st legend is replaced by h2 (see above) except in member home page
		if((mh && i == 0) || !mh){
			// mark each legend as hidden by javascript (to help developers doing maintenance or revisions)
			$(el).addClass('js obsolete');
			// insert a span above the legend and copy the legend's content to it
			$(el).parent().before($('<span/>').addClass('js legend').text($(el).text()));
		}
	});
	cleanUp();
}

function ignoreISKSuggestions() {
	var iskInput = $('#keyword-input');
	if (iskInput.val() == iskSuggestions) {
		iskInput.val('');
	}
}

function clearISKSuggestions() {
	var iskInput = $('#keyword-input');
	if (iskInput.length) {
		iskInput.focus();
		if (iskInput.hasClass('example')) {
			iskInput.val('');
			iskInput.removeClass('example');
		}
	}
}

/*
 * This function highlights a text string by adding HTML tags before
 * and after all occurrences of the search term. You can pass your own tags if you'd like, or if the
 * highlightStartTag or highlightEndTag parameters are omitted or
 * are empty strings then the default <font> tags will be used.
*/
function doHighlight(blockText, searchTerm, highlightStartTag, highlightEndTag) {
  // the highlightStartTag and highlightEndTag parameters are optional
  if ((!highlightStartTag) || (!highlightEndTag)) {
    highlightStartTag = "<strong class='hilite'>";
    highlightEndTag = "</strong>";
  }
  // find all occurences of the search term in the given text,
  // and add some "highlight" tags to them (we're not using a
  // regular expression search, because we want to filter out
  // matches that occur within HTML tags and script blocks, so
  // we have to do a little extra validation)
  var newText = "";
  var i = -1;
  var lcSearchTerm = searchTerm.toLowerCase();
  var lcBlockText = blockText.toLowerCase();
  while (blockText.length > 0) {
    i = lcBlockText.indexOf(lcSearchTerm, i+1);
    if (i < 0) {
	  // no (remaining) match... append remainder of text, and empty - to complete
      newText += blockText;
      blockText = "";
    }
		else {
      // skip anything inside an HTML tag
      if (lcBlockText.lastIndexOf(">", i) >= lcBlockText.lastIndexOf("<", i)) {
        // skip anything inside a <script> block
        if (lcBlockText.lastIndexOf("/script>", i) >= lcBlockText.lastIndexOf("<script", i)) {
	      // skip anything not bounded by non-word characters
	      var precedingChar = i > 0 ? lcBlockText.substring(i-1, i) : "";
	      if (precedingChar.length == 0 || /\W/.test(precedingChar)) {
					var trailingChar = lcBlockText.length >= i+searchTerm.length+1 ? lcBlockText.substring(i+searchTerm.length, i+searchTerm.length+1) : "";
					if (trailingChar.length == 0 || /\W/.test(trailingChar)) {
						newText += blockText.substring(0, i) + highlightStartTag + blockText.substr(i, searchTerm.length) + highlightEndTag;
						blockText = blockText.substr(i + searchTerm.length);
						lcBlockText = blockText.toLowerCase();
						i = -1;
						}
					}
        }
      }
    }
  }
  return newText;
}

//  bookmark the current page
function bookmark(url,title){
  if (is_ie) {
  window.external.AddFavorite(url,title);
  }
  else if (is_moz) {
    window.sidebar.addPanel(title,url,"");
  }
  else {
    alert("Press CTRL-D to bookmark");
  }
}

// insert a 'close' "link" (span) in each popup
function insertCloser() {
	if ($(document.body).hasClass('popup')) {
		$('#rsvpheader').append($('<span/>').addClass('js close-popup').text('Close').click(closeIt));
	}
}

function closeIt() {
	window.close();
}

/* ### PROFILE SIDEBAR ### */

function getFormSubmitAsAjaxUrl(form) {
	var rval = "";
	var firstParam;
	if (form.action.indexOf('.action') != -1) {
		// struts2 url
		if (form.action.indexOf('!') != -1) {
			rval += form.action.replace(/!(\w*).action/, 'Ajax!$1.action') + '?';
		} else {
			rval += form.action.replace(/.action/, 'Ajax.action') + '?';
		}
		firstParam = true;
	}
	else {
		// jsp url
		rval += (form.action + '?ajax=true');
		firstParam = false;
	}

	var formElems = form.elements;
	if (formElems != null && formElems.length > 0) {
		for (var i = 0; i < formElems.length; ++i) {
			var elemType = formElems[i].type;
			var includeElem = ((elemType == 'radio' || elemType == 'checkbox') && formElems[i].checked) || !(elemType == 'radio' || elemType == 'checkbox');
			if (includeElem) {
				var elemName = formElems[i].name;
				var elemValue = formElems[i].value;
				if (elemName && elemValue) {
					if (firstParam) {
						firstParam = false;
					}
					else {
						rval += '&'
					}
					rval += (encodeURIComponent(elemName) + "=" + encodeURIComponent(elemValue));
				}
			}
		}
	}
	return rval;
}

function getFormParams(form) {
	var rval = "ajax=true";
	var formElems = form.elements;
	if (formElems != null && formElems.length > 0) {
		for (var i = 0; i < formElems.length; ++i) {
			var elemType = formElems[i].type;
			var includeElem = ((elemType == 'radio' || elemType == 'checkbox') && formElems[i].checked) || !(elemType == 'radio' || elemType == 'checkbox');
			if (includeElem) {
				var elemName = formElems[i].name;
				var elemValue = formElems[i].value;
				if (elemName && elemValue) {
					rval += ("&" + encodeURIComponent(elemName) + "=" + encodeURIComponent(elemValue));
				}
			}
		}
	}
	return rval;
}

function getLinkAsAjax(anchor) {
	var rval = anchor.href;
    var actionIdx = rval.indexOf(".action");
    if (actionIdx > -1) {
        // action page, assume that the AJAX version is ActionNameAjax.action...
        rval = rval.substring(0, actionIdx) + 'Ajax' + rval.substring(actionIdx);
    }
    else {
        // regular JSP page, append an AJAX param to the end...
        if (rval.indexOf("?") == -1) {
            rval += "?ajax=true";
        }
        else {
            rval += "&ajax=true";
        }
    }
    return rval;
}

/* ### LOCATION AUTO-COMPLETE ### */

var locations = null;
var locationsSeq = 0;
var locationsTimeout = 0;
var locationRequestInput = 0;
var type = 'all';
var separator = '; ';
var starting = 0;
// remove this if it doesn't work with IE
String.prototype.trim = function() {
  return this.replace(/^\s+/, '').replace(/\s+$/, '');
};

function doLocationAutocompletion() {
	var myInput = $(this);
	if (locationsTimeout != null) {
		window.clearTimeout(locationsTimeout);
		locationsTimeout = null;
	}
	// increment the sequence number, so we will no longer process outstanding responses
	locationsSeq++;
	var text = $.trim(myInput.val());
	var input;
	var pos = text.lastIndexOf(separator);
	if (pos == -1) {
		starting = 0;
		input = text;
		type = 'all';
	}else {
		starting = pos + separator.length;
		input = text.substring(starting);
	}
	var distSelect = $('#distance-select');
	if (distSelect.length) {
		// check the value of the input, if it's 4 digits, then it could be a postcode, and enable the input
		var isPostcode = /^[0-9]{3,4}$/.test(text);
		distSelect.prop('disabled', !isPostcode);
	}
	if (text.length >= 2) {
		locationRequestInput = input;
		locationsTimeout = window.setTimeout('doLocationAutoCompletionRequest();', 300);
	} else {
		type = 'all';
		locList.text('').not('.h').addClass('h')
	}
}

function doLocationAutoCompletionRequest() {
	if (locationsTimeout != null) {
		window.clearTimeout(locationsTimeout);
		locationsTimeout = null;
	}
	// moved toggle visibility in the callback method
	if (locInput.hasClass('suburb-postcode') || locInput.parent().hasClass('suburb-postcode')) {
		type = 'suburb_or_postcode';
	}
	else if (locInput.hasClass('suburb-or-nonAU') || locInput.parent().hasClass('suburb-or-nonAU')) {
		type = 'suburb_or_nonAU';
	}
	requestXML('/location/locationSelection.action?maxNum=25&seq=' + locationsSeq + '&type=' + type + '&input=' + locationRequestInput, showMatchCallback);
}

function clicked() {
	// detect if passed argument is an event or a li element
	var idString = $(arguments[0][0] || this).attr('id');
	var id = idString.substr(1);
	var location = locations[id];
	type = location[0];
	var display;
	// allow just one suburb:
	if (type == 'suburb') {
		display = location[2] + ' ' + location[3];
		locInput.val(display);
	} else {
		display = location[2];
		locInput.val(locInput.val().substring(0, starting) + display + separator);
	}
	var distSelect = $('#distance-select');
	if (distSelect.length) {
		if (type == 'suburb') {
			distSelect.prop('disabled', false);
			if (distSelect[0].selectedIndex <= 0) {
				distSelect[0].selectedIndex = 4; // 75 Kms if nothing already selected
			}
		} else {
			distSelect[0].selectedIndex = 0; // blank selection
			distSelect.prop('disabled', true);
		}
	}
	// following long-winded syntax required to avoid js error in IE
	$('#locale :hidden').val(type);
	hideLocationList();
}

function hideLocationList() {
	if (!locList.hasClass('h')) {
		locList.addClass('h');
		currLoc = null;
	}
}

var currLoc;
function showMatchCallback(success,content) {
	if (success) {
		var ajaxResponse = eval("(" + content + ")");
		var returnedLocations = ajaxResponse.locations;
		var returnedSeq = ajaxResponse.seq;
		if (returnedLocations.length > 0 && returnedSeq == locationsSeq) {
			// adopt these locations...
			locations = returnedLocations;
			// show locations list (if hidden)
			if (locList.hasClass('h')) {
				locList.removeClass('h');
				// set position of locations list
				var iTop = locInput.offset().top + locInput.outerHeight() + 'px';
				var iLeft = locInput.offset().left + 'px';
				locList.css({'top': iTop, 'left': iLeft});
			}
			locList.empty();
			$.each(locations, function(i, el){
				var locType = '';
				locList.append($('<li/>', {id: '_' + i, tabindex: i}).text(el[2]+' '+el[3]+locType).click(clicked).hover(function(){ $(this).addClass('current'); }, function(){ $(this).removeClass('current'); }));
			})
			locInput.keydown(function(e) {
				if (e.keyCode==40) {
					currLoc = locList.find(':first-child').addClass('current').focus();
				}
			});
		}
	}
}

// in ul.locations, handle keydown events on keys 'arrow down' (40), arrow up (38) and 'enter' (13)
function shiftFocus(e) {
	e = e || window.event;
	if (currLoc && ((e.keyCode==38) || (e.keyCode==40) || (e.keyCode==13))) {
		if ((e.keyCode==38) || (e.keyCode==40)) {
			// prevent window from scrolling while using arrow keys in locations list
			if (is_ie) {// && (locList.clientHeight>=425)
				e.returnValue;
			}
			if (!is_ie) {
				e.preventDefault();
			}
			currLoc.removeClass('current');
			if (e.keyCode==40) { // down arrow
				currLoc = currLoc.next().length ? currLoc.next() : currLoc;
			}
			else if (e.keyCode==38) { // up arrow
				currLoc = currLoc.prev().length ? currLoc.prev() : currLoc;
			}
			currLoc.addClass('current').focus();
		}
		else if (e.keyCode==13) { // enter
			clicked(currLoc);
		}
	}
}

function alertKeydownTarget(e) {
	// determine the target element of the mouse click or key press
	var target = getEventTarget(e);
	alert('tag is ' + target.tagName + '; id is ' + target.id);
}

var locList;
var listShield;
var locInput;
function setupLocations() {
	var locText,
		distSelect = $('#distance-select');
	if ($('#location-input').length) {
		locInput = $('#location-input');
		// create a list of suggested locations & append to body
		locText = locInput.val();
		locList = $('<ul/>', {'class': 'js locations pa w nl b1x pl5 z7 al h'}).keydown(shiftFocus);
		$(document.body).append(locList);
		locInput.attr('autocomplete', 'off').keyup(doLocationAutocompletion).keydown(function(e){
			// hide if tab pressed
			if(e.keyCode === 9){
				hideLocationList();
			}
		});
		$(document).click(function(e){
			if($(e.target).not(locList) && $(e.target).parent().not(locList)){
				hideLocationList();
			}
		})
	}
	if (distSelect.length) {
		// check the value of the input, if contains 4 digits, then it could be a postcode or a suburb with a postcode, and enable the input
		var isPostcode = /[0-9]{3,4}/.test(locText);
		distSelect.prop('disabled', !isPostcode);
	}
}
/* ### END locations auto-suggestion ### */

function layoutSidebar() {
	if (aside) {
		if (is_ie) {
			// insert hyphen in front of each 'suggest' strong element
			$('#member-dock').find('strong.suggest').each(function(i, el){
				$(el).before($('<span/>').text('- '));
			});
		}
		var qSearch = $('#quick-search');
		if (qSearch.length) {
			// handle special omniture site tracking requirements for the docks...
			var stInputField = $('<input/>', {type: 'hidden'});
			if (userName && (userName != '')) {
				stInputField.attr({'name': 'omst25', 'value': 'member search'});
			} else {
				stInputField.attr({'name': 'omst26', 'value': 'guest search'});
			}
			qSearch.append(stInputField);
			// identify first-of-type labels within each div except #with-photos; css will set fixed width
			qSearch.find('div').each(function(i, el){
				var id = $(el).attr('id')
					qLabels = $(el).find('label');
				if(id && (id != 'with-photos') && qLabels.length){
					qLabels.eq(0).addClass('first');
				}
			});
		}
		var joinNow = $('#join-now');
		joinNow.find('div.notice:first a[href*="login.action"]').click(function(){
			//replace #join-now with loginDock.jspf (via ajax)
			var tempDiv = $('<div/>');
			tempDiv.load('/docks/login.jsp' + tomcatSessionArgs + '?loginTarget=/member/home.action', function () {
				joinNow.replaceWith(tempDiv.contents());
			});
			return false;
		}).attr('href', function(i, h){
			return h + (h.indexOf('?') > -1 ? '&' : '?') + "omst24=join%20rsvp";
		});
		if ($('#login').length) {
			attachLoginFunctions();
		}
		if ($('#lost-pass').length) {
			attachLostPassFunctions();
		}
		if ($('#gender').length) {
			attachGenderFunctions();
		}
	}
}

function attachLoginFunctions() {
	var login = $('#login');
	// 'forgot password' link
	login.find('a[href*="recoverPassword.action"]').click(function(){
		//replace #login with lostPassDock.jspf (via ajax)
		var tempDiv = $('<div/>');
		tempDiv.load('/docks/lostPass.jsp' + tomcatSessionArgs, function () {
			login.replaceWith(tempDiv.contents());
		});
		return false;
	});
	// handle special omniture site tracking requirements for the docks...
	login.append($('<input/>', { type: 'hidden', name: 'omst24', value: 'login' }));
	// shift 'join RSVP button' out to top of .aside and round it
	login.find('a.join:first').removeClass('mt10').addClass('mb10').attr('href', function(i, h){
		return h + (h.indexOf('?') > -1 ? '&' : '?') + 'omst24=join%20rsvp';
	}).insertBefore($('div.aside :first'));
}

function attachGenderFunctions() {
	var gender = $('#gender');
	gender.find('a.edit:first').click(function(){
		//replace #gender with quickSearchDockGender.jsp (via ajax)
		var tempDiv = $('<div/>');
		tempDiv.load('/common/quickSearchDockGender.jsp' + tomcatSessionArgs + '?showGuestVersion=true', function () {
			gender.replaceWith(tempDiv.contents());
		});
		return false;
	});
}

function attachLostPassFunctions() {
	var lostPass = $('#lost-pass');
	ajaxSubmit(lostPass.children('form'));
	lostPass.find('a.return:first').click(function(){
		//replace #lost-pass with loginDock.jspf (via ajax)
		var tempDiv = $('<div/>');
		tempDiv.load('/docks/login.jsp' + tomcatSessionArgs, function () {
			lostPass.replaceWith(tempDiv.contents());
		});
		return false;
	});
}

// ### SEARCH REFINE ###
// enable search refine within search results pages
function attachRefineActions() {
	if ($('#search_search-success').length && $('#refine-search-link').length && propertyData)	{
		var form = $('#refine-search-form');
		// remove redundant link to advanced search page
		$('#refine-search-link').remove();
		// insert dynamic controls in #refine-search-form
		var rdControls = $('<div/>', {'id': 'refine-dynamic-controls', 'class': 'js ca mb10'});
		form.append(rdControls);
		// if user is a member, insert a 'save' button
		var rfTitle = $('#refine-fields').attr('title');
		if (rfTitle && (rfTitle == 'is_member')) {// NB - IE can't set certain attributes dynamically
			rdControls.append($("<button id='saveButton' type='submit' name='action' value='Save' class='nb save'>'Save this as my ideal partner</button>"));
		}
		// insert a search button
		if (!radius) {
			var rnSubmit = $('<span/>', {'id': 'instant-search-submit', 'class': 'action js m mr5 mb5 mt5 ml10 h'});
			var instantSearchBtn = $("<button id='instant-search-submit-btn' type='submit' name='action' value='Search'>Update</button>");
			rnSubmit.append(instantSearchBtn);
			rdControls.append(rnSubmit);
			roundCorners(rdControls);
		} else {
			var rnSubmit = $('<button/>', {'id': 'instant-search-submit', 'class': 'js m mr5 mb5 mt5 ml10 h', 'type': 'submit', 'value': 'Search'}).text('Update');
			rdControls.append(rnSubmit);
		}
		// wtf... moving the button around seems to kill it's click function for that form, do it manually...
		rnSubmit.click(function(){
			form.submit()
		});
		// insert a trigger for function showHiddenRefineOptions(), to un-hide hidden options
		form.find('div.criteria > div').click(doRefine).addClass('collapsed');
		form.find('h3').hide();
		form.find('div.multi span').addClass('more');
		form.find('div div:not(.multi) dl dt').addClass('more');
		squelchMultiRefines();
		// apply new content to suggest-refine...
		$('#suggest-refine').html('<strong>Do you need to refine?</strong>  To further refine your search results, you can apply a filter on the left side of the search results page or run a <a href="/search/fullSearch!input.action' + tomcatSessionArgs + '">new search</a>');
	}
}

function squelchMultiRefines() {
	$.each($('#refine-search-form div.multi'), function(i, el) {
		var allAreAny = true;
		$.each($(el).find('dd'), function() {
			allAreAny = allAreAny && $(this).html() == 'any';
		});
		if (allAreAny) {
			// squelch down the dls
			$(el).find('dl').addClass('h');
			if ($(el).find('div.squelched-any').length == 0)
				$(el).find('span.more').before($('<div class="squelched-any fr">any</div>'));
		} else {
			$(el).find('dl').removeClass('h');
			$(el).find('.squelched-any').remove();
		}
	});
}

function doRefine(e) {
	var target = $(e.target);
	if (!target.is('label') && !target.is('input') && !target.is('select') && !target.is('option')) {
		if ($(this).hasClass('down')) {
			if (target.is('dt') || target.hasClass('closer')) {
				// we are already clicked... and they've clicked the close span
				trashRefineDivs();
				unDownLastSearchItem($(this));
				squelchMultiRefines();
			}
		} else {
			// remove 'down' from all other def terms (dt) ...
			$('#refine-search-form').find('div.down').each(function(i, el){
				unDownLastSearchItem($(el));
			});
			trashRefineDivs();
			squelchMultiRefines();
			// add down to our dt...
			downSearchItem($(this));
		}
		e.stopPropagation();
		return false;
	}
}

function downSearchItem(dTerm) {
	// mark clicked def term (dt) as down...
	dTerm.addClass('down');
	dTerm.removeClass('collapsed');
	dTerm.find('dd').addClass('h');
	dTerm.find('dl').removeClass('h');
	dTerm.find('.squelched-any').remove();
	dTerm.find('span').eq(0).addClass('closer');
    trashRefineDivs();
	dTerm.children('dl').each(function(j, jel) {
		$(jel).append($('<div/>').addClass('js refine pt5 cf'));
		var refineDivArr = $(jel).find('div.refine');
		if (refineDivArr.length) {
			var refineFields = $('#refine-fields');
			var refineInputs = refineFields.find('input');
			var newHTML = '';
			var inputNamesToRemove = new Array();
			var inputsToRemove = new Array();
			var refineDiv = refineDivArr.get(0);
			var dlId = $(jel).attr('id');
			// determine the previous selected values...
			var oldVal = '';
			var oldVals = new Array();
			refineInputs.each(function(i, el){
				if($(el).attr('name') == dlId){
					oldVal = $(el).val();
					oldVals[oldVals.length] = el;
				}
			});
			inputNamesToRemove[inputNamesToRemove.length] = dlId;
			if (dlId == 'kw' ) {
				// keyword search...
				newHTML = '<input class="wide" style="width:130px;" name="kw" value="' + unescape(oldVal).replace(/\+/g, ' ') + '" \/>';
			} else if (dlId == 'lo') {
				// figure out the location properties...
				var distance;
				var hint;
				refineInputs.each(function(i, el){
					if ($(el).attr('name') == 'distance') {
						distance = Math.round($(el).val());
					} else if ($(el).attr('name') == 'lh') {
						hint = $(el).val();
					}
				});
				var oldValStr = '';
				for (var q=0; q < oldVals.length; q++) {
					if (oldValStr.length > 0) {
						oldValStr += '; ';
					}
					oldValStr += $(oldVals[q]).val();
				}
				if (hint == 'excludedcountry' && oldValStr && oldValStr.length > 0) {
					oldValStr = 'not ' + oldValStr;
					hint = 'country';
				}
				newHTML += '<div class="mt5">Located in<br/>';
				newHTML += '<input id="location-input" style="width:130px;" name="lo" value="' + oldValStr + '" \/></div>';
				newHTML += '<div class="mt5">Within<br/>';
				newHTML += '<select id="distance-select" name="distance"';
				if (hint != 'suburb') {
					newHTML += ' disabled="disabled"';
				}
				newHTML += ' style="width:136px;">';
				newHTML += '<option value="" ' + (distance == '' ? 'selected="selected"' : '') + '><\/option>';
				newHTML += '<option value="10" ' + (distance == '10' ? 'selected="selected"' : '') + '>10 km<\/option>';
				newHTML += '<option value="25" ' + (distance == '25' ? 'selected="selected"' : '') + '>25 km<\/option>';
				newHTML += '<option value="50" ' + (distance == '50' ? 'selected="selected"' : '') + '>50 km<\/option>';
				newHTML += '<option value="75" ' + (distance == '75' ? 'selected="selected"' : '') + '>75 km<\/option>';
				newHTML += '<option value="100" ' + (distance == '100' ? 'selected="selected"' : '') + '>100 km<\/option>';
				newHTML += '<option value="250" ' + (distance == '250' ? 'selected="selected"' : '') + '>250 km<\/option>';
				newHTML += '<option value="500" ' + (distance == '500' ? 'selected="selected"' : '') + '>500 km<\/option>';
				newHTML += '<\/select>';
				newHTML += '<input name="lh" value="' + hint + '" type="hidden" \/>';
				newHTML += '</div>';
				inputNamesToRemove[inputNamesToRemove.length] = 'distance';
				inputNamesToRemove[inputNamesToRemove.length] = 'lo';
				inputNamesToRemove[inputNamesToRemove.length] = 'lh';
			} else if (dlId == 'ar') {
				// age range...
				var lowVal = 18;
				var highVal = 120;
				refineInputs.each(function(i, el){
					if($(el).attr('name') == 'a1'){
						lowVal = $(el).val();
					}else if ($(el).attr('name') == 'a2') {
						highVal = $(el).val();
					}
				});
				newHTML = '<div class="mt5">Aged between<br/>';
				newHTML += '<input name="a1" value="' + lowVal + '" size="5" class="mt5" \/> to <input name="a2" value="' + highVal + '" size="5" \/>';
				newHTML += '</div>';
				inputNamesToRemove[inputNamesToRemove.length] = 'a1';
				inputNamesToRemove[inputNamesToRemove.length] = 'a2';
			} else if (dlId == 'pY') {
				// relationship sought... this one is *really* messy...
				// first, figure out the gender properties...
				var x1Gender;
				var x3Gender;
				var x4Gender;
				var x6Gender;
				refineInputs.each(function(i, el){
					if($(el).attr('name') == 'x1'){
						x1Gender = $(el).val();
					}else if ($(el).attr('name') == 'x3') {
						x3Gender = $(el).val();
					}else if ($(el).attr('name') == 'x4') {
						x4Gender = $(el).val();
					}else if ($(el).attr('name') == 'x6') {
						x6Gender = $(el).val();
					}
				});
				// if x6 is specified, translate this to a full hetero search for the purposes of subsequent searches
				if (x6Gender == 135) {
					x1Gender = 134;
					x3Gender = 208;
					x4Gender = 208;
				} else if (x6Gender == 134) {
					x1Gender = 135;
					x3Gender = 207;
					x4Gender = 207;
				}
				newHTML = 'I\'m a ';
				newHTML += '<select name="x1">';
				newHTML += '<option value="134" ' + (x1Gender == '134' ? 'selected="selected"' : '') + '>Male<\/option>';
				newHTML += '<option value="135" ' + (x1Gender == '135' ? 'selected="selected"' : '') + '>Female<\/option>';
				newHTML += '<\/select>';
				newHTML += ' looking for <fieldset>';
				var possibleVals = ['197', '198'];
				for (var p in possibleVals) {
					var pval = possibleVals[p];
					var selected = x6Gender == 134 || x6Gender == 135;
					if (!selected) {
						for (var i=0; i < oldVals.length; i++) {
							var thisOldVal = $(oldVals[i]).val();
							if (thisOldVal == pval) {
								selected = true;
								break;
							}
						}
					}
					var idStr = dlId + '_' + pval;
					newHTML += '<div><input id="' + idStr + '" type="checkbox" name="' + dlId + '" value="' + pval + '" ' +
						(selected ? 'checked="checked"' : '') + '\/>' +
						'<label id="' + idStr + '_label" for="' + idStr + '">' +  propertyData[dlId][pval] + '<\/label><\/div>';
				}
				newHTML += 'with a ';
				newHTML += '<select name="x3">';
				newHTML += '<option value="207"' + (x3Gender == '207' ? 'selected="selected"' : '') + '>Male<\/option>';
				newHTML += '<option value="208"' + (x3Gender != '207' && x3Gender != '206' ? 'selected="selected"' : '') + '>Female<\/option>';
				newHTML += '<option value="206"' + (x3Gender == '206' ? 'selected="selected"' : '') + '>Male or Female<\/option>';
				newHTML += '<\/select></fieldset><fieldset>';
				possibleVals = ['195', '196'];
				for (var p in possibleVals) {
					var pval = possibleVals[p];
					var selected = x6Gender == 134 || x6Gender == 135;
					if (!selected) {
						for (var i=0; i < oldVals.length; i++) {
							var thisOldVal = $(oldVals[i]).val();
							if (thisOldVal == pval) {
								selected = true;
								break;
							}
						}
					}
					var idStr = dlId + '_' + pval;
					newHTML += '<div><input id="' + idStr + '" type="checkbox" name="' + dlId + '" value="' + pval + '" ' +
						(selected ? 'checked="checked"' : '') + '\/>' +
						'<label id="' + idStr + '_label" for="' + idStr + '">' +  propertyData[dlId][pval] + '<\/label><\/div>';
				}
				newHTML += 'with a ';
				newHTML += '<select name="x4">';
				newHTML += '<option value="207"' + (x4Gender == '207' ? 'selected="selected"' : '') + '>Male<\/option>';
				newHTML += '<option value="208"' + (x4Gender != '207' && x4Gender != '206' ? 'selected="selected"' : '') + '>Female<\/option>';
				newHTML += '<option value="206"' + (x4Gender == '206' ? 'selected="selected"' : '') + '>Male or Female<\/option>';
				newHTML += '<\/select></fieldset>';
				inputNamesToRemove[inputNamesToRemove.length] = 'x1';
				inputNamesToRemove[inputNamesToRemove.length] = 'x3';
				inputNamesToRemove[inputNamesToRemove.length] = 'x4';
				inputNamesToRemove[inputNamesToRemove.length] = 'x6';
			} else if (dlId == 'pT') {
				// height...  this one is messy...
				var lowestVal = '';
				var highestVal = '';

				refineInputs.each(function(i, el){
					if($(el).attr('name') == 'h1' || $(el).attr('name') == 'h2' || $(el).attr('name') == 'pT'){
						if ((lowestVal == '' || $(el).val() < lowestVal) && $(el).attr('name') != 'h2') {
							lowestVal = $(el)
						}
						if ((highestVal == '' || $(el).val() > highestVal) && $(el).attr('name') != 'h1') {
						 highestVal = $(el)
						}
					}
				});
				if (lowestVal == '144')
					lowestVal = '';
				if (highestVal == '161')
					highestVal = '';
				// these select boxes are required for the best UI...
				// however much of the JS is design to work around INPUT fields and not SELECT fields,
				// and it's simplest and most consistent to continue to allow this by creating hidden
				// input fields that always reflect the select boxes
				var possibleVals = propertyData[dlId];
				newHTML += '<select name="h1" style="width:90px;">';
				for (var pval in possibleVals) {
					newHTML +=
						'<option value="' + pval + '" ' +
						(pval == lowestVal ? 'selected="selected"' : '') +
						'>' + propertyData[dlId][pval] + '<\/option>';
				}
				newHTML +='<\/select> to <select name="h2" style="width:90px;">';
				for (var pval in possibleVals) {
					newHTML +=
						'<option value="' + pval + '" ' +
						(pval == highestVal ? 'selected="selected"' : '') +
						'>' + propertyData[dlId][pval] + '<\/option>';
				}
				newHTML += '<\/select>';
				inputNamesToRemove[inputNamesToRemove.length] = 'h1';
				inputNamesToRemove[inputNamesToRemove.length] = 'h2';
			} else {
				// this must be a "property" style item... we expect to have data for it in the property data array...
				var possibleVals = propertyData[dlId];
				// if there is a null option, this tends to imply radio button operation...
				var exclusive = propertyData[dlId][''];
				for (var pval in possibleVals) {
					var selected = false;
					for (var i=0; i < oldVals.length; i++) {
						var thisOldVal = $(oldVals[i]).val();
						if (thisOldVal == pval) {
							selected = true;
							break;
						}
					}
					if (exclusive && (oldVals.length == 0 || oldVal == '')) {
						selected = (pval == '');
					}
					var idStr = dlId + '_' + pval;
					newHTML +=
						'<div><input id="' + idStr + '" type="' + (exclusive ? 'radio' : 'checkbox') + '" name="' + dlId + '" value="' + pval + '" ' +
						(selected ? 'checked="checked"' : '') + '\/>' +
						'<label id="' + idStr + '_label" for="' + idStr + '">' + possibleVals[pval] + '<\/label><\/div>';
				}
			}
			//alert(newHTML);
			$(refineDiv).html(newHTML);
			setupLocations();
			for (var i=0; i < inputNamesToRemove.length; i++) {
				refineInputs.each(function(k, kel){
					if ($(kel).attr('name') == inputNamesToRemove[i]) {
						inputsToRemove[inputsToRemove.length] = $(kel);
					}
				});
			}
			// must use this chunky removeNode call, as these elements were embedded into the doc directly...
			if (inputsToRemove.length > 0) {
				removeNode(inputsToRemove);
			}
		}
	});
	$('#instant-search-submit').removeClass('h');
	var lastDL = $(dTerm).find('dl:last');
	var lastDLid = lastDL.attr('id');
	lastDL.append($('#instant-search-submit'));
	if (lastDLid == 'lo' || lastDLid == 'kw' || lastDLid == 'ar') {
		$('#instant-search-submit').addClass('button-right');
	} else {
		$('#instant-search-submit').removeClass('button-right');
	}
}
function trashRefineDivs() {
	$('#instant-search-submit').addClass('h');
	var dForm = $('#refine-search-form');
	var dLists =  dForm.find('dl');
	var refineFields = $('#refine-fields');
	if (refineFields.length) {
		dForm.find('div.refine').each(function(i, el){
			var displayStr = '';
			// relationship preferences - composite of pY, x1, x3
			var isSeekingRel = false;
			var isSeekingFriend = false;
			var isSeekingMaleRel = false;
			var isSeekingFemaleRel = false;
			var isSeekingMaleFriend = false;
			var isSeekingFemaleFriend = false;
			var isMale = true;
			// merge the refine div options into the hidden parent search fields...
			var refineValuesToTransfer = new Object();
			var refineInputs = $(el).find('input, select');
			if(refineInputs.length){
				refineInputs.each(function(j, jel){
					var refineDivInput = $(jel),
						refName = refineDivInput.attr('name');
					if (refineDivInput.prop('disabled'))
						return true; // === continue;
					if (refName == 'kw') {
						// keyword search
						displayStr = ' ' + refineDivInput.val();
						if (refineDivInput.val() != '')
							setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'a1') {
						// age (from)
						displayStr += refineDivInput.val();
						if (refineDivInput.val() != '')
							setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'a2') {
						// age (to)
						displayStr += ' to ' + refineDivInput.val();
						if (refineDivInput.val() != '')
							setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'h1') {
						// height (from)
						displayStr += propertyData['pT'][refineDivInput.val()];
						if (refineDivInput.val() != '')
							setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'h2') {
						// height (to)
						displayStr += ' to ' + propertyData['pT'][refineDivInput.val()];
						if (refineDivInput.val() != '')
							setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'pY') {
						// Relationship sought
						if (refineDivInput.prop('checked')) {
							if (refineDivInput.val() == 195|| refineDivInput.val() == 196)
								isSeekingFriend = true;
							if (refineDivInput.val() == 197 || refineDivInput.val() == 198)
								isSeekingRel = true;
							setValue(refineValuesToTransfer, refineDivInput[0]);
						}
					} else if (refName == 'x1') {
						// My gender
						isMale = refineDivInput.val() != 135;
						setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'x3') {
						// romantic gender...
						if (refineDivInput.val() == 207 || refineDivInput.val() == 206)
							isSeekingMaleRel = true;
						if (refineDivInput.val() == 208 || refineDivInput.val() == 206)
							isSeekingFemaleRel = true;
						setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'x4') {
						// platonic gender...
						if (refineDivInput.val() == 207 || refineDivInput.val() == 206)
							isSeekingMaleFriend = true;
						if (refineDivInput.val() == 208 || refineDivInput.val() == 206)
							isSeekingFemaleFriend = true;
						setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'distance') {
						// distance
						displayStr = "Within " + refineDivInput.val() + "km of " + displayStr;
						setValue(refineValuesToTransfer, refineDivInput[0]);
					} else if (refName == 'lo') {
						// location
						displayStr += refineDivInput.val();
						var endOfContent = displayStr.search(/;\s*$/);
						if (endOfContent > -1)
							displayStr = displayStr.substr(0, endOfContent);
						setValue(refineValuesToTransfer, refineDivInput[0]);
						// hide the location popup if necessary...
						hideLocationList();
					} else if (refName == 'lh') {
						// location hint
						setValue(refineValuesToTransfer, refineDivInput[0]);
					} else {
						// standard property value
						if (refineDivInput.prop('checked')) {
							displayStr += '; ' + propertyData[refName][refineDivInput.val()];
							if (refineDivInput.val() != '')
								setValue(refineValuesToTransfer, refineDivInput[0]);
						}
					}
				});
				// now, after we've done the strings, transfer the required inputs to the hidden area
				for (var nameToTransfer in refineValuesToTransfer) {
					var valuesForThisName  = refineValuesToTransfer[nameToTransfer];
					for (var valueToTransfer in valuesForThisName) {
						refineFields.append($('<input/>', {'type': 'hidden', 'name': nameToTransfer, 'value': valueToTransfer}));
					}
				}
			}
			var dList = $(el).parent();
			// update the data (display str) node...
			if (displayStr.indexOf('; ') == 0) {
				displayStr = displayStr.substr(2, displayStr.length);
			}
			// line breaks now instead of commas... grrr...
			displayStr = displayStr.replace(/; /g, '<br/>');
			if (dList.attr('id') == 'pY') {
				// relationship composite....
				var isSeekingMale = isSeekingMaleFriend && isSeekingFriend || isSeekingMaleRel && isSeekingRel;
				var isSeekingFemale = isSeekingFemaleFriend && isSeekingFriend || isSeekingFemaleRel && isSeekingRel;
				if (isSeekingMale && isSeekingFemale) {
					displayStr += "with a male or female";
				} else if (isSeekingMale) {
					displayStr += "with a male";
				} else if (isSeekingFemale) {
					displayStr += "with a female";
				}
			} else if (dList.attr('id') == 'pT') {
				// clean up funny heights...
				if (displayStr.indexOf(' to ') == 0) {
					displayStr = displayStr.substr(4);
					if (displayStr.length > 0)
						displayStr = 'at most ' + displayStr;
				}
				else if (displayStr.match(/.* to $/)) {
					displayStr = 'at least ' + displayStr.substr(0, displayStr.length -4);
				}
			}
			if (displayStr == '') {
				displayStr = 'any';
			}
			dList.find('dd:first').html(displayStr);
			// delete the refine div...
			$(el).remove();
		});
	}
}

function setValue(valueMap, formItem) {
	var itemName = formItem.name;
	if (formItem.type == 'select') {
		// single select...
		setValueStr(valueMap, itemName, formItem.selected);
	} else if (formItem.type == 'select-multiple') {
		// multiple select...
		var formOptions = formItem.options;
		for (var i=0; i < formOptions.length; i++) {
			if (formOptions[i].selected) {
				setValueStr(valueMap, itemName, formOptions[i].value);
			}
		}
	} else {
		// generic other form element, text, hidden, radio, checkbox...  can just choose the value
		setValueStr(valueMap, itemName, formItem.value);
	}
}

function setValueStr(valueMap, category, value) {
    var mappedValues = valueMap[category];
    if (!mappedValues) {
        mappedValues = new Object();
        valueMap[category] = mappedValues;
    }
    mappedValues[value] = true;
}

function unDownLastSearchItem(dTerm) {
	dTerm.removeClass('down');
	dTerm.addClass('collapsed');
	dTerm.find('dd').removeClass('h');
	dTerm.find('span:first').removeClass('closer');
}
// ### END refine search ###

// attach a class name to body which will enable targeting of styles specifically at IE
function enableAltStyles() {
	// mark page as javascript-enabled
	$(document.body).addClass('js');
	if (is_ie) {
		$(document.body).addClass('ie v' + ieNum);
	}
	if (ffNum!=-1) {
		$(document.body).addClass('ff v' + ffNum*10);
		if ((ffNum >= 3) && (ffNum < 3.6)) {
			$(document.body).addClass('no-gr');
		}
	}
}

function amendTestId() {
	var testString = (document.body.id.indexOf('_test')!=-1) ? '_test' : '.test';
	document.body.id = document.body.id.replace(testString,'');
}

// determine the target element for the event (i.e. the element you clicked on, moused over, changed etc)
function getEventTarget(e) {
	e = e || window.event;
	return e.target || e.srcElement;
}

function iso8601(date) {
  var a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z?)$/.exec(date);
  if (a) {
    return new Date(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]);
  }
}


// declaring this variable here prevents a javascript error when 'runSectionFunctions' doesn't exist
// note - in a section's js file, runSectionFunctions must be called like so: runSectionFunctions = function() {...
var runSectionFunctions;

/* ### run functions when DOM is READY (i.e. - before page has finished loading) ### */
// technique devised in 2006 by Dean Edwards, Matthias Millar & John Resig
// except for "poll footer" approach for IE - Adrian Neilson Hall
// speeds up i.a. layout changes and menu effects;
function runGlobalFunctions() {
	// for Opera - quit if this function has already been called & flag as run
	if (arguments.callee.done) return;
	arguments.callee.done = true;
	// kill the timer
	if (_timer) {
		clearInterval(_timer);
		_timer = null;
	}
	initRSVP();
	layoutSidebar();
	setupSuccessStories();
	writeKeywordSuggestions();
	attachRefineActions();
	doAdLayout();
	if (runSectionFunctions) {
		runSectionFunctions();
	}
	addToggleMediaVisibility();
	attachTarget();
	insertCloser();
	attachFormFunctions();
	handleSubscriberOnlyLinks();
	handleGoBackLinks();
	setupLocations();
    setAdTimer();
    insertTips($("#help-tips"));
}

/* for Mozilla & Opera 9 */
if (document.addEventListener) {
	document.addEventListener("DOMContentLoaded", runGlobalFunctions, false);
}

/* for IE8- */
// note - the script defer/onreadystatechange technique proposed by Edwards, Miller & Resig chokes at times with complex pages (e.g. guest/member home)
// keep checking for presence of #tail (at end of .footer), then run global functions when found
if (is_ie8down) {
	var _timer = setInterval(function() {
		if ($('#tail').length) {
			//alert('DOM is ready');
			runGlobalFunctions();
		}
	}, 10);
}

/* for Safari & Chrome */
if (is_webkit) {
	var _timer = setInterval(function() {
		if (/loaded|complete/.test(document.readyState)) {
			runGlobalFunctions();
		}
	}, 10);
}

/* placement for CMS SiteTips content */
function insertTips (tipContainer) {
    // get all the tips + filter out any without any placement
    var contentId = tipContainer.attr("title");
    if (contentId && contentId.indexOf("id") == 0) {
        contentId = contentId.substr("id".length);

        var isAlreadyExcluded = tipContainer.hasClass("excluded");

        var tips = tipContainer.children("div").filter(function () { return $($(this).attr("title")).size(); });
        var toggle = tipContainer.children("span").filter(function () {
            var selector = $(this).attr("title");
            if (selector.indexOf("before ") == 0) selector = selector.substr("before ".length);
            if (selector.indexOf("after ") == 0) selector = selector.substr("after ".length);
            if (selector.indexOf("prepend ") == 0) selector = selector.substr("prepend ".length);
            if (selector.indexOf("append ") == 0) selector = selector.substr("append ".length);
            return $(selector).size(); });

        if (tips.length > 0) {
            tips.each(function (index) {
                var self = $(this);
                var targetAttr = self.attr("title");
                self.attr("title", "");
                var placement = $(targetAttr); // the positioning parent is a jquery selector based on this elements title attribute
	            if (!placement.size()) { // eh? ok.
                    self.remove();
                    return;
                }
                self.addClass("box point bot w b3v pa z7 al").css({'max-width': 250, 'min-width': 200});
				if (is_ie && ieNum==7) {
					self.append('<div class="indicator"/>');
				}
                self.hide();

                // add close and prev/next links
                self.find(":first").after($("<a/>", {
                    text: "close [x]", 'class': 'w30 fr ar crp du s',
                    click: function () {
                        // get rid of it, no matter what...
                        self.hide();

                        if (!isAlreadyExcluded) {
                            // don't do it again...
                            isAlreadyExcluded = true;
                            
                            // perform the AJAX CMS exclusion for this contentId
                            jsonCall('/cms/contentExclusion.action' + tomcatSessionArgs + '?contentId=' + contentId, "get", null, function (data) {
								if (data.resultCode == 'error') {
									alert(data.actionErrors.join("\n"));
								}
							});
                        }
                        
                        return false;
                    }
                }));
                if (tips.length > 1) {
					self.append($("<div/>", { 'class' : 'cf mt15 st ca' })
						.append($("<div/>", { text: 'Tip ' + (index+1) + ' of ' + tips.size(), 'class': 'w50' }))
						.append($("<div/>", { 'class': 'links w50 ar' })
							.append($("<a/>", {
								text: 'Prev', href: '#', 'class': 'mr5',
								click: function() {
									tips.eq((index + tips.size() - 1) % tips.size()).data("show")();
									self.hide();
									return false;
								}
							}))
							.append($("<a/>", {
								text: 'Next', href: '#',
								click: function() {
									tips.eq((index + 1) % tips.size()).data("show")();
									self.hide();
									return false;
								}
							}))
						)
	                );
                }

                // special display function - reposition every time in case something moved
                self.data("show", function () {
					// remove class 'r-side' if present
					self.removeClass('r-side');
                    var offset = placement.offset();
					// set left pos of tip box to target eleemtn less 30px
	                // but note special case on small screens
	                var left = smartphone ? 10 : offset.left - 30;
					// ensure tip is not off-screen; but arrow won't line up for left=5 :(
					if (!smartphone) {
						if (left < 5) {
							left = 5;
						}
						// we also need to ensure that the tip doesn't extend beyond the right edge of the header;
						// get limit - offset().left + width of header
						// if the offset().left + width of tip is greater then limit
						// subtract tip width from limit and assign that value as left
						// note - tip box width is 250 + padding + border = 276
						// also shift pointer arrow to right hand side 
						var limit = $('.header').offset().left + $('.header').width();
						var tipEdgeR = left + 276;
						if (tipEdgeR > limit) {
							left = limit - 276;
							self.addClass('r-side');
						}
					}
					//if smartphone and target element is on right, switch arrow pos
					else {
						if (offset.left > 200) {
							self.addClass('r-side');
						}
					}
	                var top = offset.top - self.outerHeight() - 15;
					// if too close to the top of the page, switch to underneath
	                if (top < 20) {
		                top = offset.top + placement.height() + 15;
		                // and switch the pointing arrow from the bottom to the top
		                self.removeClass('bot').addClass('top');
	                }
					if (smartphone && (top < 60)) {
						top = 60;
					}
                    self.css({
                        // center on positioning parent
                        left: left,
                        // pixel gap above positioning parent
                        top: top
                    });
                    self.fadeIn("fast");
					// scroll to current tip
					// for tips close to top of page, just scroll to top					
					var tTop = self.offset().top < 60 ? 0 : self.offset().top;
					$('html,body').animate({scrollTop: tTop}, 'slow');
                });
                // move tip to a more appropriate positioning parent
                self.appendTo($('body'));
            });

            var toggleFn = function () {
                if (tips.is(":visible"))
                    tips.hide();
                else
                    tips.eq(0).data("show")();
                return false;
            };

            toggle.each(function(index) {
                var self = $(this);
                var selector = self.attr("title");
                var targetAttr = selector;
                if (selector.indexOf("before ") == 0) targetAttr = selector.substr("before ".length);
                if (selector.indexOf("after ") == 0) targetAttr = selector.substr("after ".length);
                if (selector.indexOf("prepend ") == 0) targetAttr = selector.substr("prepend ".length);
                if (selector.indexOf("append ") == 0) targetAttr = selector.substr("append ".length);

                self.attr("title", "");
                var placement = $(targetAttr); // the positioning parent is a jquery selector based on this elements title attribute
                if (!placement.size()) { // eh? ok.
                    self.remove();
                    return;
                }


                if (selector.indexOf("before ") == 0) placement.before(self);
                else if (selector.indexOf("after ") == 0) placement.after(self);
                else if (selector.indexOf("prepend ") == 0) placement.prepend(self);
                else /*if (selector.indexOf("append ") == 0) */placement.append(self);

                self.click(toggleFn);
            });

            // if the tips are not closed, then show them
            if (!isAlreadyExcluded)
                toggleFn();
        }
    }

    // no longer needed
    tipContainer.remove();
}

// blurred / focused detection, for the purposes of the chat client...
function onBlur() {
    try { $('body').removeClass('focused').addClass('blurred'); } catch (e) { /* eh */  }; //document.body.className = 'blurred'
}
function onFocus(){
    try { $('body').removeClass('blurred').addClass('focused'); } catch (e) { /* eh */  }; //document.body.className = 'focused'
}

if (/*@cc_on!@*/false) { // check for Internet Explorer
    document.onfocusin = onFocus;
    document.onfocusout = onBlur;
} else {
    window.onfocus = onFocus;
    window.onblur = onBlur;
}

function isWindowFocused() {
    try { return !$('body').hasClass('blurred') } catch (e) { /* eh */  }; //document.body.className.indexOf('blurred') == -1
}

/* for other modern browsers (or if #tail is not found by IE) */
window.onload = runGlobalFunctions;

$(function () {

    var urlToForm = {
        '/profile/editAboutMe!input.action': 'edit-about-me-form',
        '/profile/editMyDetails!input.action': 'edit-details-form',
        '/profile/editIdealPartner!input.action': 'ideal-summary-form',
        '/profile/editLookingFor!input.action': 'edit-looking-form',
        '/profile/editInterests!input.action': 'edit-interests-form'
    };

    var links = $("a[href^='/profile/editAboutMe!input.action'],"+
                  "a[href^='/profile/editMyDetails!input.action'],"+
                  "a[href^='/profile/editIdealPartner!input.action'],"+
                  "a[href^='/profile/editLookingFor!input.action'],"+
                  "a[href^='/profile/editInterests!input.action']").filter(
            function () { return $(this).parents().find("#profile_display #rsvpcontent,#profile_display .aside").length == 0; });

    var profileUrl = "/profile/display.action" + tomcatSessionArgs;
    links.attr("href", function () {
        var href = $(this).attr("href");

        // focus form elements
        var hash = href.indexOf("#");
        if (hash >= 0)
            return profileUrl + "#" + href.substring(hash + 1);

        // focus form sections
        var endUrl = href.search(/[;#\?$]/);
        if (endUrl == -1) endUrl = href.length;
        var replacement = urlToForm[href.substring(0, endUrl)];
        if (replacement)
            return profileUrl + "#" + replacement;

        // ... whatever else...
        return profileUrl;
    });
});

function loginIframeLoad(o) {
	setTimeout(function () {
		var f = o.frame || o.window.frameElement;
		var win = o.window || o.frame.contentWindow;
		var doc = win.document;
		var de = doc.documentElement;
		var timer = null;
		var cb = function (e) {
			if (!timer) {
				timer = setTimeout(function () {
					var height = doc.body.scrollHeight || de.offsetHeight;
					f.style.height = height + "px";
					timer = null;
				}, 100);
			}
		};
		cb();
		win.resizeIframe = cb;
	}, 50);
}
function loginIframe (loginTarget, registration) {
	var src = registration ?
		'/registration/signUp!input.action?iframe=true' :
		'/login!input.action?iframe=true' + (loginTarget ? '&amp;loginTarget=' + encodeURIComponent(loginTarget) : '');
	var iframe = $('<iframe id="login-registration" onload="loginIframeLoad({frame:this})" src="' + src + '" width="100%" style="height:440px" padding="0" frameborder="0" scrolling="no" />'); // NB - height was 435 pre-Facebook
	doLightbox({ content: iframe, width: 660 });
	$('.lightbox').addClass('v9').prepend($('<div class="p c-tl c-tr" style="height:60px"><img src="/images/core/rsvp_logo_158x69.png" alt="RSVP" /><h4 class="l oc fr mt20">Australia\'s <b class="wc">No.1</b> Dating site*</h4></div>'));
	$('.lightbox > span.close').addClass('wc'); //NB - add 'dn' here to remove underline
	if (is_ie7down) {
		$('.lightbox > span.close').append(' [x]');
	}
}
$(function () {
	if (window.parent && window.parent !== window && window.frameElement && window.parent.loginIframeLoad)
		window.parent.loginIframeLoad({window:window});
});

// login lightbox
$(function () {
    if (!smartphone && !isMember && location.protocol == 'http:') {
	    var allowed = [
            '/profile/display.action',
            '/profile/astroCompat.action',
            '/recoverPassword.action',
            '/removeAutoLogin.action',
            '/resendAccountLock.action',
            '/mobile/myMobile.action',
            '/logout.action',
            '/search/',
            '/help/',
            '/photo/viewPhotoGuidelines.action',
            '/registration/'
        ];
        var host = location.protocol + "//" + location.host;
        $('a[href*=".action"],form.login[method=""],form.login[method="get"]').each(function () {
	        var force = false;
            var link = $(this);
            var form = link.is("form");
            var href = (form ? link.attr("action") : link.attr("href")).replace(/;jsessionid=[^\?]+(\?|#|$)/, '$1');
	        var matches = /\/login(![a-zA-Z0-9\$_]+)?\.action\?loginTarget=([^&#]+)/.exec(href);
	        if (matches) {
		        href = decodeURIComponent(matches[2]);
		        force = true;
	        }
	        if (/\/login(![a-zA-Z0-9\$_]+)?\.action/.test(href)) {
		        href = '';
		        force = true;
	        }
            if (form) {
                var ser = link.serialize();
                if (ser.length)
                    href += (href.indexOf("?") >= 0 ? '&' :'?') + ser;
            }
            if (href.substring(0, host.length) == host)
                href = href.substring(host.length);

            // check for "no light box" class...
	        var found = false;
            if (link.hasClass('nlb')) {
                found = true;
	            force = false;
            }
            if (!force && !found) {
                for (var v in allowed) {
                    if (href.substring(0, allowed[v].length) == allowed[v]) {
                        if (allowed[v] != '/profile/display.action' || href.indexOf('&mail=true') == -1) {
                            found = true;
                            break;
                        }
                    }
                }
            }
            if (force || !found) {
                link[form ? 'submit' : 'click'](function () {
                    if (!link.attr("target") && !$("#lightbox").is(":visible")) {
                        loginIframe(href, $.cookie("rv") != "true");
                        return false;
                    }
                });
            }
        });
    }
});

// layout and behaviour changes for new drop menu in white strip (26/11/2010)
$(function () {
	if (isMember) {
		// toggle drop list (rNav) on clicking user name
		// also toggle border around the trigger (user name)
		var rNav = $('.header nav[data-type=profile]').eq(0);
		rNav.find('[data-type=username]').click(function() {
			rNav.find('ul').toggle();
			$(this).toggleClass('down');
		});
		// close drop list when clicking outside the nav
		// also remove border around the trigger (user name)
		$(document.body).click(function (e) {
			var target = $(e.target);
			var is_trigger = target.is('[data-type=username]') || target.is('[data-type=profile]');
			if (!is_trigger) {
				rNav.find('ul').hide();
				rNav.find('[data-type=username]').removeClass('down');
			}
			// ensure the list drops on clicking the arrow in IE7
			if (is_ie && ieNum === 7 && target.is(rNav.find('.indicator'))) {
				rNav.find('ul').toggle();
				rNav.find('[data-type=username]').toggleClass('down');
			}
		});
		
		// for smartphones, shift drop menu into #rsvpheader & tweak its styling
		if (smartphone) {
			$('#site-name').after(rNav.addClass('fr mt10'));
			rNav.find('h4').addClass('wc').find('[data-type=username]').removeClass('xc');
			rNav.find('ul').css({'left': 'auto', 'right': 0, 'top': 10, 'z-index': 10});
		}
	}
});

$(function () {
	if (!isMember && !smartphone && !$.cookie("ap")) { // 'ap' indicates user has logged in previously
		var visitNumber = +$.cookie("vn");
		if ($.cookie("os") !== "1") { // 'os' for overlay shown
			if (visitNumber % 3 === 2) {
				$.cookie("rp", "1", { path: "/" });
				loginIframe(null, true);
			}
			$.cookie("vn", visitNumber+1, { expires: 30, path: "/" });
			$.cookie("os", "1", { path: "/" }); // session cookie
		}
	}

	// take a tour
	$('a.take-a-tour').doLightbox({
		content: $('#take-a-tour-content'),
		width: 760,
		height: 300,
		onload: function(c){
			c.show();
		},
		onunload: function(c){
			$(document.body).append(c.hide());
		}
	});
});

/* when page has loaded, call functions that fire on resize or pageshow
- all other 'onload' functions have been shifted to 'runGlobalFunctions' (see above)
- note: adding an event listener for a function to a window on load and setting it to true, causes that function to fire multiple times in Opera */

$(window).bind('pageshow', resetSubmitted); // to handle firefox "fastcache" to allow submit buttons to be used after back

/* ### NB - place all jQuery functions at top - see marked section ### */

/* ### comms MVTs - RSVP-651 ### */
if (isMember && !smartphone) {
	(function () {
		function makeContacted() {
			var li = $(this);
			li.find(".contact").remove();
			var container = li.find(".email.action,nav[data-role=select]").eq(0).parent();
			setTimeout(function () { // just to make it happen after everything else
				if (li.is("[data-channel]") && !container.find(".js-free-contact").length) {
					container.append("<div class='vs db ac gc mt2 js-free-contact'><strong>Free</strong> until " + li.data("channel") + "</div>");
					li.find(".kiss.action, nav[data-role=select]").remove();
				}
				if (!container.find(".contact-history").length) {
					container.append("<a class='vs db ac mt5 contact-history' href='/contact/pairedContactHistory.action" + tomcatSessionArgs + "?targetUserId=" + li.data("userId") + "'>Contact History</a>");
				}
			}, 1);
		}
		function prepareCommsMvtState() {
			var search = document.body.id === 'search_search-success';
			var contactHistory = document.body.id === 'contact_pairedContactHistory';
			var profile = document.body.id === 'profile_display';
			if (!search && !contactHistory && !profile)
				return;

			var complete = function () {
				if (contactHistory) {
					var history = $("#contact-history");
					var send = history.next();
					if (history.length && send.find("form").length) {
						var sendcontainer = $("<div class='section ca'/>").appendTo(history);
						sendcontainer.append($("#comms-mvt-own-photo").children());
						send.appendTo(sendcontainer);
						send.prop("class", "speech left box w b2 w80 fr");
						send.find(".ti-, .ml-30").removeClass("ti- ml-30");

						$("#emailSendSupport_emailMessage").data({width: 480 });
						$("#email-form").css({ marginRight: 0 });
					}
				}

				$(".aside #send-kiss-form a.more").click(function () {
					makeCommsMvtKissHandler("#comms-mvt-1", search, false, true).call(this); 
					return false;
				});
			};

			var kissButton;

			if (contactHistory) {
				// don't do kiss replies here
				if (!$("#kiss-form :hidden[name='kissSendSupport.kissId']").length) {
					kissButton = $('<a href="#" class="action mb20" id="send-kiss-submit">Kiss Me</a>');
					$("#kiss-form").after(kissButton);
					$("#kiss-form").after($("#kiss-form > h3"));
					$("#kiss-form").hide();
				}
			} else if (profile) {
				kissButton = $("a.kiss.action");
				if (!kissButton.find("em.free").length) {
					kissButton = undefined;
				}
			} else if (search) {
				if (!$(".gallery-view").length) {
					$(".email.action").each(function () {
						$(this).addClass("mt10").appendTo($(this).parent());
					});
					$(".profiles > li:has(.contact)").each(makeContacted);
				}
				$(".profiles > li[data-channel=true] .kiss.action").remove();
				kissButton = $(".kiss.action").addClass("cf");
			}

			if (kissButton) {
				// shortcut return -- if we did kisses, don't try to do replies as well.
				return {
					type: 'kiss',
					elements: $(kissButton),
					search: search,
					complete: complete
				};
			}

			// kiss replies
			var replyButtons;
			if (contactHistory) {
				if ($("#kiss-form").length) {
					replyButtons = $(
						"<a class='kiss action' href='#interested'>Interested</a> " +
						"<a class='kiss action' href='#tell-me-more'>Tell me more</a> " +
						"<a class='kiss action' href='#not-interested'>Sorry not interested</a>");
					$("#kiss-form").children(":not(:header)").remove();
					$("#kiss-form").append($("<div class='ml-30'/>").append(replyButtons)).addClass("mb20");
				}
			} else if (profile) {
				replyButtons = $(".kiss.action");
			}

			if (replyButtons) {
				return {
					type: 'reply',
					elements: replyButtons,
					search: search,
					complete: complete
				};
			}
		}

		function commsMvt2And3(buttonId, hideEmail) {
			var state = prepareCommsMvtState();
			if (state) {
				switch (state.type) {
					case 'kiss':
						if (state.search && $("ol.gallery-view").length) {
							// same as mvt-1
							state.elements.click(makeCommsMvtKissHandler("#comms-mvt-1", state.search, true));
						} else {
							state.elements.each(function () {
								var element = $(this);
								var button = $(buttonId).clone();
								var template = $("#comms-mvt-1").clone();
								
								button.find("a[data-kiss-id]:first").click(makeCommsMvtKissHandler(template, state.search, true));

								var emailButton = button.find("a.js-email");
								if (emailButton.length) {
									emailButton.click(function () {
										var email = $("#email-form");
										$("nav[data-role=select] > ul").hide();
										if (!state.search && email.length && (email.hasClass("h0") || email.hasClass("h"))) {
											email.removeClass("h h0");
											return false;
										}
									});
									if (state.search) {
										var li = element.parents("li[data-user-id]:first");
										emailButton.attr("href", "/profile/display.action" + tomcatSessionArgs + "?handle=" + li.data("username") + "&mail=true&uid=" + li.data("userId"));
									}
									var buyLink = $("#buy-stamps-link");
									if (buyLink.length) {
										buyLink.parent().prev(":header").andSelf().remove();
									}
								}

								button.find("a.js-chat").each(function () {
									if (state.search && element.parents("li[data-user-id]:first").find(".online").length == 0) {
										$(this).remove();
									} else {
										$(this).click(function () {
											var uid = $(this).data("userId"),
												handle = $(this).data("username");
											if (state.search) {
												var li = $(this).parents("li[data-user-id]:first");
												uid = li.data("userId");
												handle = li.data("username");
											}
											chatWith(uid, handle);
											$("nav[data-role=select] > ul").hide();
											return false;
										});
									}
								});

								button.find("a[data-kiss-id]:gt(0)").click(function (e) {
									var template = $("#comms-mvt-1").clone();
									var kissId = $(this).data("kissId");
									template.find(":radio:not([value=" + kissId + "])").each(function () {
										$(this).parent().hide();
									});
									template.find(".js-more-options > p, .js-more-options .js-more-options").remove();
									template.find(":radio[value=" + kissId + "]").prop("checked", true).addClass("h0");

									return (makeCommsMvtKissHandler(template, state.search, true)).call(this);
								});

								$(this).replaceWith(button.children());
							});
							if (hideEmail) {
								$(".email.action").each(function () {
									if ($(this).parents("li[data-channel]").length == 0) {
										if ($(this).parents("[id^='comms-mvt-']").length == 0) {
											$(this).remove();
										}
									}
								});
							}
						}
						break;
					case 'reply':
						// same as mvt-1
						state.elements.click(makeCommsMvtKissHandler(function () { return "#comms-mvt-reply-" + this.href.replace(/^.*#/, ""); }, false, true));
						break;
				}
				state.complete();
			}
		}

		function makeCommsMvtKissHandler(formIdCallback, search, collapseMoreOptions, openSecondMoreOptions) {
			return function () {
				var formId = (typeof formIdCallback === 'function') ? formIdCallback.call(this) : formIdCallback;
				var lightboxContents = $(formId).clone(true);
				var cat = -Math.floor(Math.random() * 100000);
				lightboxContents.find("label[for]").each(function () {
					var id = $(this).attr("for");
					lightboxContents.find("#" + id).attr("id", id + cat);
					$(this).attr("for", id + cat);
				});
				var li = search ? $(this).parents("li[data-user-id]:first") : null;

				if (search) {
					var username = li.data("username");
					var uid = li.data("userId");
					var photo = li.find(".photo img");
					lightboxContents.find(".js-photo").append(photo.clone());
					lightboxContents.find(".js-username").text(username);
					lightboxContents.find(":hidden[name$=targetUserId]").val(uid);
				}

				var close = lightboxContents.find(".js-cancel, .js-close");
				var form = lightboxContents.find("form");

				if (form.find(!collapseMoreOptions ? ".js-more-options .js-more-options" : ".js-more-options").length && !form.find(".js-more-options > div").filter(function () { return $(this).css("display") === 'none'; }).length) {
					var moreOptionsDiv = form.find(!collapseMoreOptions ? ".js-more-options .js-more-options" : ".js-more-options");
					moreOptionsDiv.each(function () {
						var div = $(this);
						div.find(">div:not(:has(:radio))").remove();
						var moreLink = $("<a href='#' class='more'/>").text(div.data("title") || "Show more messages").click(function (e) {
							var form = $(this).parents("form:first");
							div.show();
							form.find(":radio.h0").removeClass("h0");
							$(this).remove();
							return false;
						});
						form.find(":radio:first").addClass("h0");
						div.hide().before(moreLink);
						if (openSecondMoreOptions) {
							moreLink.click();
						}
					});
				}
				
				doLightbox({
					content: lightboxContents.children(),
					width: 620
				});
				close.click(function () {
					doLightbox.close();
					return $(this).attr("href") !== "#";
				});

				ajaxSubmit(form, makeCommsMvtSuccessCallback(li), undefined);
				return false;
			};
		}

		function makeCommsMvtSuccessCallback(searchLiElement) {
			return function () {
				try {
					window.$v.doBinaryCounter("kiss_sent");
				}
				catch (e) {
					if (window.console && typeof console.log === 'function') {
						console.log('error recording mvt event', e);
					}
				}

				doLightbox.close();

				var success = $("#comms-mvt-send-success").clone(true);

				if (searchLiElement) {
					var username = searchLiElement.data("username");
					success.find(".js-username").text(username);

					if (!searchLiElement.find('.status .contact').length) {
						makeContacted.call(searchLiElement);
					}
				}

				var reload = !searchLiElement;
				var noreload = function () {
					if (!$(this).is(".js-cancel, .js-close")) {
						reload = false;
					}
					return true;
				};

				var searchResultsLink = success.find("a[href*=fullSearch]"); //#search-results-link
				success.find(".js-cancel, .js-close").click(doLightbox.close);
				success.find("a").click(noreload);
				doLightbox({
					content: success.children(),
					width: 620,
					onunload: function () {
						if (reload) {
							location.reload();
						}
					}
				});

				if (searchResultsLink.length) {
					var loader = $("<div/>");
					loader.load(searchResultsLink.attr("href") + " ol.profiles", null, function () {
						loader.find(".js-cancel, .js-close").click(doLightbox.close);
						loader.find("a").click(noreload);
						$(this).find("li:gt(3)").remove();
					});
					searchResultsLink.after(loader);
					searchResultsLink.remove();
				}
			};
		}

		$(document).click(function (e) {
			var target = $(e.target);
			if (!target.is("nav[data-role=select]") && !target.parents("nav[data-role=select]").length) {
				$("nav[data-role=select] > ul").hide();
			}
		});

		// export some functions to the global scope.
		$.extend(window, {
			commsMvt1: function () {
				var state = prepareCommsMvtState();
				if (state) {
					switch (state.type) {
						case 'kiss':
							state.elements.click(makeCommsMvtKissHandler("#comms-mvt-1", state.search, false));
							break;
						case 'reply':
							state.elements.click(makeCommsMvtKissHandler(function () { return "#comms-mvt-reply-" + this.href.replace(/^.*#/, ""); }, false, false));
							break;
					}
					state.complete();
				}
			},
			commsMvt2: function () { commsMvt2And3("#comms-mvt-2", false); },
			commsMvt3: function () { commsMvt2And3("#comms-mvt-3", true); },
			commsMvt4: function () {
				$("#contact_pairedContactHistory #kiss-success-reccomenders").removeClass("h");
				$("#contact_pairedContactHistory .success h4:contains('Kiss Sent!')").text('Your kiss has been sent to ' + $.trim($('#rsvpcontent h1').text()).split(/\s/)[0] + '!');
			},
			commsMvtKissPanel: function () {
				$("#rsvpcontainer > .aside > #send-kiss-form").remove();
			}
		});
	})();


	// report user actions to mvt
	$(function () {
		// read the querystring into params
		var params = {};
		var querystring = window.location.search;
		if (querystring.charAt(0) == '?') {
			querystring = querystring.substring(1);
		}
		var nvs = querystring.split("&");
		$.each(nvs, function (i, nv) {
			var eqi = nv.indexOf('='), n;
			if (eqi == -1) {
				params[nv] = '';
			} else {
				params[nv.substring(0, eqi)] = nv.substring(eqi + 1);
			}
		});
		// do stuff with it
		if (params['kissSendSupport.sendSuccess'] === 'true') {
			if (window.$v && typeof $v.doBinaryCounter === 'function') {
				if (params['kissSendSupport.source'] === 'quick') {
					$v.doBinaryCounter("kiss_sent_quick");
				} else {
					$v.doBinaryCounter("kiss_sent");
				}
			}
		}
	});
}
