  function dataObject(){
    this.room = "";
    this.name = "";
    this.avatar = "";
    this.loginStatus = false;
    this.isAdmin = false;
    this.chatFilter = true;
    this.linkFilter = true;
    this.imageFilter = true;
    this.selectedText = "";
    this.pmRoom = "";
    this.chatTarget = "chatText";
    this.input = new Object();
    this.input.linkURL = "http://";
    this.input.linkTitle = "Link";
    this.botName = "Morti";
    this.backgroundImage = "";
    this.showBkg = true;
    this.shareMe = true;
  }
  var data = new dataObject;
  var URL = "pChat.funcs.php";
  var loginName;
  var signupName;
  var signupPass1;
  var signupPass2;
  var signupEmail;
  var chatText;
  var pmText;
  var maxShakes = 5;
  var shakeCount = 0;
  var shakeTime = null;
  var ajaxMain;
  var ajaxLeft;
  var ajaxPM;
  var ajaxCommon;
  var ajaxCommand;
  var tw, dw, sw, lw, pmw, rm;
  var urlData = new Array("name", "room", "avatar", "isAdmin", "loginStatus", "chatFilter", "linkFilter", "imageFilter", "shareMe");
  //name=Dave&room=Living+Room&avatar=DJM&loginStatus=true&chatFilter=false&linkFilter=false;
  var divArray = new Array(
    "debug",
    "smDebug",
    "stylePanel",
    "emotesWindow",
    "roomPanel",
    "colorWindow",
    "pmWindow",
    "telescope",
    "signupWindow",
    "changePassWindow",
    "imagePanel",
    "linkPanel",
    "loginWindow"
  );
    for (var curDiv = 0; curDiv < divArray.length; curDiv++) {
      var thisDiv = document.getElementById(divArray[curDiv]);
      thisDiv.onclick = function(thisDiv) {
        var z = this.style.zIndex;
        var id = this.id;
        for (var curDiv = 0; curDiv < divArray.length; curDiv++) {
          document.getElementById(divArray[curDiv]).style.zIndex--;
        }
        this.style.zIndex = 50;
      }
      thisDiv.style.zIndex = 20;
    }

  function ajaxRequestObject() {
    var ajaxRequest = null;
    try{
      ajaxRequest = new XMLHttpRequest();
    }
    catch (e){
      try{
        ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
      }
      catch (e) {
        try{
          ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch (e){
          alert("Your browser broke!");
        }
      }
    }
    return ajaxRequest;
  }

  ajaxMain    = new ajaxRequestObject();   // ajax object for use with the main chat panel
  ajaxLeft    = new ajaxRequestObject();   // ajax object for use with the left chat panel
  ajaxPM      = new ajaxRequestObject();   // ajax object for use with the PM window
  ajaxCommon  = new ajaxRequestObject();   // generic ajax object for common functions that don't require a text response
  ajaxCommand = new ajaxRequestObject();   // generic ajax object for command functions, like shake or kick


  function winOnload() {
    var wWidth = xClientWidth();
    var bkg = document.getElementById('body');
    bkg.style.backgroundImage = "url('images/house2.png')";
    if (wWidth <=1024) bkg.style.backgroundImage = "url('images/house_small.png')";
    data.backgroundImage = document.getElementById("body").style.backgroundImage;
    //alert(wWidth);
    xEnableDrag('styleHeader', null,dragParent,null);
    xEnableDrag('imageHeader', null,dragParent,null);
    xEnableDrag('pmHeader', null,dragParent,null);
    xEnableDrag('debugHeader', null,dragParent,null);
    xEnableDrag('smDebugHeader', null,dragParent,null);
    xEnableDrag('emotesHeader', null,dragParent,null);
    xEnableDrag('telescopeHeader', null,dragParent,null);
    xEnableDrag('tsControlsHeader', null,dragParent,null);
    xEnableDrag('roomPanel', null,null,null);
    xEnableDrag('colorWindow', null,null,null);
    loginName = document.getElementById("loginName");
    signupName = document.getElementById("signupName");
    signupPass1 = document.getElementById("signupPass1");
    signupPass2 = document.getElementById("signupPass2");
    signupEmail = document.getElementById("signupEmail");
    chatText = document.getElementById("chatText");
    chatText.onfocus = function() {
      data.chatTarget = "chatText";
    }
    chatText.onmouseup = function() {                               // this function captures the selected text for 99% of browsers
      if (document.selection != undefined) {   // and copies the text into the data object, for later use
        data.selectedText = document.selection.createRange().text;
      }
      else if (document.selectionStart != undefined) {
        data.selectedText = chatText.value.substring(chatText.selectionStart, chatText.selectionEnd);
      }
      else if (document.getSelection) {
        data.selectedText = (chatText.value).substring(chatText.selectionStart, chatText.selectionEnd);
      }
      else if(window.getSelection) {
        data.selectedText = window.getSelection();
      }
      else data.selectedText = "";
    }
    pmText = document.getElementById("pmText");
    pmText.onfocus = function() {
      data.chatTarget = "pmText";
    }
    pmText.onmouseup = function() {                               // this function captures the selected text for 99% of browsers
      if (document.selection != undefined) {   // and copies the text into the data object, for later use
        data.selectedText = document.selection.createRange().text;
      }
      else if (document.selectionStart != undefined) {
        data.selectedText = pmText.value.substring(pmText.selectionStart, pmText.selectionEnd);
      }
      else if (document.getSelection) {
        data.selectedText = (pmText.value).substring(pmText.selectionStart, pmText.selectionEnd);
      }
      else if(window.getSelection) {
        data.selectedText = window.getSelection();
      }
      else data.selectedText = "";
    }
    pruneUsers();
    initChat();
    hideDiv("emotesWindow");
    hideDiv("colorWindow");
    hideDiv("imagePanel");
    hideDiv("linkPanel");
    hideDiv("smDebug");
    getBotName();
    setInterval("updateLeft()", 5000);         // update the main panel every 5 seconds
    setInterval("updateMainPanel()", 2500);     // update the left panel every 2.5 seconds
    setInterval("updatePM()", 2600);            // update the PM panel every 2.6 seconds
    setInterval("monitorPmInvites()", 3000);   // Check for PM invites every 3 seconds
    setInterval("commandCheck()", 2000);       // Check for chat commands every 2 seconds
    setTimeout("checkLogin()", 500);
    setTimeout("showSky()", 1000);
  }

  function initChat() {
    checkLogin();
    var leftURL = URL + "?func=getLeft";
    var mainURL = URL + "?func=getMain";
    var pmhURL = URL + "?func=getPmHeader";
    ajaxFunction(ajaxLeft, leftURL, 'leftPanel');
    setTimeout("ajaxFunction(ajaxCommon, '" + mainURL + "', 'mainPanel')", 100);
    var lis = data.loginStatus;
    if (lis == "true") {
      showDiv('chatForm');
    }
    else updateMainPanel();
    var isAdmin = data.isAdmin;
    if (isAdmin === true) showDiv('adminLink');
  }

  function ajaxFunction(ajaxRequest, fName, element){
    ajaxRequest.abort();
    ajaxRequest.open("GET", fName, true);
    ajaxRequest.onreadystatechange=function() {
      if(ajaxRequest.readyState == 4) {
        //alert(ajaxRequest.responseText);
        document.getElementById(element).innerHTML = ajaxRequest.responseText;
      }
    }
    ajaxRequest.send(null);
  }

  function ajaxPostFunction(ajaxRequest, fName, element, params, abortIfBlank){
    var response = "";
    //alert('Processing ' + element);
    ajaxRequest.abort();
    ajaxRequest.open("POST", fName, true);
    ajaxRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    ajaxRequest.setRequestHeader("Content-length", params.length);
    ajaxRequest.setRequestHeader("Connection", "close");
    ajaxRequest.onreadystatechange=function() {
      if(ajaxRequest.readyState == 4 && ajaxRequest.status == 200) {
        response = ajaxRequest.responseText;
        //if (element == "pmContent") alert(response);
        if (abortIfBlank === false || response != "") document.getElementById(element).innerHTML = response;
      }
    }
    ajaxRequest.send(params);
  }

  function ajaxPostEval(ajaxRequest, fName, params, abortIfBlank){
    var response = "";
        //alert("fName = "+fName+"\nparams = "+params);
    ajaxRequest.abort();
    ajaxRequest.open("POST", fName, true);
    ajaxRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    ajaxRequest.setRequestHeader("Content-length", params.length);
    ajaxRequest.setRequestHeader("Connection", "close");
    ajaxRequest.onreadystatechange=function() {
      if(ajaxRequest.readyState == 4 && ajaxRequest.status == 200) {
        response = ajaxRequest.responseText;
        if (abortIfBlank === false || response != "") {
          //alert("|"+response+"|");
          //alert (data.botName);
          eval(response);
        }
      }
    }
    ajaxRequest.send(params);
  }

  function postSignup() {
    //alert("Posting login!");
    var name = signupName.value;
    if (signupEmail.value == "") {
      alert("You can't retrieve a lost password if you don't give us your email address. We promise never to use it except to mail you a lost password.");
      signupEmail.focus();
      return false;
    }
    if (name == "") {
      alert("Name required!");
      signupName.focus();
      return false;
    }
    if (signupPass1.value == "" || signupPass1.value != signupPass2.value) {
      alert("Passwords must match, and cannot be blank.");
      signupPass1.value = "";
      signupPass2.value = "";
      signupPass1.focus();
      return false;
    }
    //var params = "func=login&name=" + name + "&pass=" + pass + "&avatar=" + avatar + "&room=" + room + "&chatFilter=" + cf + "&linkFilter=" + lf + "&imageFilter=" + imgf;
    var params = "func=signup&name="+ name + "&pass=" + signupPass1.value + "&email=" + signupEmail.value;
    ajaxPostEval(ajaxCommon, URL, params, false);
    return false;
  }

  function postLogin() {
    //alert("Posting login!");
    var name = document.getElementById("loginName").value;
    var pass = document.getElementById("pass").value;
    var room = document.getElementById("room").value;
    var avatar = document.lForm.avatar.value;
    var chatFilter = document.getElementById("chatFilter");
    var cf = chatFilter.checked;
    var linkFilter = document.getElementById("linkFilter");
    var lf = linkFilter.checked;
    var imageFilter = document.getElementById("imageFilter");
    var imgf = imageFilter.checked;
    var shareFilter = document.getElementById("share");
    var shareMe = shareFilter.checked;
    data.chatFilter = (cf === true) ? "true" : "false";
    data.linkFilter = (lf === true) ? "true" : "false";
    data.imageFilter = (imgf === true) ? "true" : "false";
    data.shareMe = (shareMe === true) ? "true" : "false";
    if (name == "") {
      alert("Name required!");
      loginName.focus();
      return false;
    }
    if ((avatar == "") && (pass == "")) {
      alert("You kinda need an avatar\nClick on an image below to choose one.");
      loginName.focus();
      return false;
    }
    data.name = name;
    data.room = room;
    data.avatar = avatar;
    //var params = "func=login&name=" + name + "&pass=" + pass + "&avatar=" + avatar + "&room=" + room + "&chatFilter=" + cf + "&linkFilter=" + lf + "&imageFilter=" + imgf;
    var params = "func=login&share=" + shareMe;
    var paramTag = "";
    for (ele in data) {
      if (ele != "isAdmin"
       && ele != "loginStatus"
       && ele != "selectedText"
       && ele != "pmRoom"
       && ele != "input"
       && ele != "backgroundImage"
       && ele != "chatTarget"
       ) {
         paramTag = "&" + ele + "=" + data[ele];
       }
       else {
         paramTag = "";
       }
       params += paramTag;
    }
    params += "&pass=" + pass;
    ajaxPostEval(ajaxCommon, URL, params, false);
    return false;
  }

  function logout() {
    var params = "func=logout";
    ajaxPostEval(ajaxCommon, URL, params, false);
    data.name = "";
    data.room = "";
    data.avatar = "";
    data.loginStatus = "false";
    data.isAdmin = "false";
    data.pmRoom = "";
    hideDiv("logoutLink");
    hideDiv("pmWindow");
    hideDiv("chatForm");
    roomPic("../house2");
    setTimeout("initChat()", 100);
    //document.getElementById("adminLink").style.visibility = "hidden";
  }

  function updateMainPanel(force) {
    force = force || false;
    var main = document.getElementById("mainPanel");
    if ((main.innerHTML.indexOf("You are not logged in") > -1) && force === false) return false;
    if (data.loginStatus == "false") {
      main.innerHTML = "<p>There are new features available! Please check the<a href=\"help.php\" target=\"_blank\">Help Page</a> for more information.</p>You are not logged in. <a onclick=\"showDiv('loginWindow'); return false\" href=\"#\">Login here</a>.";
      return false;
    }
    updatePanel("mainPanel", force);
    commandCheck();
    return false;
  }

  function updatePanel(panel, force) {
    force = force || false;
    var udName;
    var paramList = "";
    for (ele in urlData) {
      udName = urlData[ele];
      paramList += "&" + udName + "=" + data[udName];
    }
    var room = data.room;
    var params = "";
    switch (panel) {
      case "mainPanel":
        var params = "func=getMain" + paramList;
        var ajaxReq = ajaxMain;
        break;
      case "leftPanel":
        var params = "func=getLeft";
        var ajaxReq = ajaxLeft;
        break;
      case "pmHeader":
      case "pmContent":
        var params = "func=getPM&pmRoom=" + data.pmRoom + paramList;
        var ajaxReq = ajaxPM;
    }
    if (params == "") return false;
    ajaxPostFunction(ajaxReq, URL, panel, params, false);
    if (data.room != "") roomPic(room);
    return false;
  }

  function updateLeft(force) {
    force = force || false;
    var leftPanel = document.getElementById("leftPanel").innerHTML;
    if (leftPanel.indexOf("Not Logged In") > -1) return false;
    updatePanel('leftPanel', force);
    return false;
  }

  function updatePM(force) {
    if (data.pmRoom == "") return false;
    force = force || false;
    //alert("Updating!");
    var params = "func=getPM&pmRoom=" + data.pmRoom;
    ajaxPostFunction(ajaxPM, URL, "pmContent", params, false);
    return false;
  }

  function doRollCall() {
    var params = "func=rollCall";
    ajaxPostEval(ajaxCommon, URL, params, false);
  }

  function updateDivs() {
    updateMainPanel(true);
    setTimeout("updateLeft()", 50);
    setTimeout("updatePM(true)", 100);
  }

  function sendChat() {
    var chatText = document.getElementById('chatText');
    if (chatText.value === "") return false;
    var msg = chatText.value;
    msg = replaceParts(msg);
    chatText.value = "";
    var params = "func=postMessage&msg=" + msg;
    ajaxPostEval(ajaxCommon, URL, params, true);
    chatText.focus();
    commandCheck();
    return false;
  }

  function sendPM() {
    if (data.pmRoom == "") return false;
    var pmText = document.getElementById('pmText');
    if (pmText.value == "") return false;
    var msg = pmText.value;
    pmText.value = "";
    var params = "func=postPM&msg=" + msg + "&pmRoom=" + data.pmRoom + "&name=" + data.name;
    var x = ajaxPostEval(ajaxPM,URL,params,true);
    pmText.focus();
    return false;
  }

  function switchRoom(newRoom) {
    if(data.loginStatus != "true") return false;
    var oldRoom = data.room;
    roomPic(newRoom);
    var params = "func=switchRooms&oldRoom="+oldRoom+"&newRoom="+newRoom;
    ajaxPostEval(ajaxCommon, URL, params, true);
    hideDiv("roomPanel");
    var chatTarget = document.getElementById(data.chatTarget);
    chatTarget.focus();
    return false;
  }

  function bringToFront(e) {
    var z = e.style.zIndex;
    var id = e.id;
    switch (id) {
      case "loginWindow":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
      case "pmWindow":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        break;
      case "emotesWindow":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
      case "colorWindow":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
      case "roomPanel":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
      case "debug":
        e.style.zIndex = 50;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
      case "smDebug":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
      case "stylePanel":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
      case "formatPanel":
        e.style.zIndex = 50;
        document.getElementById("debug").style.zIndex--;
        document.getElementById("smDebug").style.zIndex--;
        document.getElementById("stylePanel").style.zIndex--;
        document.getElementById("roomPanel").style.zIndex--;
        document.getElementById("emotesWindow").style.zIndex--;
        document.getElementById("colorWindow").style.zIndex--;
        document.getElementById("loginWindow").style.zIndex--;
        document.getElementById("pmWindow").style.zIndex--;
        break;
    }
  }

  function hideMe(e) {
    var parent = xParent(e);
    //alert (parent.id);
    parent.style.visibility = "hidden";
    var chatTarget = document.getElementById(data.chatTarget);
    try {chatTarget.focus();}
    catch(e){}
  }

  function showLink(name) {
    var div = document.getElementById(name);
    div.style.visibility = "visible";
        var chatTarget = document.getElementById(data.chatTarget);
    chatTarget.focus();
  }
// showLoginDiv
  function showLoginDiv() {
    var lw = document.getElementById('loginWindow');
    lw.style.visibility = "visible";
    bringToFront(lw);
    setTimeout("loginName.focus()", 50);
  }

  function showSignupDiv() {
    var sw = document.getElementById('signupWindow');
    sw.style.visibility = "visible";
    bringToFront(sw);
    signupName.value = "";
    signupPass1.value = "";
    signupPass2.value = "";
    signupEmail.value = "";
    setTimeout("signupName.focus()", 50);
  }

  function showChat() {
    hideDiv('loginWindow');
    showDiv('logoutLink');
    showDiv('shareLabel');
    showDiv('chatForm');
  }

  function  showDiv(name) {
    var div = document.getElementById(name);
    div.style.visibility = "visible";
    bringToFront(div);
    var chatTarget = document.getElementById(data.chatTarget);
    try {chatTarget.focus();}
    catch(e){}
  }

  function  toggleDiv(name) {
    var div = document.getElementById(name);
    if (div === null) alert(name + " does not exist!");
    else {
      var vis = div.style.visibility;
      div.style.visibility = (vis == "hidden") ? "visible" : "hidden";
      if (div.style.visibility == "visible") bringToFront(div);
      var chatTarget = document.getElementById(data.chatTarget);
      try {chatTarget.focus();}
      catch(e){}
    }
  }

  function hideDiv(name) {
    var div = document.getElementById(name);
    if (div === null) alert(name + " does not exist!");
    else {
      div.style.visibility = "hidden";
      var chatTarget = document.getElementById(data.chatTarget);
      try {chatTarget.focus();}
      catch(e){}
    }
  }

  function PM_user(targetName, targetRoom) {
    var message = prompt("What do you wish to say to " + targetName + "?", data.name + " sent you a private message.");
    if (message == null) return false;
    //alert("This function is still in the works. when it's done, you'll be able to PM "+targetName+" to your heart's content");
    var myName = data.name;
    var myRoom = data.room;
    var params = "func=pmRequest&targetName=" + targetName + "&targetRoom=" + targetRoom + "&name=" + myName + "&message=" + message;
    ajaxPostEval(ajaxCommon, URL, params, true);
    showDiv("pmWindow");
    var pmHeader = document.getElementById("pmHeader");
    var pmhContent = pmHeader.innerHTML + ": " + myName + " - " + targetName;
    pmHeader.innerHTML = pmhContent;
    var pmText = document.getElementById("pmText");
    pmText.value = "";
    pmText.focus();
    data.pmRoom = "closet_" + myName + "_" + targetName;
    var pmMsg = myName + ": " + message;
    var pmContent = document.getElementById("pmContent");
    pmContent.innerHTML = pmMsg;
    updateDivs();
    return false;
  }

  function shakeMain() {
    var x, y, n, r, v;
    var date = new Date();
    if (shakeTime === null) shakeTime = date.getTime();
    var maxTime = shakeTime + 1000;
    var now = date.getTime();
    if (now >= maxTime) {
      restoreMain();
      shakeTime = null;
      clearTimeout(v);
      setTimeout("restoreMain(250, 0)", 105);
      return;
    }
    var e = document.getElementById("mainPanel");
    xSlideTo(e,50,50,50);
    setTimeout("restoreMain(250, 0)", 52);
    v = setTimeout("shakeMain()", 105);
    return;
  }

  function restoreMain(x,y) {
    var e = document.getElementById("mainPanel");
    xSlideTo(e,x,y, 50);
  }

  function addEmote(alt) {
    var chatTarget = document.getElementById(data.chatTarget);
    chatTarget.value += alt;
    var ecb = document.getElementById("ecb").checked;
    if (ecb === false) hideDiv("emotesWindow");
    try {chatTarget.focus();}
    catch(e){}
  }

  function pickMe(rb) {
    document.lForm.avatar.value = rb;
  }

  function showData() {
    var out = "";
    var propertyString;
    for (property in data) {
      propertyString = property + " = " + data[property] + "\n";
      out += propertyString;
    }
      alert(out);
  }

  function checkLogin() {
    var params = "func=checkLogin";
    var x = ajaxPostEval(ajaxCommon, URL, params, false);
    if (data.loginStatus === false) updateMainPanel(true);
  }

  function loggedIn() {
    //alert('you are logged in, ' + data.name + '!');
    showDiv("chatForm");
    showDiv("logoutLink");
    hideDiv("loginWindow");
  }

  function addColor(color) {
   var chatTarget = document.getElementById(data.chatTarget);
   var colorStartTag = "[" + color + "]";
    var colorEndTag = "[/" + color + "]";
    if(data.selectedText !== "") {
      var sel = data.selectedText;
      var selStart = chatTarget.value.indexOf(sel);
      var selLen = sel.length;
      var selEnd = selStart + selLen;
      var begin = (chatTarget.value).substring(0, selStart);
      var middle = colorStartTag + sel + colorEndTag;
      var end = (chatTarget.value).substring(selEnd, (chatTarget.value).length);
      chatTarget.value = begin + middle + end;
    }
    else if(document.getSelection) {
      var begin = (chatTarget.value).substring(0, chatTarget.selectionStart);
      var middle = (chatTarget.value).substring(chatTarget.selectionStart, chatTarget.selectionEnd);
      var end = (chatTarget.value).substring(chatTarget.selectionEnd);
      //alert("begin = " + begin + "\nmiddle = " + middle + "\nend = " + end);
      var text = begin + colorStartTag + middle + colorEndTag + end;
      chatTarget.value = text;
    }
    else {
      chatTarget.value += colorStartTag + colorEndTag
    }
    hideDiv("colorWindow");
    try {chatTarget.focus();}
    catch(e){}
  }

  function addImg() {
    var chatTarget = document.getElementById(data.chatTarget);
    var imgURL = document.getElementById("imgURL");
    var imgText = imgURL.value;
    imgURL.value = "http://";
    if ((imgText == "") || (imgText == "http://")) return false;
    chatTarget.value += '[img]' + imgText + '[/img]';
    hideDiv("imagePanel");
    try {chatTarget.focus();}
    catch(e){}
    return false;
  }

  function addLinkTags() {
    var chatTarget = document.getElementById(data.chatTarget);
    var linkURL = document.getElementById('linkURL');
    var linkTitle = document.getElementById('linkTitle');
    var luv = linkURL.value;
    var ltv = linkTitle.value;
    if (ltv == "") ltv = luv;
    chatTarget.value += '[link=' + luv + ']' + ltv + '[/link]';
    linkURL.value = data.input.linkURL;
    linkTitle.value = data.input.linkTitle;
    hideDiv("linkPanel");
    try {chatTarget.focus();}
    catch(e){}
    return false;
  }

  function setFilter(name, ele){
    switch (name) {
      case "chatFilter":
        var filter1 = document.getElementById("cf");
        var filter2 = document.getElementById("chatFilter");
        break;
      case "imageFilter":
        var filter1 = document.getElementById("if");
        var filter2 = document.getElementById("imageFilter");
        break;
      case "linkFilter":
        var filter1 = document.getElementById("lf");
        var filter2 = document.getElementById("linkFilter");
        break;
    }
    //alert(ele.checked);
    //ele.checked = (!ele.checked);
    filter1.checked = ele.checked;
    filter2.checked = ele.checked;
    data[name] = (filter2.checked === true) ? "true" : "false";
    var filter = data[name];
    var params = "func=setFilter&filterName=" + name + "&filterVal=" + filter;
    ajaxPostEval(ajaxCommon, URL, params, true);
  }

  function setBkg(ele) {
    data.showBkg = ele.checked;
    var params = "func=setVar&varName=showBkg&value=" + (ele.checked) ? "true" : "false";
    ajaxPostEval(ajaxCommon, URL, params, true);
    updateMainPanel();
  }

  function setStyle(color) {
    //alert("switching to " + color + " style!");
    var bd = document.getElementById('body');
    bd.className = color;
    var chatTarget = document.getElementById(data.chatTarget);
    try {chatTarget.focus();}
    catch(e){}
  }

  function shareMe(ele) {
    data.shareMe = ele.checked;
    var shareMe = (data.shareMe) ? "true" : "false";
    var chatTarget = document.getElementById(data.chatTarget);
    try {chatTarget.focus()}
    catch(e){alert("oops?")}
    var params = "func=toggleBot&name=" + data.name + "&room=" + data.room + "&shareMe=" + shareMe;
    ajaxPostEval(ajaxCommon, URL, params, true);
  }

  function kick(targetName) {
    var name = data.name;
    if (name == targetName) {
      alert("You have been kicked, " + name + "!!!");
      logout();
    }
  }

  function chooseFormat(f) {
    var chatTarget = document.getElementById(data.chatTarget);
    var fEnd = "[/" + f.substring(1);
    if(data.selectedText !== "") {
      var sel = data.selectedText;
      var selStart = chatTarget.value.indexOf(sel);
      var selLen = sel.length;
      var selEnd = selStart + selLen;
      var begin = (chatTarget.value).substring(0, selStart);
      var middle = f + sel + fEnd;
      var end = (chatTarget.value).substring(selEnd, (chatTarget.value).length);
      chatTarget.value = begin + middle + end;
    }
    else if(document.getSelection) {
      var begin = (chatTarget.value).substring(0, chatTarget.selectionStart);
      var middle = (chatTarget.value).substring(chatTarget.selectionStart, chatTarget.selectionEnd);
      var end = (chatTarget.value).substring(chatTarget.selectionEnd);
      var text = begin + f + middle  + fEnd;
      chatTarget.value = text;
    }
    else {
      chatTarget.value += f + fEnd
    }
    try {chatTarget.focus();}
    catch(e){}
  }

  function askBot() {
    var chatTarget = document.getElementById(data.chatTarget);
    var botName = data.botName;
    chatTarget.value = "/ask " + botName + " ";

    try {chatTarget.focus()}
    catch(e){alert("oops?")}
  }

  function getBotName() {
    var params = "func=getBotName";
    var x = ajaxPostEval(ajaxCommon, URL, params, false);
  }

  function commandCheck() {
    var content  = document.getElementById("mainPanel").innerHTML;
    var debug = document.getElementById("soundmanager-debug");
    debug.innerHTML = content;
    var cmdBan   = "<!-- ban ";
    var cmdBuzz  = "<!-- buzz ";
    var cmdFlash  = "<!-- flash ";
    var cmdKick  = "<!-- kick ";
    var cmdShake = "<!-- shake ";
    var cmdGetCoords = "<!-- getCoords";
    if (content.indexOf(cmdBuzz)      > -1) doCmd("buzz",content);
    if (content.indexOf(cmdFlash)     > -1) doCmd("flash",content);
    if (content.indexOf(cmdShake)     > -1) doCmd("shake",content);
    if (content.indexOf(cmdKick)      > -1) doCmd("kick",content);
    if (content.indexOf(cmdBan)       > -1) doCmd("ban",content);
    if (content.indexOf(cmdGetCoords) > -1) {
      //alert("Found getCoords!");
      var start = content.indexOf("getCoords");
      var end = content.indexOf(" -->", start);
      var command = content.substr(start, end - start);
      var splitArray = command.split(", ", 3);
      var action     = splitArray[0];
      var targetLoc  = splitArray[1];
      var targetName = splitArray[2];
      //alert("action = " + action + "\ntargetLoc = " + targetLoc + "\ntargetName = " + targetName);
      if (targetName == data.name) {
        deleteNotification("system", targetLoc, action);
        getCoords(targetLoc, targetName);
      }
    }
  }

  function doCmd(action, content) {
    var debug = document.getElementById("soundmanager-debug");
    debug.innerHTML = content;
    var myName = data.name;
    var cmd = "<!-- " + action + " ";
    var cbl = cmd.length;
    var find1 = content.indexOf(cmd);
    var find2 = content.indexOf(" -->", find1);
    var fLen = find2 - (find1 + cbl);
    var targetName = content.substr(find1 + cbl, fLen);
    //alert("find1 = " + find1 + "\nfind2 = " + find2 + "\ntargetName = |" + targetName + "|");
    if (targetName == myName) {
      //alert(action + " " + myName);
      deleteNotification("system", myName, action);
      eval(action + '("' + myName +'", "kicked")');
    }
  }

  function pruneRoom() {
    var isAdmin = data.isAdmin;
    //alert(isAdmin);
    if (isAdmin == false) {
      alert("Admin only!");
      return false;
    }
    var params = "func=prune";
    ajaxPostEval(ajaxCommon, URL, params, true);
    return false;
  }

  function pruneUsers() {
    var params = "func=clearInactiveUsers";
    ajaxPostEval(ajaxCommon, URL, params, true);
    return false;
  }

  function replaceParts(msg) {
    // replace characters that may corrupt a URL String
    var sign, beginning, end, start, oldStart;
    var tmpMsg = msg;
    var findArray = new Array("&", "?", "#");
    var replArray = new Array("_amp_", "_qm_", "_ps_");
    for (var sign in findArray) {
      start = 0;
      oldStart = start;
      start = tmpMsg.indexOf(findArray[sign], start);
      while (start != -1) {
        beginning = tmpMsg.substr(0, start);
        end = tmpMsg.substr(start + 1);
        //alert("start = " + start + "\nbeginning = " + beginning + "\nend = " + end);
        tmpMsg = beginning + replArray[sign] + end;
        oldStart = start;
        start = tmpMsg.indexOf(findArray[sign], start);
      }
    }
    return tmpMsg;
  }

  function dragParent(el, dx, dy, ev) {
    var ep = xParent(el);
    xMoveTo(ep, ep.offsetLeft + dx, ep.offsetTop + dy);
  }

  function roomPic(room) {
    if (data.showBkg === true) {
      var bi = "url('images/rooms/"+room+"')";
      document.getElementById('body').style.backgroundImage = bi;
      data.backgroundImage = bi;
    }
    else {
      document.getElementById('body').style.backgroundImage = "url()";
    }
  }

  function monitorPmInvites() {
    var content = document.getElementById("mainPanel").innerHTML;
    var myName = data.name;
    var search = "<!-- PM request: " + myName;
    var pmrStart = content.indexOf(search);
    //alert("Checking for PMs!\nsearch = " + search + "\npmrStart = " + pmrStart + "\n\ncontent:\n" + content);
    if (pmrStart < 0) return false;
    var targetName, sender, message;
    var searchLen = search.length;
    pmrStart += searchLen;
    var pmrEnd = content.indexOf(" -->",pmrStart);
    var pmrLen = pmrEnd - pmrStart;
    var pmr = content.substr(pmrStart,pmrLen);
    var pmrArray = pmr.split(" : ", 3);
    targetName = pmrArray[0];
    sender = pmrArray[1];
    message = pmrArray[2];
    var accept = confirm(sender + " whispered to you, '" + message + "'.\nDo you wish to talk to them?");
    deletePM(sender, myName);
    if (accept === false) return false;
    showDiv("pmWindow");
    var pmText = document.getElementById("pmText");
    pmText.value = "";
    pmText.focus();
    data.pmRoom = "closet_" + sender + "_" + myName;
    var pmMsg = sender + ": " + message;
    var pmContent = document.getElementById("pmContent");
    updateDivs();
    return false;
  }

  function deletePM(sender, targetName) {
    var params = "func=deletePmNotification&sender=" + sender + "&targetName=" + targetName + "&pmRoom=" + data.room;
    var x = ajaxPostEval(ajaxCommon,URL,params,true);
  }

  function deleteNotification(sender, targetName, cmd) {
    var name = data.name;
    //alert("Deleting Notification.");
    var params = "func=deleteNotification&sender=" + sender + "&targetName=" + targetName + "&room=" + data.room + "&cmd=" + cmd + "&name=" + name;
    var x = ajaxPostEval(ajaxCommon,URL,params,true);
    document.getElementById('mainPanel').innerHTML = "";
  }

  function clearDefault(input) {
    var id = input.id;
    var defaultValue = data.input[id];
    if (input.value == defaultValue) input.value = "";
    //alert(defaultValue);
  }

  function buzzUser(name) {
    var txt = document.getElementById(data.chatTarget);
    txt.value = "/buzz " + name;
    sendChat();
  }

  function buzz() {
    document.getElementById('soundmanager-debug').innerHTML = "";
    soundManager.createSound('mySound','scripts/buzzer.mp3');
    soundManager.stop('mySound');
    soundManager.play('mySound');
  }

  function flashUser(name) {
    var txt = document.getElementById(data.chatTarget);
    txt.value = "/flash " + name;
    sendChat();
  }

  function flash() {
    var bd = document.getElementById('body');
    var curColor = bd.className;
    switch (curColor) {
      case "white":
        var color1 = "black";
        var color2 = "default";
        break;
      case "black":
        var color1 = "default";
        var color2 = "white";
        break;
      case "default":
      default:
        var color1 = "white";
        var color2 = "black";
        curColor = "default";
    }
    //alert("Flash! curColor = " + curColor);
    setStyle(color1);
    setTimeout("setStyle('"+color2+"')", 50);
    setTimeout("setStyle('"+curColor+"')", 100);
  }

  function shake() {
    var main = document.getElementById('mainPanel');
    var w = xWidth(main);
    var x = xWidth(main, w);
    shakeMain();
  }

function xEvent(evt) // object prototype
{
  var e = evt || window.event;
  if (!e) return;
  this.type = e.type;
  this.target = e.target || e.srcElement;
  this.relatedTarget = e.relatedTarget;
  if (xDef(e.pageX)) { this.pageX = e.pageX; this.pageY = e.pageY; }
  else if (xDef(e.clientX)) { this.pageX = e.clientX + xScrollLeft(); this.pageY = e.clientY + xScrollTop(); }
  if (xDef(e.offsetX)) { this.offsetX = e.offsetX; this.offsetY = e.offsetY; }
  else if (xDef(e.layerX)) { this.offsetX = e.layerX; this.offsetY = e.layerY; }
  else { this.offsetX = this.pageX - xPageX(this.target); this.offsetY = this.pageY - xPageY(this.target); }
  this.keyCode = e.keyCode || e.which || 0;
  this.shiftKey = e.shiftKey; this.ctrlKey = e.ctrlKey; this.altKey = e.altKey;
  if (typeof e.type == 'string') {
    if (e.type.indexOf('click') != -1) {this.button = 0;}
    else if (e.type.indexOf('mouse') != -1) {
      this.button = e.button;
    }
  }
}

var xLibrary={version:'4.17',license:'GNU LGPL',url:'http://cross-browser.com/'};

function xAddEventListener(e,eT,eL,cap)
{
  if(!(e=xGetElementById(e)))return;
  eT=eT.toLowerCase();
  if(e.addEventListener)e.addEventListener(eT,eL,cap||false);
  else if(e.attachEvent)e.attachEvent('on'+eT,eL);
  else {
    var o=e['on'+eT];
    e['on'+eT]=typeof o=='function' ? function(v){o(v);eL(v);} : eL;
  }
}
// xDef r1, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xDef()
{
  for(var i=0; i<arguments.length; ++i){if(typeof(arguments[i])=='undefined') return false;}
  return true;
}
// xEnableDrag r7, Copyright 2002-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xEnableDrag(id,fS,fD,fE)
{
  var mx = 0, my = 0, el = xGetElementById(id);
  if (el) {
    el.xDragEnabled = true;
    xAddEventListener(el, 'mousedown', dragStart, false);
  }
  // Private Functions
  function dragStart(e)
  {
    if (el.xDragEnabled) {
      var ev = new xEvent(e);
      xPreventDefault(e);
      mx = ev.pageX;
      my = ev.pageY;
      xAddEventListener(document, 'mousemove', drag, false);
      xAddEventListener(document, 'mouseup', dragEnd, false);
      if (fS) {
        fS(el, ev.pageX, ev.pageY, ev);
      }
    }
  }
  function drag(e)
  {
    var ev, dx, dy;
    xPreventDefault(e);
    ev = new xEvent(e);
    dx = ev.pageX - mx;
    dy = ev.pageY - my;
    mx = ev.pageX;
    my = ev.pageY;
    if (fD) {
      fD(el, dx, dy, ev);
    }
    else {
      xMoveTo(el, el.offsetLeft + dx, el.offsetTop + dy);
    }
  }
  function dragEnd(e)
  {
    var ev = new xEvent(e);
    xPreventDefault(e);
    xRemoveEventListener(document, 'mouseup', dragEnd, false);
    xRemoveEventListener(document, 'mousemove', drag, false);
    if (fE) {
      fE(el, ev.pageX, ev.pageY, ev);
    }
    if (xEnableDrag.drop) {
      xEnableDrag.drop(el, ev);
    }
  }
}

xEnableDrag.drops = []; // static property
// xGetElementById r2, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xGetElementById(e)
{
  if(typeof(e)=='string') {
    if(document.getElementById) e=document.getElementById(e);
    else if(document.all) e=document.all[e];
    else e=null;
  }
  return e;
}
// xLeft r2, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xLeft(e, iX)
{
  if(!(e=xGetElementById(e))) return 0;
  var css=xDef(e.style);
  if (css && xStr(e.style.left)) {
    if(xNum(iX)) e.style.left=iX+'px';
    else {
      iX=parseInt(e.style.left);
      if(isNaN(iX)) iX=xGetComputedStyle(e,'left',1);
      if(isNaN(iX)) iX=0;
    }
  }
  else if(css && xDef(e.style.pixelLeft)) {
    if(xNum(iX)) e.style.pixelLeft=iX;
    else iX=e.style.pixelLeft;
  }
  return iX;
}
// xMoveTo r1, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xMoveTo(e,x,y)
{
  xLeft(e,x);
  xTop(e,y);
}
// xNum r2, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xNum()
{
  for(var i=0; i<arguments.length; ++i){if(isNaN(arguments[i]) || typeof(arguments[i])!='number') return false;}
  return true;
}
// xPageX r2, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xPageX(e)
{
  var x = 0;
  e = xGetElementById(e);
  while (e) {
    if (xDef(e.offsetLeft)) x += e.offsetLeft;
    e = xDef(e.offsetParent) ? e.offsetParent : null;
  }
  return x;
}
// xPageY r4, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xPageY(e)
{
  var y = 0;
  e = xGetElementById(e);
  while (e) {
    if (xDef(e.offsetTop)) y += e.offsetTop;
    e = xDef(e.offsetParent) ? e.offsetParent : null;
  }
  return y;
}
// xPreventDefault r1, Copyright 2004-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xPreventDefault(e)
{
  if (e && e.preventDefault) e.preventDefault();
  else if (window.event) window.event.returnValue = false;
}
// xRemoveEventListener r6, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xRemoveEventListener(e,eT,eL,cap)
{
  if(!(e=xGetElementById(e)))return;
  eT=eT.toLowerCase();
  if(e.removeEventListener)e.removeEventListener(eT,eL,cap||false);
  else if(e.detachEvent)e.detachEvent('on'+eT,eL);
  else e['on'+eT]=null;
}
// xScrollLeft r3, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xScrollLeft(e, bWin)
{
  var offset=0;
  if (!xDef(e) || bWin || e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    var w = window;
    if (bWin && e) w = e;
    if(w.document.documentElement && w.document.documentElement.scrollLeft) offset=w.document.documentElement.scrollLeft;
    else if(w.document.body && xDef(w.document.body.scrollLeft)) offset=w.document.body.scrollLeft;
  }
  else {
    e = xGetElementById(e);
    if (e && xNum(e.scrollLeft)) offset = e.scrollLeft;
  }
  return offset;
}
// xScrollTop r3, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xScrollTop(e, bWin)
{
  var offset=0;
  if (!xDef(e) || bWin || e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    var w = window;
    if (bWin && e) w = e;
    if(w.document.documentElement && w.document.documentElement.scrollTop) offset=w.document.documentElement.scrollTop;
    else if(w.document.body && xDef(w.document.body.scrollTop)) offset=w.document.body.scrollTop;
  }
  else {
    e = xGetElementById(e);
    if (e && xNum(e.scrollTop)) offset = e.scrollTop;
  }
  return offset;
}
// xTop r2, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xTop(e, iY)
{
  if(!(e=xGetElementById(e))) return 0;
  var css=xDef(e.style);
  if(css && xStr(e.style.top)) {
    if(xNum(iY)) e.style.top=iY+'px';
    else {
      iY=parseInt(e.style.top);
      if(isNaN(iY)) iY=xGetComputedStyle(e,'top',1);
      if(isNaN(iY)) iY=0;
    }
  }
  else if(css && xDef(e.style.pixelTop)) {
    if(xNum(iY)) e.style.pixelTop=iY;
    else iY=e.style.pixelTop;
  }
  return iY;
}

function xStr(s)
{
  for(var i=0; i<arguments.length; ++i){if(typeof(arguments[i])!='string') return false;}
  return true;
}

function xGetComputedStyle(e, p, i)
{
  if(!(e=xGetElementById(e))) return null;
  var s, v = 'undefined', dv = document.defaultView;
  if(dv && dv.getComputedStyle){
    s = dv.getComputedStyle(e,'');
    if (s) v = s.getPropertyValue(p);
  }
  else if(e.currentStyle) {
    v = e.currentStyle[xCamelize(p)];
  }
  else return null;
  return i ? (parseInt(v) || 0) : v;
}

function xSlideTo(e, x, y, uTime)
{
  if (!(e=xGetElementById(e))) return;
  if (!e.timeout) e.timeout = 25;
  e.xTarget = x; e.yTarget = y; e.slideTime = uTime; e.stop = false;
  e.yA = e.yTarget - xTop(e); e.xA = e.xTarget - xLeft(e); // A = distance
  if (e.slideLinear) e.B = 1/e.slideTime;
  else e.B = Math.PI / (2 * e.slideTime); // B = period
  e.yD = xTop(e); e.xD = xLeft(e); // D = initial position
  var d = new Date(); e.C = d.getTime();
  if (!e.moving) _xSlideTo(e);
}
function _xSlideTo(e)
{
  if (!(e=xGetElementById(e))) return;
  var now, s, t, newY, newX;
  now = new Date();
  t = now.getTime() - e.C;
  if (e.stop) { e.moving = false; }
  else if (t < e.slideTime) {
    setTimeout("_xSlideTo('"+e.id+"')", e.timeout);

    s = e.B * t;
    if (!e.slideLinear) s = Math.sin(s);
//    if (e.slideLinear) s = e.B * t;
//    else s = Math.sin(e.B * t);

    newX = Math.round(e.xA * s + e.xD);
    newY = Math.round(e.yA * s + e.yD);
    xMoveTo(e, newX, newY);
    e.moving = true;
  }
  else {
    xMoveTo(e, e.xTarget, e.yTarget);
    e.moving = false;
    if (e.onslideend) e.onslideend();
  }
}
function xClientWidth(){
  var v=0,d=document,w=window;
  if((!d.compatMode || d.compatMode == 'CSS1Compat') && !w.opera && d.documentElement && d.documentElement.clientWidth)
    {v=d.documentElement.clientWidth;}
  else if(d.body && d.body.clientWidth)
    {v=d.body.clientWidth;}
  else if(xDef(w.innerWidth,w.innerHeight,d.height)) {
    v=w.innerWidth;
    if(d.height>w.innerHeight) v-=16;
  }
  return v;
}
// xParent r1, Copyright 2001-2007 Michael Foster (Cross-Browser.com)
// Part of X, a Cross-Browser Javascript Library, Distributed under the terms of the GNU LGPL

function xParent(e, bNode)
{
  if (!(e=xGetElementById(e))) return null;
  var p=null;
  if (!bNode && xDef(e.offsetParent)) p=e.offsetParent;
  else if (xDef(e.parentNode)) p=e.parentNode;
  else if (xDef(e.parentElement)) p=e.parentElement;
  return p;
}

function xWidth(e,w)
{
  if(!(e=xGetElementById(e))) return 0;
  if (xNum(w)) {
    if (w<0) w = 0;
    else w=Math.round(w);
  }
  else w=-1;
  var css=xDef(e.style);
  if (e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    w = xClientWidth();
  }
  else if(css && xDef(e.offsetWidth) && xStr(e.style.width)) {
    if(w>=0) {
      var pl=0,pr=0,bl=0,br=0;
      if (document.compatMode=='CSS1Compat') {
        var gcs = xGetComputedStyle;
        pl=gcs(e,'padding-left',1);
        if (pl !== null) {
          pr=gcs(e,'padding-right',1);
          bl=gcs(e,'border-left-width',1);
          br=gcs(e,'border-right-width',1);
        }
        // Should we try this as a last resort?
        // At this point getComputedStyle and currentStyle do not exist.
        else if(xDef(e.offsetWidth,e.style.width)){
          e.style.width=w+'px';
          pl=e.offsetWidth-w;
        }
      }
      w-=(pl+pr+bl+br);
      if(isNaN(w)||w<0) return 0;
      else e.style.width=w+'px';
    }
    w=e.offsetWidth;
  }
  else if(css && xDef(e.style.pixelWidth)) {
    if(w>=0) e.style.pixelWidth=w;
    w=e.style.pixelWidth;
  }
  return w;
}

function xCamelize(cssPropStr) {
  var i, c, a = cssPropStr.split('-');
  var s = a[0];
  for (i=1; i<a.length; ++i) {
    c = a[i].charAt(0);
    s += a[i].replace(c, c.toUpperCase());
  }
  return s;
}

function SoundManager(smURL,smID) {
  var self = this;
  this.version = 'V2.0b.20070415';
  this.url = (smURL||'soundmanager2.swf');

  this.debugMode = true;           // enable debugging output (div#soundmanager-debug, OR console if available + configured)
  this.useConsole = true;          // use firebug/safari console.log()-type debug console if available
  this.consoleOnly = false;        // if console is being used, do not create/write to #soundmanager-debug
  this.nullURL = 'null.mp3';  // path to "null" (empty) MP3 file, used to unload sounds

  this.defaultOptions = {
    'autoLoad': false,             // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)
    'stream': true,                // allows playing before entire file has loaded (recommended)
    'autoPlay': false,             // enable playing of file as soon as possible (much faster if "stream" is true)
    'onid3': null,                 // callback function for "ID3 data is added/available"
    'onload': null,                // callback function for "load finished"
    'whileloading': null,          // callback function for "download progress update" (X of Y bytes received)
    'onplay': null,                // callback for "play" start
    'whileplaying': null,          // callback during play (position update)
    'onstop': null,                // callback for "user stop"
    'onfinish': null,              // callback function for "sound finished playing"
    'onbeforefinish': null,        // callback for "before sound finished playing (at [time])"
    'onbeforefinishtime': 5000,    // offset (milliseconds) before end of sound to trigger beforefinish (eg. 1000 msec = 1 second)
    'onbeforefinishcomplete':null, // function to call when said sound finishes playing
    'onjustbeforefinish':null,     // callback for [n] msec before end of current sound
    'onjustbeforefinishtime':200,  // [n] - if not using, set to 0 (or null handler) and event will not fire.
    'multiShot': true,             // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
    'pan': 0,                      // "pan" settings, left-to-right, -100 to 100
    'volume': 100                  // self-explanatory. 0-100, the latter being the max.
  }

  this.allowPolling = true;        // allow flash to poll for status update (required for "while playing", "progress" etc. to work.)
  this.enabled = false;
  this.o = null;
  this.id = (smID||'sm2movie');
  this.oMC = null;
  this.sounds = [];
  this.soundIDs = [];
  this.isIE = (navigator.userAgent.match(/MSIE/));
  this.isSafari = (navigator.userAgent.match(/safari/i));
  this.debugID = 'soundmanager-debug';
  this._debugOpen = true;
  this._didAppend = false;
  this._appendSuccess = false;
  this._didInit = false;
  this._disabled = false;
  this._hasConsole = (typeof console != 'undefined' && typeof console.log != 'undefined');
  this._debugLevels = !self.isSafari?['debug','info','warn','error']:['log','log','log','log'];

  // --- public methods ---

  this.getMovie = function(smID) {
    // return self.isIE?window[smID]:document[smID];
    return self.isIE?window[smID]:(self.isSafari?document[smID+'-embed']:document.getElementById(smID+'-embed'));
  }

  this.loadFromXML = function(sXmlUrl) {
    try {
      self.o._loadFromXML(sXmlUrl);
    } catch(e) {
      self._failSafely();
      return true;
    }
    return true;
  }

  this.createSound = function(oOptions) {
    if (!self._didInit) throw new Error('soundManager.createSound(): Not loaded yet - wait for soundManager.onload() before calling sound-related methods');
    if (arguments.length==2) {
      // function overloading in JS! :) ..assume simple createSound(id,url) use case
      oOptions = {'id':arguments[0],'url':arguments[1]}
    }
    var thisOptions = self._mergeObjects(oOptions);
    self._writeDebug('soundManager.createSound(): "<a href="#" onclick="soundManager.play(\''+thisOptions.id+'\');return false" title="play this sound">'+thisOptions.id+'</a>" ('+thisOptions.url+')',1);
    if (self._idCheck(thisOptions.id,true)) {
      self._writeDebug('sound '+thisOptions.id+' already defined - exiting',2);
      return false;
    }
    self.sounds[thisOptions.id] = new SMSound(self,thisOptions);
    self.soundIDs[self.soundIDs.length] = thisOptions.id;
    try {
      self.o._createSound(thisOptions.id,thisOptions.onjustbeforefinishtime);
    } catch(e) {
      self._failSafely();
      return true;
    }
    if (thisOptions.autoLoad || thisOptions.autoPlay) self.sounds[thisOptions.id].load(thisOptions);
    if (thisOptions.autoPlay) self.sounds[thisOptions.id].playState = 1; // we can only assume this sound will be playing soon.
    return true;
  }

  this.destroySound = function(sID) {
    // explicitly destroy a sound before normal page unload, etc.
    if (!self._idCheck(sID)) return false;
    for (var i=self.soundIDs.length; i--;) {
      if (self.soundIDs[i] == sID) {
        delete self.soundIDs[i];
        continue;
      }
    }
    self.sounds[sID].unload();
    delete self.sounds[sID];
    return true;
  }

  this.load = function(sID,oOptions) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].load(oOptions);
    return true;
  }

  this.unload = function(sID) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].unload();
    return true;
  }

  this.play = function(sID,oOptions) {
    if (!self._idCheck(sID)) {
      if (typeof oOptions != 'Object') oOptions = {url:oOptions}; // overloading use case: play('mySound','/path/to/some.mp3');
      if (oOptions && oOptions.url) {
        // overloading use case, creation + playing of sound: .play('someID',{url:'/path/to.mp3'});
        self._writeDebug('soundController.play(): attempting to create "'+sID+'"',1);
        oOptions.id = sID;
        self.createSound(oOptions);
      } else {
        return false;
      }
    }
    self.sounds[sID].play(oOptions);
    return true;
  }

  this.start = this.play; // just for convenience

  this.setPosition = function(sID,nMsecOffset) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].setPosition(nMsecOffset);
    return true;
  }

  this.stop = function(sID) {
    if (!self._idCheck(sID)) return false;
    self._writeDebug('soundManager.stop('+sID+')',1);
    self.sounds[sID].stop();
    return true;
  }

  this.stopAll = function() {
    self._writeDebug('soundManager.stopAll()',1);
    for (var oSound in self.sounds) {
      if (self.sounds[oSound] instanceof SMSound) self.sounds[oSound].stop(); // apply only to sound objects
    }
    return true;
  }

  this.pause = function(sID) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].pause();
    return true;
  }

  this.resume = function(sID) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].resume();
    return true;
  }

  this.togglePause = function(sID) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].togglePause();
    return true;
  }

  this.setPan = function(sID,nPan) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].setPan(nPan);
    return true;
  }

  this.setVolume = function(sID,nVol) {
    if (!self._idCheck(sID)) return false;
    self.sounds[sID].setVolume(nVol);
    return true;
  }

  this.setPolling = function(bPolling) {
    if (!self.o || !self.allowPolling) return false;
    self._writeDebug('soundManager.setPolling('+bPolling+')');
    self.o._setPolling(bPolling);
    return true;
  }

  this.disable = function() {
    // destroy all functions
    if (self._disabled) return false;
    self._disabled = true;
    self._writeDebug('soundManager.disable(): Disabling all functions - future calls will return false.',1);
    for (var i=self.soundIDs.length; i--;) {
      self._disableObject(self.sounds[self.soundIDs[i]]);
    }
    self.initComplete(); // fire "complete", despite fail
    self._disableObject(self);
    return true;
  }

  this.getSoundById = function(sID,suppressDebug) {
    if (!sID) throw new Error('SoundManager.getSoundById(): sID is null/undefined');
    var result = self.sounds[sID];
    if (!result && !suppressDebug) {
      self._writeDebug('"'+sID+'" is an invalid sound ID.',2);
      // soundManager._writeDebug('trace: '+arguments.callee.caller);
    }
    return result;
  }

  this.onload = function() {
    // window.onload() equivalent for SM2, ready to create sounds etc.
    // this is a stub - you can override this in your own external script, eg. soundManager.onload = function() {}
    soundManager._writeDebug('<em>Warning</em>: soundManager.onload() is undefined.',2);
  }

  this.onerror = function() {
    // stub for user handler, called when SM2 fails to load/init
  }

  // --- "private" methods ---

  this._idCheck = this.getSoundById;

  this._disableObject = function(o) {
    for (var oProp in o) {
      if (typeof o[oProp] == 'function' && typeof o[oProp]._protected == 'undefined') o[oProp] = function(){return false;}
    }
    oProp = null;
  }

  this._failSafely = function() {
    // exception handler for "object doesn't support this property or method"
    var flashCPLink = 'http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html';
    var fpgssTitle = 'You may need to whitelist this location/domain eg. file:///C:/ or C:/ or mysite.com, or set ALWAYS ALLOW under the Flash Player Global Security Settings page. Note that this seems to apply only to file system viewing.';
    var flashCPL = '<a href="'+flashCPLink+'" title="'+fpgssTitle+'">view/edit</a>';
    var FPGSS = '<a href="'+flashCPLink+'" title="Flash Player Global Security Settings">FPGSS</a>';
    if (!self._disabled) {
      self._writeDebug('soundManager: JS-&gt;Flash communication failed. Possible causes: flash/browser security restrictions ('+flashCPL+'), insufficient browser/plugin support, or .swf not found',2);
      self._writeDebug('Verify that the movie path of <em>'+self.url+'</em> is correct (<a href="'+self.url+'" title="If you get a 404/not found, fix it!">test link</a>)',1);
      if (self._didAppend) {
        if (!document.domain) {
          self._writeDebug('Loading from local file system? (document.domain appears to be null, this URL path may need to be added to \'trusted locations\' in '+FPGSS+')',1);
          self._writeDebug('Possible security/domain restrictions ('+flashCPL+'), should work when served by http on same domain',1);
        }
        // self._writeDebug('Note: Movie added via JS method, static object/embed in-page markup method may work instead.');
      }
      self.disable();
    }
  }

  this._createMovie = function(smID,smURL) {
    if (self._didAppend && self._appendSuccess) return false; // ignore if already succeeded
    if (window.location.href.indexOf('debug=1')+1) self.debugMode = true; // allow force of debug mode via URL
    self._didAppend = true;
    var html = ['<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="16" height="16" id="'+smID+'"><param name="movie" value="'+smURL+'"><param name="quality" value="high"><param name="allowScriptAccess" value="always" /></object>','<embed name="'+smID+'-embed" id="'+smID+'-embed" src="'+smURL+'" width="1" height="1" quality="high" allowScriptAccess="always" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed>'];
    var toggleElement = '<div id="'+self.debugID+'-toggle" style="position:fixed;_position:absolute;right:0px;bottom:0px;_top:0px;width:1.2em;height:1.2em;line-height:1.2em;margin:2px;padding:0px;text-align:center;border:1px solid #999;cursor:pointer;background:#fff;color:#333;z-index:706" title="Toggle SM2 debug console" onclick="soundManager._toggleDebug()">-</div>';
    var debugHTML = '<div id="'+self.debugID+'" style="display:'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly))?'block':'none')+';opacity:0.85"></div>';
    var appXHTML = 'soundManager._createMovie(): appendChild/innerHTML set failed. Serving application/xhtml+xml MIME type? Browser may be enforcing strict rules, not allowing write to innerHTML. (PS: If so, this means your commitment to XML validation is going to break stuff now, because this part isn\'t finished yet. ;))';

    var sHTML = '<div style="position:absolute;left:-256px;top:-256px;width:1px;height:1px" class="movieContainer">'+html[self.isIE?0:1]+'</div>'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly)) && !document.getElementById(self.debugID)?'x'+debugHTML+toggleElement:'');

    var oTarget = (document.body?document.body:document.getElementsByTagName('div')[0]);
    if (oTarget) {
      self.oMC = document.createElement('div');
      self.oMC.className = 'movieContainer';
      // "hide" flash movie
      self.oMC.style.position = 'absolute';
      self.oMC.style.left = '-256px';
      self.oMC.style.width = '1px';
      self.oMC.style.height = '1px';
      try {
        oTarget.appendChild(self.oMC);
        self.oMC.innerHTML = html[self.isIE?0:1];
        self._appendSuccess = true;
      } catch(e) {
        // may fail under app/xhtml+xml - has yet to be tested
        throw new Error(appXHTML);
      }
      if (!document.getElementById(self.debugID) && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly))) {
        var oDebug = document.createElement('div');
        oDebug.id = self.debugID;
        oDebug.style.display = (self.debugMode?'block':'none');
        if (self.debugMode) {
          try {
            var oD = document.createElement('div');
            oTarget.appendChild(oD);
            oD.innerHTML = toggleElement;
          } catch(e) {
            throw new Error(appXHTML);
          }
        }
        oTarget.appendChild(oDebug);
      }
      oTarget = null;
    }
    self._writeDebug('-- SoundManager 2 Version '+self.version.substr(1)+' --',1);
    self._writeDebug('soundManager._createMovie(): trying to load <a href="'+smURL+'" title="Test this link (404=bad)">'+smURL+'</a>',1);
    return true;
  }

  this._writeDebug = function(sText,sType) {
    if (!self.debugMode) return false;
    if (self._hasConsole && self.useConsole) {
      console[self._debugLevels[sType]||'log'](sText); // firebug et al
      if (self.useConsoleOnly) return true;
    }
    var sDID = 'soundmanager-debug';
    try {
      var o = document.getElementById(sDID);
      if (!o) return false;
      var p = document.createElement('div');
      p.innerHTML = sText;
      // o.appendChild(p); // top-to-bottom
      o.insertBefore(p,o.firstChild); // bottom-to-top
    } catch(e) {
      // oh well
    }
    o = null;
    return true;
  }
  this._writeDebug._protected = true;

  this._writeDebugAlert = function(sText) { alert(sText); }

  if (window.location.href.indexOf('debug=alert')+1) {
    self.debugMode = true;
    self._writeDebug = self._writeDebugAlert;
  }

  this._toggleDebug = function() {
    var o = document.getElementById(self.debugID);
    var oT = document.getElementById(self.debugID+'-toggle');
    if (!o) return false;
    if (self._debugOpen) {
      // minimize
      oT.innerHTML = '+';
      o.style.display = 'none';
    } else {
      oT.innerHTML = '-';
      o.style.display = 'block';
    }
    self._debugOpen = !self._debugOpen;
    return true;
  }

  this._toggleDebug._protected = true;

  this._debug = function() {
    self._writeDebug('soundManager._debug(): sounds by id/url:',0);
    for (var i=0,j=self.soundIDs.length; i<j; i++) {
      self._writeDebug(self.sounds[self.soundIDs[i]].sID+' | '+self.sounds[self.soundIDs[i]].url,0);
    }
  }

  this._mergeObjects = function(oMain,oAdd) {
    // non-destructive merge
    var o1 = oMain;
    var o2 = (typeof oAdd == 'undefined'?self.defaultOptions:oAdd);
    for (var o in o2) {
      if (typeof o1[o] == 'undefined') o1[o] = o2[o];
    }
    return o1;
  }

  this.createMovie = function(sURL) {
    if (sURL) self.url = sURL;
    self._initMovie();
  }

  this._initMovie = function() {
    // attempt to get, or create, movie
    if (self.o) return false; // pre-init may have fired this function before window.onload(), may already exist
    self.o = self.getMovie(self.id); // try to get flash movie (inline markup)
    if (!self.o) {
      // try to create
      self._createMovie(self.id,self.url);
      self.o = self.getMovie(self.id);
    }
    if (self.o) {
      self._writeDebug('soundManager._initMovie(): Got '+self.o.nodeName+' element ('+(self._didAppend?'created via JS':'static HTML')+')',1);
    }
    return true;
  }

  this.initComplete = function() {
    if (self._didInit) return false;
    self._didInit = true;
    self._writeDebug('-- SoundManager 2 '+(self._disabled?'failed to load':'loaded')+' ('+(self._disabled?'security/load error':'OK')+') --',1);
    if (self._disabled) {
      self._writeDebug('soundManager.initComplete(): calling soundManager.onerror()',1);
      self.onerror.apply(window);
      return false;
    }
    self._writeDebug('soundManager.initComplete(): calling soundManager.onload()',1);
    try {
      // call user-defined "onload", scoped to window
      self.onload.apply(window);
    } catch(e) {
      // something broke (likely JS error in user function)
      self._writeDebug('soundManager.onload() threw an exception: '+e.message,2);
      throw e; // (so browser/console gets message)
    }
    self._writeDebug('soundManager.onload() complete',1);
    return true;
  }

  this.init = function() {
    // called after onload()
    // self._initMovie();
    // event cleanup
    if (window.removeEventListener) {
      window.removeEventListener('load',self.beginInit,false);
    } else if (window.detachEvent) {
      window.detachEvent('onload',self.beginInit);
    }
    try {
      self.o._externalInterfaceTest(); // attempt to talk to Flash
      self._writeDebug('Flash ExternalInterface call (JS -&gt; Flash) succeeded.',1);
      if (!self.allowPolling) self._writeDebug('Polling (whileloading/whileplaying support) is disabled.',1);
      self.setPolling(true);
      self.enabled = true;
    }  catch(e) {
      self._failSafely();
      self.initComplete();
      return false;
    }
    self.initComplete();
    return true;
  }

  this.beginDelayedInit = function() {
    setTimeout(self.beginInit,200);
  }

  this.beginInit = function() {
    self.createMovie(); // ensure creation if not already done
    self._initMovie();
    setTimeout(self.init,1000); // some delay required, otherwise JS<->Flash/ExternalInterface communication fails under non-IE (?!)
  }

  this.destruct = function() {
    if (self.isSafari) {
      for (var i=self.soundIDs.length; i--;) {
        if (self.sounds[self.soundIDs[i]].readyState == 1) self.sounds[self.soundIDs[i]].unload();
      }
    }
    self.disable();
    // self.o = null;
    // self.oMC = null;
  }

}

function SMSound(oSM,oOptions) {
  var self = this;
  var sm = oSM;
  this.sID = oOptions.id;
  this.url = oOptions.url;
  this.options = sm._mergeObjects(oOptions);
  this.id3 = {
  }

  self.resetProperties = function(bLoaded) {
    self.bytesLoaded = null;
    self.bytesTotal = null;
    self.position = null;
    self.duration = null;
    self.durationEstimate = null;
    self.loaded = false;
    self.loadSuccess = null;
    self.playState = 0;
    self.paused = false;
    self.readyState = 0; // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
    self.didBeforeFinish = false;
    self.didJustBeforeFinish = false;
  }

  self.resetProperties();

  // --- public methods ---

  this.load = function(oOptions) {
    self.loaded = false;
    self.loadSuccess = null;
    self.readyState = 1;
    self.playState = (oOptions.autoPlay||false); // if autoPlay, assume "playing" is true (no way to detect when it actually starts in Flash unless onPlay is watched?)
    var thisOptions = sm._mergeObjects(oOptions);
    if (typeof thisOptions.url == 'undefined') thisOptions.url = self.url;
    try {
      sm._writeDebug('loading '+thisOptions.url,1);
      sm.o._load(self.sID,thisOptions.url,thisOptions.stream,thisOptions.autoPlay,thisOptions.whileloading?1:0);
    } catch(e) {
      sm._writeDebug('SMSound().load(): JS-&gt;Flash communication failed.',2);
    }
  }

  this.unload = function() {
    // Flash 8/AS2 can't "close" a stream - fake it by loading an empty MP3
    sm._writeDebug('SMSound().unload(): "'+self.sID+'"');
    self.setPosition(0); // reset current sound positioning
    sm.o._unload(self.sID,sm.nullURL);
    // reset load/status flags
    self.resetProperties();
  }

  this.play = function(oOptions) {
    if (!oOptions) oOptions = {};

    // --- TODO: make event handlers specified via oOptions only apply to this instance of play() (eg. onfinish applies but will reset to default on finish)
    if (oOptions.onfinish) self.options.onfinish = oOptions.onfinish;
    if (oOptions.onbeforefinish) self.options.onbeforefinish = oOptions.onbeforefinish;
    if (oOptions.onjustbeforefinish) self.options.onjustbeforefinish = oOptions.onjustbeforefinish;
    // ---

    var thisOptions = sm._mergeObjects(oOptions);
    if (self.playState == 1) {
      // var allowMulti = typeof oOptions.multiShot!='undefined'?oOptions.multiShot:sm.defaultOptions.multiShot;
      var allowMulti = thisOptions.multiShot;
      if (!allowMulti) {
        sm._writeDebug('SMSound.play(): "'+self.sID+'" already playing? (one-shot)',1);
        return false;
      } else {
        sm._writeDebug('SMSound.play(): "'+self.sID+'" already playing (multi-shot)',1);
      }
    }
    if (!self.loaded) {
      if (self.readyState == 0) {
        sm._writeDebug('SMSound.play(): .play() before load request. Attempting to load "'+self.sID+'"',1);
        // try to get this sound playing ASAP
        thisOptions.stream = true;
        thisOptions.autoPlay = true;
        // TODO: need to investigate when false, double-playing
        // if (typeof oOptions.autoPlay=='undefined') thisOptions.autoPlay = true; // only set autoPlay if unspecified here
        self.load(thisOptions); // try to get this sound playing ASAP
      } else if (self.readyState == 2) {
        sm._writeDebug('SMSound.play(): Could not load "'+self.sID+'" - exiting',2);
        return false;
      } else {
        sm._writeDebug('SMSound.play(): "'+self.sID+'" is loading - attempting to play..',1);
      }
    } else {
      sm._writeDebug('SMSound.play(): "'+self.sID+'"');
    }
    if (self.paused) {
      self.resume();
    } else {
      self.playState = 1;
      self.position = (thisOptions.offset||0);
      if (thisOptions.onplay) thisOptions.onplay.apply(self);
      self.setVolume(thisOptions.volume);
      self.setPan(thisOptions.pan);
      if (!thisOptions.autoPlay) {
        // sm._writeDebug('starting sound '+self.sID);
        sm.o._start(self.sID,thisOptions.loop||1,self.position); // TODO: verify !autoPlay doesn't cause issue
      }
    }
    return true;
  }

  this.start = this.play; // just for convenience

  this.stop = function(bAll) {
    if (self.playState == 1) {
      self.playState = 0;
      self.paused = false;
      if (sm.defaultOptions.onstop) sm.defaultOptions.onstop.apply(self);
      sm.o._stop(self.sID);
    }
  }

  this.setPosition = function(nMsecOffset) {
    // sm._writeDebug('setPosition('+nMsecOffset+')');
    sm.o._setPosition(self.sID,nMsecOffset/1000,self.paused||!self.playState); // if paused or not playing, will not resume (by playing)
  }

  this.pause = function() {
    if (self.paused) return false;
    sm._writeDebug('SMSound.pause()');
    self.paused = true;
    sm.o._pause(self.sID);
    return true;
  }

  this.resume = function() {
    if (!self.paused) return false;
    sm._writeDebug('SMSound.resume()');
    self.paused = false;
    sm.o._pause(self.sID); // flash method is toggle-based (pause/resume)
    return true;
  }

  this.togglePause = function() {
    // if playing, pauses - if paused, resumes playing.
    sm._writeDebug('SMSound.togglePause()');
    if (!self.playState) {
      // self.setPosition();
      self.play({offset:self.position/1000});
      return false;
    }
    if (self.paused) {
      sm._writeDebug('SMSound.togglePause(): resuming..');
      self.resume();
    } else {
      sm._writeDebug('SMSound.togglePause(): pausing..');
      self.pause();
    }
    return true;
  }

  this.setPan = function(nPan) {
    if (typeof nPan == 'undefined') nPan = 0;
    sm.o._setPan(self.sID,nPan);
    self.options.pan = nPan;
  }

  this.setVolume = function(nVol) {
    if (typeof nVol == 'undefined') nVol = 100;
    sm.o._setVolume(self.sID,nVol);
    self.options.volume = nVol;
  }

  // --- "private" methods called by Flash ---

  this._whileloading = function(nBytesLoaded,nBytesTotal,nDuration) {
    self.bytesLoaded = nBytesLoaded;
    self.bytesTotal = nBytesTotal;
    self.duration = nDuration;
    self.durationEstimate = parseInt((self.bytesTotal/self.bytesLoaded)*self.duration); // estimate total time (will only be accurate with CBR MP3s.)
    if (self.readyState != 3 && self.options.whileloading) self.options.whileloading.apply(self);
    // soundManager._writeDebug('duration/durationEst: '+self.duration+' / '+self.durationEstimate);
  }

  this._onid3 = function(oID3PropNames,oID3Data) {
    // oID3PropNames: string array (names)
    // ID3Data: string array (data)
    sm._writeDebug('SMSound()._onid3(): "'+this.sID+'" ID3 data received.');
    var oData = [];
    for (var i=0,j=oID3PropNames.length; i<j; i++) {
      oData[oID3PropNames[i]] = oID3Data[i];
      // sm._writeDebug(oID3PropNames[i]+': '+oID3Data[i]);
    }
    self.id3 = sm._mergeObjects(self.id3,oData);
    if (self.options.onid3) self.options.onid3.apply(self);
  }

  this._whileplaying = function(nPosition) {
    if (isNaN(nPosition) || nPosition == null) return false; // Flash may return NaN at times
    self.position = nPosition;
    if (self.playState == 1) {
      if (self.options.whileplaying) self.options.whileplaying.apply(self); // flash may call after actual finish
      if (self.loaded && self.options.onbeforefinish && self.options.onbeforefinishtime && !self.didBeforeFinish && self.duration-self.position <= self.options.onbeforefinishtime) {
        sm._writeDebug('duration-position &lt;= onbeforefinishtime: '+self.duration+' - '+self.position+' &lt= '+self.options.onbeforefinishtime+' ('+(self.duration-self.position)+')');
        self._onbeforefinish();
      }
    }
    return true;
  }

  this._onload = function(bSuccess) {
    bSuccess = (bSuccess==1?true:false);
    sm._writeDebug('SMSound._onload(): "'+self.sID+'"'+(bSuccess?' loaded.':' failed to load (or loaded from cache - weird bug) - [<a href="'+self.url+'">test URL</a>]'));
    self.loaded = bSuccess;
    self.loadSuccess = bSuccess;
    self.readyState = bSuccess?3:2;
    if (self.options.onload) self.options.onload.apply(self);
  }

  this._onbeforefinish = function() {
    if (!self.didBeforeFinish) {
      self.didBeforeFinish = true;
      if (self.options.onbeforefinish) self.options.onbeforefinish.apply(self);
    }
  }

  this._onjustbeforefinish = function(msOffset) {
    // msOffset: "end of sound" delay actual value (eg. 200 msec, value at event fire time was 187)
    if (!self.didJustBeforeFinish) {
      self.didJustBeforeFinish = true;
      // soundManager._writeDebug('SMSound._onjustbeforefinish()');
      if (self.options.onjustbeforefinish) self.options.onjustbeforefinish.apply(self);;
    }
  }

  this._onfinish = function() {
    // sound has finished playing
    sm._writeDebug('SMSound._onfinish(): "'+self.sID+'"');
    self.playState = 0;
    self.paused = false;
    if (self.options.onfinish) self.options.onfinish.apply(self);
    if (self.options.onbeforefinishcomplete) self.options.onbeforefinishcomplete.apply(self);
    // reset some state items
    self.setPosition(0);
    self.didBeforeFinish = false;
    self.didJustBeforeFinish = false;
  }

}

var soundManager = new SoundManager();

// attach onload handler
if (window.addEventListener) {
  window.addEventListener('load',soundManager.beginDelayedInit,false);
  window.addEventListener('beforeunload',soundManager.destruct,false);
} else if (window.attachEvent) {
  window.attachEvent('onload',soundManager.beginInit);
  window.attachEvent('beforeunload',soundManager.destruct);
} else {
  // no add/attachevent support - safe to assume no JS->Flash either.
  soundManager.onerror();
  soundManager.disable();
}