/* slidy.js

   Copyright (c) 2005-2013 W3C (MIT, ERCIM, Keio), All Rights Reserved.
   W3C liability, trademark, document use and software licensing
   rules apply, see:

   http://www.w3.org/Consortium/Legal/copyright-documents
   http://www.w3.org/Consortium/Legal/copyright-software

   Defines single name "w3c_slidy" in global namespace
   Adds event handlers without trampling on any others
*/

// the slidy object implementation
var w3c_slidy = {
  // classify which kind of browser we're running under
  ns_pos: (typeof window.pageYOffset!='undefined'),
  khtml: ((navigator.userAgent).indexOf("KHTML") >= 0 ? true : false),
  opera: ((navigator.userAgent).indexOf("Opera") >= 0 ? true : false),
  ipad: ((navigator.userAgent).indexOf("iPad") >= 0 ? true : false),
  iphone: ((navigator.userAgent).indexOf("iPhone") >= 0 ? true : false),
  android: ((navigator.userAgent).indexOf("Android") >= 0 ? true : false),
  ie: (typeof document.all != "undefined" && !this.opera),
  ie6: (!this.ns_pos && navigator.userAgent.indexOf("MSIE 6") != -1),
  ie7: (!this.ns_pos && navigator.userAgent.indexOf("MSIE 7") != -1),
  ie8: (!this.ns_pos && navigator.userAgent.indexOf("MSIE 8") != -1),
  ie9: (!this.ns_pos && navigator.userAgent.indexOf("MSIE 9") != -1),

  // data for swipe and double tap detection on touch screens
  last_tap: 0,
  prev_tap: 0,
  start_x: 0,
  start_y: 0,
  delta_x: 0,
  delta_y: 0,

  // are we running as XHTML? (doesn't work on Opera)
  is_xhtml: /xml/.test(document.contentType),

  slide_number: 0, // integer slide count: 0, 1, 2, ...
  slide_number_element: null, // element containing slide number
  slides: [], // set to array of slide div's
  notes: [], // set to array of handout div's
  backgrounds: [], // set to array of background div's
  observers: [], // list of observer functions
  toolbar: null, // element containing toolbar
  title: null, // document title
  last_shown: null, // last incrementally shown item
  eos: null,  // span element for end of slide indicator
  toc: null, // table of contents
  outline: null, // outline element with the focus
  selected_text_len: 0, // length of drag selection on document
  view_all: 0,  // 1 to view all slides + handouts
  want_toolbar: true,  // user preference to show/hide toolbar
  mouse_click_enabled: true, // enables left click for next slide
  scroll_hack: 0, // IE work around for position: fixed
  disable_slide_click: false,  // used by clicked anchors

  lang: "en", // updated to language specified by html file

  help_anchor: null, // used for keyboard focus hack in showToolbar()
  help_page: "http://www.w3.org/Talks/Tools/Slidy2/help/help.html",
  help_text: "Navigate with mouse click, space bar, Cursor Left/Right, " +
             "or Pg Up and Pg Dn. Use S and B to change font size.",

  size_index: 0,
  size_adjustment: 0,
  sizes:  new Array("10pt", "12pt", "14pt", "16pt", "18pt", "20pt",
                    "22pt", "24pt", "26pt", "28pt", "30pt", "32pt"),

  // needed for efficient resizing
  last_width: 0,
  last_height: 0,


  // Needed for cross browser support for relative width/height on
  // object elements. The work around is to save width/height attributes
  // and then to recompute absolute width/height dimensions on resizing
   objects: [],

  // attach initialiation event handlers
  set_up: function () {
    var init = function() { w3c_slidy.init(); };
    if (typeof window.addEventListener != "undefined")
      window.addEventListener("load", init, false);
    else
      window.attachEvent("onload", init);
  },

  hide_slides: function () {
    if (document.body && !w3c_slidy.initialized)
      document.body.style.visibility = "hidden";
    else
      setTimeout(w3c_slidy.hide_slides, 50);
  },

  // hack to persuade IE to compute correct document height
  // as needed for simulating fixed positioning of toolbar
  ie_hack: function () {
    window.resizeBy(0,-1);
    window.resizeBy(0, 1);
  },

  init: function () {
    //alert("slidy starting test 10");
    document.body.style.visibility = "visible";
    this.init_localization();
    this.add_toolbar();
    this.wrap_implicit_slides();
    this.collect_slides();
    this.collect_notes();
    this.collect_backgrounds();
    this.objects = document.body.getElementsByTagName("object");
    this.patch_anchors();
    this.slide_number = this.find_slide_number(location.href);
    window.offscreenbuffering = true;
    this.size_adjustment = this.find_size_adjust();
    this.time_left = this.find_duration();
    this.hide_image_toolbar();  // suppress IE image toolbar popup
    this.init_outliner();  // activate fold/unfold support
    this.title = document.title;
    this.keyboardless = (this.ipad||this.iphone||this.android);

    if (this.keyboardless)
    {
      w3c_slidy.remove_class(w3c_slidy.toolbar, "hidden")
      this.want_toolbar = 0;
    }

    // work around for opera bug
    this.is_xhtml = (document.body.tagName == "BODY" ? false : true);

    if (this.slides.length > 0)
    {
      var slide = this.slides[this.slide_number];
   
      if (this.slide_number > 0)
      {
        this.set_visibility_all_incremental("visible");
        this.last_shown = this.previous_incremental_item(null);
        this.set_eos_status(true);
      }
      else
      {
        this.last_shown = null;
        this.set_visibility_all_incremental("hidden");
        this.set_eos_status(!this.next_incremental_item(this.last_shown));
      }

      this.set_location();
      this.add_class(this.slides[0], "first-slide");
      w3c_slidy.show_slide(slide);
    }

    this.toc = this.table_of_contents();

    this.add_initial_prompt();

    // bind event handlers without interfering with custom page scripts
    // Tap events behave too weirdly to support clicks reliably on
    // iPhone and iPad, so exclude these from click handler

    if (!this.keyboardless)
    {
      this.add_listener(document.body, "click", this.mouse_button_click);
      this.add_listener(document.body, "mousedown", this.mouse_button_down);
    }

    this.add_listener(document, "keydown", this.key_down);
    this.add_listener(document, "keypress", this.key_press);
    this.add_listener(window, "resize", this.resized);
    this.add_listener(window, "scroll", this.scrolled);
    this.add_listener(window, "unload", this.unloaded);

    this.add_listener(document, "gesturechange", function ()
    {
      return false;
    });

    this.attach_touch_handers(this.slides);

    // this seems to be a debugging hack
    //if (!document.body.onclick)
    //  document.body.onclick = function () { };

    this.single_slide_view();

    //this.set_location();

    this.resized();

    if (this.ie7)
      setTimeout(w3c_slidy.ie_hack, 100);

    this.show_toolbar();

    // for back button detection
    setInterval(function () { w3c_slidy.check_location(); }, 200);
    w3c_slidy.initialized = true;
  },

  // create div element with links to each slide
  table_of_contents: function () {
    var toc = this.create_element("div");
    this.add_class(toc, "slidy_toc hidden");
    //toc.setAttribute("tabindex", "0");

    var heading = this.create_element("div");
    this.add_class(heading, "toc-heading");
    heading.innerHTML = this.localize("Table of Contents");

    toc.appendChild(heading);
    var previous = null;

    for (var i = 0; i < this.slides.length; ++i)
    {
      var title = this.has_class(this.slides[i], "title");
      var num = document.createTextNode((i + 1) + ". ");

      toc.appendChild(num);

      var a = this.create_element("a");
      a.setAttribute("href", "#(" + (i+1) + ")");

      if (title)
        this.add_class(a, "titleslide");

      var name = document.createTextNode(this.slide_name(i));
      a.appendChild(name);
      a.onclick = w3c_slidy.toc_click;
      a.onkeydown = w3c_slidy.toc_key_down;
      a.previous = previous;

      if (previous)
        previous.next = a;

      toc.appendChild(a);

      if (i == 0)
        toc.first = a;

      if (i < this.slides.length - 1)
      {
        var br = this.create_element("br");
        toc.appendChild(br);
      }

      previous = a;
    }

    toc.focus = function () {
      if (this.first)
        this.first.focus();
    }

    toc.onmouseup = w3c_slidy.mouse_button_up;

    toc.onclick = function (e) {
      e||(e=window.event);

      if (w3c_slidy.selected_text_len <= 0)
         w3c_slidy.hide_table_of_contents(true);

      w3c_slidy.stop_propagation(e);
    
      if (e.cancel != undefined)
        e.cancel = true;
      
      if (e.returnValue != undefined)
        e.returnValue = false;
      
      return false;
    };

    document.body.insertBefore(toc, document.body.firstChild);
    return toc;
  },

  is_shown_toc: function () {
    return !w3c_slidy.has_class(w3c_slidy.toc, "hidden");
  },

  show_table_of_contents: function () {
    w3c_slidy.remove_class(w3c_slidy.toc, "hidden");
    var toc = w3c_slidy.toc;
    toc.focus();

    if (w3c_slidy.ie7 && w3c_slidy.slide_number == 0)
      setTimeout(w3c_slidy.ie_hack, 100);
  },

  hide_table_of_contents: function (focus) {
    w3c_slidy.add_class(w3c_slidy.toc, "hidden");

    if (focus && !w3c_slidy.opera &&
        !w3c_slidy.has_class(w3c_slidy.toc, "hidden"))
      w3c_slidy.set_focus();
  },

  toggle_table_of_contents: function () {
    if (w3c_slidy.is_shown_toc())
      w3c_slidy.hide_table_of_contents(true);
    else
      w3c_slidy.show_table_of_contents();
  },

  // called on clicking toc entry
  toc_click: function (e) {
    if (!e)
      e = window.event;

    var target = w3c_slidy.get_target(e);

    if (target && target.nodeType == 1)
    {
      var uri = target.getAttribute("href");

      if (uri)
      {
        //alert("going to " + uri);
        var slide = w3c_slidy.slides[w3c_slidy.slide_number];
        w3c_slidy.hide_slide(slide);
        w3c_slidy.slide_number = w3c_slidy.find_slide_number(uri);
        slide = w3c_slidy.slides[w3c_slidy.slide_number];
        w3c_slidy.last_shown = null;
        w3c_slidy.set_location();
        w3c_slidy.set_visibility_all_incremental("hidden");
        w3c_slidy.set_eos_status(!w3c_slidy.next_incremental_item(w3c_slidy.last_shown));
        w3c_slidy.show_slide(slide);
        //target.focus();

        try
        {
          if (!w3c_slidy.opera)
            w3c_slidy.set_focus();
        }
        catch (e)
        {
        }
      }
    }

    w3c_slidy.hide_table_of_contents(true);
    if (w3c_slidy.ie7) w3c_slidy.ie_hack();
    w3c_slidy.stop_propagation(e);
    return w3c_slidy.cancel(e);
  },

  // called onkeydown for toc entry
  toc_key_down: function (event) {
    var key;

    if (!event)
      var event = window.event;

    // kludge around NS/IE differences 
    if (window.event)
      key = window.event.keyCode;
    else if (event.which)
      key = event.which;
    else
      return true; // Yikes! unknown browser

    // ignore event if key value is zero
    // as for alt on Opera and Konqueror
    if (!key)
      return true;

    // check for concurrent control/command/alt key
    // but are these only present on mouse events?

    if (event.ctrlKey || event.altKey)
      return true;

    if (key == 13)
    {
      var uri = this.getAttribute("href");

      if (uri)
      {
        //alert("going to " + uri);
       var slide = w3c_slidy.slides[w3c_slidy.slide_number];
        w3c_slidy.hide_slide(slide);
        w3c_slidy.slide_number = w3c_slidy.find_slide_number(uri);
        slide = w3c_slidy.slides[w3c_slidy.slide_number];
        w3c_slidy.last_shown = null;
        w3c_slidy.set_location();
        w3c_slidy.set_visibility_all_incremental("hidden");
        w3c_slidy.set_eos_status(!w3c_slidy.next_incremental_item(w3c_slidy.last_shown));
        w3c_slidy.show_slide(slide);
        //target.focus();

        try
        {
          if (!w3c_slidy.opera)
            w3c_slidy.set_focus();
        }
        catch (e)
        {
        }
      }

      w3c_slidy.hide_table_of_contents(true);

      if (self.ie7)
       w3c_slidy.ie_hack();

      return w3c_slidy.cancel(event);
    }

    if (key == 40 && this.next)
    {
      this.next.focus();
      return w3c_slidy.cancel(event);
    }

    if (key == 38 && this.previous)
    {
      this.previous.focus();
      return w3c_slidy.cancel(event);
    }

    return true;
  },

  touchstart: function (e)
  {
    // a double touch often starts with a
    // single touch due to fingers touching
    // down at slightly different times
    // thus avoid calling preventDefault here
    this.prev_tap = this.last_tap;
    this.last_tap = (new Date).getTime();

    var tap_delay = this.last_tap - this.prev_tap;

    if (tap_delay <= 200)
    {
      // double tap
    }

    var touch = e.touches[0];

    this.pageX = touch.pageX;
    this.pageY = touch.pageY;
    this.screenX = touch.screenX;
    this.screenY = touch.screenY;
    this.clientX = touch.clientX;
    this.clientY = touch.clientY;

    this.delta_x = this.delta_y = 0;
  },

  touchmove: function (e)
  {
    // override native gestures for single touch
    if (e.touches.length > 1)
      return;

    e.preventDefault();
    var touch = e.touches[0];
    this.delta_x = touch.pageX - this.pageX;
    this.delta_y = touch.pageY - this.pageY;
  },

  touchend: function (e)
  {
    // default behavior for multi-touch
    if (e.touches.length > 1)
      return;

    var delay = (new Date).getTime() - this.last_tap;
    var dx = this.delta_x;
    var dy = this.delta_y;
    var abs_dx = Math.abs(dx);
    var abs_dy = Math.abs(dy);

    if (delay < 500 && (abs_dx > 100 || abs_dy > 100))
    {
      if (abs_dx > 0.5 * abs_dy)
      {
        e.preventDefault();

        if (dx < 0)
          w3c_slidy.next_slide(true);
        else
          w3c_slidy.previous_slide(true);
      }
      else if (abs_dy > 2 * abs_dx)
      {
        e.preventDefault();
        w3c_slidy.toggle_table_of_contents();
      }
    }
  },

  // ### OBSOLETE ###
  before_print: function () {
    this.show_all_slides();
    this.hide_toolbar();
    alert("before print");
  },

  // ### OBSOLETE ###
  after_print: function () {
    if (!this.view_all)
    {
      this.single_slide_view();
      this.show_toolbar();
    }
    alert("after print");
  },

  // ### OBSOLETE ###
  print_slides: function () {
    this.before_print();
    window.print();
    this.after_print();
  },

  // ### OBSOLETE ?? ###
  toggle_view: function () {
    if (this.view_all)
    {
      this.single_slide_view();
      this.show_toolbar();
      this.view_all = 0;
    }
    else
    {
      this.show_all_slides();
      this.hide_toolbar();
      this.view_all = 1;
    }
  },

  // prepare for printing  ### OBSOLETE ###
  show_all_slides: function () {
    this.remove_class(document.body, "single_slide");
    this.set_visibility_all_incremental("visible");
  },

  // restore after printing  ### OBSOLETE ###
  single_slide_view: function () {
    this.add_class(document.body, "single_slide");
    this.set_visibility_all_incremental("visible");
    this.last_shown = this.previous_incremental_item(null);
  },

  // suppress IE's image toolbar pop up
  hide_image_toolbar: function () {
    if (!this.ns_pos)
    {
      var images = document.getElementsByTagName("IMG");

      for (var i = 0; i < images.length; ++i)
        images[i].setAttribute("galleryimg", "no");
    }
  },

  unloaded: function (e) {
    //alert("unloaded");
  },

  // Safari and Konqueror don't yet support getComputedStyle()
  // and they always reload page when location.href is updated
  is_KHTML: function () {
    var agent = navigator.userAgent;
    return (agent.indexOf("KHTML") >= 0 ? true : false);
  },

  // find slide name from first h1 element
  // default to document title + slide number
  slide_name: function (index) {
    var name = null;
    var slide = this.slides[index];

    var heading = this.find_heading(slide);

    if (heading)
      name = this.extract_text(heading);

    if (!name)
      name = this.title + "(" + (index + 1) + ")";

    name.replace(/\&/g, "&amp;");
    name.replace(/\</g, "&lt;");
    name.replace(/\>/g, "&gt;");

    return name;
  },

  // find first h1 element in DOM tree
  find_heading: function (node) {
    if (!node || node.nodeType != 1)
      return null;

    if (node.nodeName == "H1" || node.nodeName == "h1")
      return node;

    var child = node.firstChild;

    while (child)
    {
      node = this.find_heading(child);

      if (node)
        return node;

      child = child.nextSibling;
    }

    return null;
  },

  // recursively extract text from DOM tree
  extract_text: function (node) {
    if (!node)
      return "";

    // text nodes
    if (node.nodeType == 3)
      return node.nodeValue;

    // elements
    if (node.nodeType == 1)
    {
      node = node.firstChild;
      var text = "";

      while (node)
      {
        text = text + this.extract_text(node);
        node = node.nextSibling;
      }

      return text;
    }

    return "";
  },

  // find copyright text from meta element
  find_copyright: function () {
    var name, content;
    var meta = document.getElementsByTagName("meta");

    for (var i = 0; i < meta.length; ++i)
    {
      name = meta[i].getAttribute("name");
      content = meta[i].getAttribute("content");

      if (name == "copyright")
        return content;
    }

    return null;
  },

  find_size_adjust: function () {
    var name, content, offset;
    var meta = document.getElementsByTagName("meta");

    for (var i = 0; i < meta.length; ++i)
    {
      name = meta[i].getAttribute("name");
      content = meta[i].getAttribute("content");

      if (name == "font-size-adjustment")
        return 1 * content;
    }

    return 1;
  },

  // <meta name="duration" content="20" />  for 20 minutes
  find_duration: function () {
    var name, content, offset;
    var meta = document.getElementsByTagName("meta");

    for (var i = 0; i < meta.length; ++i)
    {
      name = meta[i].getAttribute("name");
      content = meta[i].getAttribute("content");

      if (name == "duration")
        return 60000 * content;
    }

    return null;
  },

  replace_by_non_breaking_space: function (str) {
    for (var i = 0; i < str.length; ++i)
      str[i] = 160;
  },

  // ### CHECK ME ### is use of "li" okay for text/html?
  // for XHTML do we also need to specify namespace?
  init_outliner: function () {
    var items = document.getElementsByTagName("li");

    for (var i = 0; i < items.length; ++i)
    {
      var target = items[i];

      if (!this.has_class(target.parentNode, "outline"))
        continue;

      target.onclick = this.outline_click;
/* ### more work needed for IE6
      if (!this.ns_pos)
      {
        target.onmouseover = this.hover_outline;
        target.onmouseout = this.unhover_outline;
      }
*/
      if (this.foldable(target))
      {
        target.foldable = true;
        target.onfocus = function () {w3c_slidy.outline = this;};
        target.onblur = function () {w3c_slidy.outline = null;};

        if (!target.getAttribute("tabindex"))
          target.setAttribute("tabindex", "0");

        if (this.has_class(target, "expand"))
          this.unfold(target);
        else
          this.fold(target);
      }
      else
      {
        this.add_class(target, "nofold");
        target.visible = true;
        target.foldable = false;
      }
    }
  },

  foldable: function (item) {
    if (!item || item.nodeType != 1)
      return false;

    var node = item.firstChild;

    while (node)
    {
      if (node.nodeType == 1 && this.is_block(node))
        return true;

      node = node.nextSibling;
    }

    return false;
  },

  // ### CHECK ME ### switch to add/remove "hidden" class
  fold: function (item) {
    if (item)
    {
      this.remove_class(item, "unfolded");
      this.add_class(item, "folded");
    }

    var node = item ? item.firstChild : null;

    while (node)
    {
      if (node.nodeType == 1 && this.is_block(node)) // element
      {
         w3c_slidy.add_class(node, "hidden");
      }

      node = node.nextSibling;
    }

    item.visible = false;
  },

  // ### CHECK ME ### switch to add/remove "hidden" class
  unfold: function (item) {
    if (item)
    {
      this.add_class(item, "unfolded");
      this.remove_class(item, "folded");
    }

    var node = item ? item.firstChild : null;

    while (node)
    {
      if (node.nodeType == 1 && this.is_block(node)) // element
      {
        w3c_slidy.remove_class(node, "hidden");
      }

      node = node.nextSibling;
    }

    item.visible = true;
  },

  outline_click: function (e) {
    if (!e)
      e = window.event;

    var rightclick = false;
    var target = w3c_slidy.get_target(e);

    while (target && target.visible == undefined)
      target = target.parentNode;

    if (!target)
      return true;

    if (e.which)
      rightclick = (e.which == 3);
    else if (e.button)
      rightclick = (e.button == 2);

    if (!rightclick && target.visible != undefined)
    {
      if (target.foldable)
      {
        if (target.visible)
          w3c_slidy.fold(target);
        else
          w3c_slidy.unfold(target);
      }

      w3c_slidy.stop_propagation(e);
      e.cancel = true;
      e.returnValue = false;
    }

    return false;
  },

  add_initial_prompt: function () {
    var prompt = this.create_element("div");
    prompt.setAttribute("class", "initial_prompt");

    var p1 = this.create_element("p");
    prompt.appendChild(p1);
    p1.setAttribute("class", "help");

    if (this.keyboardless)
      p1.innerHTML = "swipe left to move to next slide";
    else
      p1.innerHTML = "Space, Right Arrow or swipe left to move to " +
                     "next slide, click help below for more details";

    this.add_listener(prompt, "click", function (e) {
      document.body.removeChild(prompt);
      w3c_slidy.stop_propagation(e);
    
      if (e.cancel != undefined)
        e.cancel = true;
      
      if (e.returnValue != undefined)
        e.returnValue = false;
      
      return false;
    });

    document.body.appendChild(prompt);
    this.initial_prompt = prompt;
    setTimeout(function() {document.body.removeChild(prompt);}, 5000);
  },

  add_toolbar: function () {
    var counter, page;

     this.toolbar = this.create_element("div");
     this.toolbar.setAttribute("class", "toolbar");

     // a reasonably behaved browser
     if (this.ns_pos || !this.ie6)
     {
       var right = this.create_element("div");
       right.setAttribute("style", "float: right; text-align: right");

       counter = this.create_element("span")
       counter.innerHTML = this.localize("slide") + " n/m";
       right.appendChild(counter);
       this.toolbar.appendChild(right);

       var left = this.create_element("div");
       left.setAttribute("style", "text-align: left");

       // global end of slide indicator
       this.eos = this.create_element("span");
       this.eos.innerHTML = "* ";
       left.appendChild(this.eos);

       var help = this.create_element("a");
       help.setAttribute("href", this.help_page);
       help.setAttribute("title", this.localize(this.help_text));
       help.innerHTML = this.localize("help?");
       left.appendChild(help);
       help.style.display="none"; 
       this.help_anchor = help;  // save for focus hack

       var gap1 = document.createTextNode(" ");
       left.appendChild(gap1);

       var contents = this.create_element("a");
       contents.setAttribute("href", "javascript:w3c_slidy.toggle_table_of_contents()");
       contents.setAttribute("title", this.localize("table of contents"));
       contents.innerHTML = this.localize("Contents");
       left.appendChild(contents);

       var gap2 = document.createTextNode(" ");
       left.appendChild(gap2);

       var copyright = this.find_copyright();

       if (copyright)
       {
         var span = this.create_element("span");
         span.className = "copyright";
         span.innerHTML = copyright;
         left.appendChild(span);
       }

       this.toolbar.setAttribute("tabindex", "0");
       this.toolbar.appendChild(left);
     }
     else // IE6 so need to work around its poor CSS support
     {
       this.toolbar.style.position = (this.ie7 ? "fixed" : "absolute");
       this.toolbar.style.zIndex = "200";
       this.toolbar.style.width = "99.9%";
       this.toolbar.style.height = "1.2em";
       this.toolbar.style.top = "auto";
       this.toolbar.style.bottom = "0";
       this.toolbar.style.left = "0";
       this.toolbar.style.right = "0";
       this.toolbar.style.textAlign = "left";
       this.toolbar.style.fontSize = "60%";
       this.toolbar.style.color = "red";
       this.toolbar.borderWidth = 0;
       this.toolbar.className = "toolbar";
       this.toolbar.style.background = "rgb(240,240,240)";

       // would like to have help text left aligned
       // and page counter right aligned, floating
       // div's don't work, so instead use nested
       // absolutely positioned div's.

       var sp = this.create_element("span");
       sp.innerHTML = "&nbsp;&nbsp;*&nbsp;";
       this.toolbar.appendChild(sp);
       this.eos = sp;  // end of slide indicator

       var help = this.create_element("a");
       help.setAttribute("href", this.help_page);
       help.setAttribute("title", this.localize(this.help_text));
       help.innerHTML = this.localize("help?");
       this.toolbar.appendChild(help);
       this.help_anchor = help;  // save for focus hack

       var gap1 = document.createTextNode(" ");
       this.toolbar.appendChild(gap1);

       var contents = this.create_element("a");
       contents.setAttribute("href", "javascript:toggleTableOfContents()");
       contents.setAttribute("title", this.localize("table of contents".localize));
       contents.innerHTML = this.localize("contents?");
       this.toolbar.appendChild(contents);

       var gap2 = document.createTextNode(" ");
       this.toolbar.appendChild(gap2);

       var copyright = this.find_copyright();

       if (copyright)
       {
         var span = this.create_element("span");
         span.innerHTML = copyright;
         span.style.color = "black";
         span.style.marginLeft = "0.5em";
         this.toolbar.appendChild(span);
       }

       counter = this.create_element("div")
       counter.style.position = "absolute";
       counter.style.width = "auto"; //"20%";
       counter.style.height = "1.2em";
       counter.style.top = "auto";
       counter.style.bottom = 0;
       counter.style.right = "0";
       counter.style.textAlign = "right";
       counter.style.color = "red";
       counter.style.background = "rgb(240,240,240)";

       counter.innerHTML = this.localize("slide") + " n/m";
       this.toolbar.appendChild(counter);
     }

     // ensure that click isn't passed through to the page
     this.toolbar.onclick =
         function (e) {
           if (!e)
             e = window.event;

           var target = e.target;

           if (!target && e.srcElement)
             target = e.srcElement;

           // work around Safari bug
           if (target && target.nodeType == 3)
             target = target.parentNode;

           w3c_slidy.stop_propagation(e);

           if (target && target.nodeName.toLowerCase() != "a")
             w3c_slidy.mouse_button_click(e);
         };

     this.slide_number_element = counter;
     this.set_eos_status(false);
     document.body.appendChild(this.toolbar);
  },

  // wysiwyg editors make it hard to use div elements
  // e.g. amaya loses the div when you copy and paste
  // this function wraps div elements around implicit
  // slides which start with an h1 element and continue
  // up to the next heading or div element
  wrap_implicit_slides: function () {
    var i, heading, node, next, div;
    var headings = document.getElementsByTagName("h1");

    if (!headings)
      return;

    for (i = 0; i < headings.length; ++i)
    {
      heading = headings[i];

      if (heading.parentNode != document.body)
        continue;

      node = heading.nextSibling;

      div = document.createElement("div");
      this.add_class(div, "slide");
      document.body.replaceChild(div, heading);
      div.appendChild(heading);

      while (node)
      {
        if (node.nodeType == 1) // an element
        {
           if (node.nodeName == "H1" || node.nodeName == "h1")
             break;

           if (node.nodeName == "DIV" || node.nodeName == "div")
           {
             if (this.has_class(node, "slide"))
               break;

             if (this.has_class(node, "handout"))
               break;
           }
        }

        next = node.nextSibling;
        node = document.body.removeChild(node);
        div.appendChild(node);
        node = next;
      } 
    }
  },

  attach_touch_handers: function(slides)
  {
    var i, slide;

    for (i = 0; i < slides.length; ++i)
    {
      slide = slides[i];
      this.add_listener(slide, "touchstart", this.touchstart);
      this.add_listener(slide, "touchmove", this.touchmove);
      this.add_listener(slide, "touchend", this.touchend);
    }
  },

// return new array of all slides
  collect_slides: function () {
    var slides = new Array();
    var divs = document.body.getElementsByTagName("div");

    for (var i = 0; i < divs.length; ++i)
    {
      div = divs.item(i);

      if (this.has_class(div, "slide"))
      {
        // add slide to collection
        slides[slides.length] = div;

        // hide each slide as it is found
        this.add_class(div, "hidden");

        // add dummy <br/> at end for scrolling hack
        var node1 = document.createElement("br");
        div.appendChild(node1);
        var node2 = document.createElement("br");
        div.appendChild(node2);
      }
      else if (this.has_class(div, "background"))
      {  // work around for Firefox SVG reload bug
        // which otherwise replaces 1st SVG graphic with 2nd
        div.style.display = "block";
      }
    }

    this.slides = slides;
  },

  // return new array of all <div class="handout">
  collect_notes: function () {
    var notes = new Array();
    var divs = document.body.getElementsByTagName("div");

    for (var i = 0; i < divs.length; ++i)
    {
      div = divs.item(i);

      if (this.has_class(div, "handout"))
      {
        // add note to collection
        notes[notes.length] = div;

        // and hide it
        this.add_class(div, "hidden");
      }
    }

    this.notes = notes;
  },

  // return new array of all <div class="background">
  // including named backgrounds e.g. class="background titlepage"
  collect_backgrounds: function () {
    var backgrounds = new Array();
    var divs = document.body.getElementsByTagName("div");

    for (var i = 0; i < divs.length; ++i)
    {
      div = divs.item(i);

      if (this.has_class(div, "background"))
      {
        // add background to collection
        backgrounds[backgrounds.length] = div;

        // and hide it
        this.add_class(div, "hidden");
      }
    }

    this.backgrounds = backgrounds;
  },

  // set click handlers on all anchors
  patch_anchors: function () {
    var self = w3c_slidy;
    var handler = function (event) {
      // compare this.href with location.href
      // for link to another slide in this doc

      if (self.page_address(this.href) == self.page_address(location.href))
      {
        // yes, so find new slide number
        var newslidenum = self.find_slide_number(this.href);

        if (newslidenum != self.slide_number)
        {
          var slide = self.slides[self.slide_number];
          self.hide_slide(slide);
          self.slide_number = newslidenum;
          slide = self.slides[self.slide_number];
          self.show_slide(slide);
          self.set_location();
        }
      }
      else
        w3c_slidy.stop_propagation(event);

//      else if (this.target == null)
//        location.href = this.href;

      this.blur();
      self.disable_slide_click = true;
    };

    var anchors = document.body.getElementsByTagName("a");

    for (var i = 0; i < anchors.length; ++i)
    {
      if (window.addEventListener)
        anchors[i].addEventListener("click", handler, false);
      else
        anchors[i].attachEvent("onclick", handler);
    }
  },

  // ### CHECK ME ### see which functions are invoked via setTimeout
  // either directly or indirectly for use of w3c_slidy vs this
  show_slide_number: function () {
    var timer = w3c_slidy.get_timer();
    w3c_slidy.slide_number_element.innerHTML = timer + w3c_slidy.localize("slide") + " " +
           (w3c_slidy.slide_number + 1) + "/" + w3c_slidy.slides.length;
  },

  // every 200mS check if the location has been changed as a
  // result of the user activating the Back button/menu item
  // doesn't work for Opera < 9.5
  check_location: function () {
    var hash = location.hash;

    if (w3c_slidy.slide_number > 0 && (hash == "" || hash == "#"))
      w3c_slidy.goto_slide(0);
    else if (hash.length > 2 && hash != "#("+(w3c_slidy.slide_number+1)+")")
    {
      var num = parseInt(location.hash.substr(2));

      if (!isNaN(num))
        w3c_slidy.goto_slide(num-1);
    }

    if (w3c_slidy.time_left && w3c_slidy.slide_number > 0)
    {
      w3c_slidy.show_slide_number();

      if (w3c_slidy.time_left > 0)
        w3c_slidy.time_left -= 200;
    } 
  },

  get_timer: function () {
    var timer = "";
    if (w3c_slidy.time_left)
    {
      var mins, secs;
      secs = Math.floor(w3c_slidy.time_left/1000);
      mins = Math.floor(secs / 60);
      secs = secs % 60;
      timer = (mins ? mins+"m" : "") + secs + "s ";
    }

    return timer;
  },

  // this doesn't push location onto history stack for IE
  // for which a hidden iframe hack is needed: load page into
  // the iframe with script that set's parent's location.hash
  // but that won't work for standalone use unless we can
  // create the page dynamically via a javascript: URL
  // ### use history.pushState if available
  set_location: function () {
     var uri = w3c_slidy.page_address(location.href);
     var hash = "#(" + (w3c_slidy.slide_number+1) + ")";

     if (w3c_slidy.slide_number >= 0)
       uri = uri + hash;

     if (typeof(history.pushState) != "undefined" && location.protocol !== "file:")
     {
       document.title = w3c_slidy.title + " (" + (w3c_slidy.slide_number+1) + ")";
       history.pushState(0, document.title, hash);
       w3c_slidy.show_slide_number();
       w3c_slidy.notify_observers();
       return;
     }

     if (w3c_slidy.ie && (w3c_slidy.ie6 || w3c_slidy.ie7))
       w3c_slidy.push_hash(hash);

     if (uri != location.href) // && !khtml
        location.href = uri;

     if (this.khtml)
        hash = "(" + (w3c_slidy.slide_number+1) + ")";

     if (!this.ie && location.hash != hash && location.hash != "")
       location.hash = hash;

     document.title = w3c_slidy.title + " (" + (w3c_slidy.slide_number+1) + ")";
     w3c_slidy.show_slide_number();
     w3c_slidy.notify_observers();
  },

  notify_observers: function ()
  {
    var slide = this.slides[this.slide_number];

    for (var i = 0; i < this.observers.length; ++i)
      this.observers[i](this.slide_number+1, this.find_heading(slide).innerText, location.href);
  },

  add_observer: function (observer)
  {
    for (var i = 0; i < this.observers.length; ++i)
    {
      if (observer == this.observers[i])
        return;
    }

    this.observers.push(observer);
  },

  remove_observer: function (o)
  {
    for (var i = 0; i < this.observers.length; ++i)
    {
      if (observer == this.observers[i])
      {
        this.observers.splice(i,1);
        break;
      }
    }
  },

  page_address: function (uri) {
    var i = uri.indexOf("#");

    if (i < 0)
      i = uri.indexOf("%23");

    // check if anchor is entire page

    if (i < 0)
      return uri;  // yes

    return uri.substr(0, i);
  },

  // only used for IE6 and IE7
  on_frame_loaded: function (hash) {
    location.hash = hash;
    var uri = w3c_slidy.page_address(location.href);
    location.href = uri + hash;
  },

  // history hack with thanks to Bertrand Le Roy
  push_hash: function (hash) {
    if (hash == "") hash = "#(1)";
      window.location.hash = hash;

    var doc = document.getElementById("historyFrame").contentWindow.document;
    doc.open("javascript:'<html></html>'");
    doc.write("<html><head><script type=\"text/javascript\">window.parent.w3c_slidy.on_frame_loaded('"+
      (hash) + "');</script></head><body>hello mum</body></html>");
      doc.close();
  },

  // find current slide based upon location
  // first find target anchor and then look
  // for associated div element enclosing it
  // finally map that to slide number
  find_slide_number: function (uri) {
    // first get anchor from page location

    var i = uri.indexOf("#");

    // check if anchor is entire page
    if (i < 0)
      return 0;  // yes

    var anchor = unescape(uri.substr(i+1));

    // now use anchor as XML ID to find target
    var target = document.getElementById(anchor);

    if (!target)
    {
      // does anchor look like "(2)" for slide 2 ??
      // where first slide is (1)
      var re = /\((\d)+\)/;

      if (anchor.match(re))
      {
        var num = parseInt(anchor.substring(1, anchor.length-1));

        if (num > this.slides.length)
          num = 1;

        if (--num < 0)
          num = 0;

        return num;
      }

      // accept [2] for backwards compatibility
      re = /\[(\d)+\]/;

      if (anchor.match(re))
      {
         var num = parseInt(anchor.substring(1, anchor.length-1));

         if (num > this.slides.length)
            num = 1;

         if (--num < 0)
            num = 0;

         return num;
      }

      // oh dear unknown anchor
      return 0;
    }

    // search for enclosing slide

    while (true)
    {
      // browser coerces html elements to uppercase!
      if (target.nodeName.toLowerCase() == "div" &&
            this.has_class(target, "slide"))
      {
        // found the slide element
        break;
      }

      // otherwise try parent element if any

      target = target.parentNode;

      if (!target)
      {
        return 0;   // no luck!
      }
    };

    for (i = 0; i < slides.length; ++i)
    {
      if (slides[i] == target)
        return i;  // success
    }

    // oh dear still no luck
    return 0;
  },

  previous_slide: function (incremental) {
    if (!w3c_slidy.view_all)
    {
      var slide;

      if ((incremental || w3c_slidy.slide_number == 0) && w3c_slidy.last_shown != null)
      {
        w3c_slidy.last_shown = w3c_slidy.hide_previous_item(w3c_slidy.last_shown);
        w3c_slidy.set_eos_status(false);
      }
      else if (w3c_slidy.slide_number > 0)
      {
        slide = w3c_slidy.slides[w3c_slidy.slide_number];
        w3c_slidy.hide_slide(slide);

        w3c_slidy.slide_number = w3c_slidy.slide_number - 1;
        slide = w3c_slidy.slides[w3c_slidy.slide_number];
        w3c_slidy.set_visibility_all_incremental("visible");
        w3c_slidy.last_shown = w3c_slidy.previous_incremental_item(null);
        w3c_slidy.set_eos_status(true);
        w3c_slidy.show_slide(slide);
      }

      w3c_slidy.set_location();

      if (!w3c_slidy.ns_pos)
        w3c_slidy.refresh_toolbar(200);
    }
  },

  next_slide: function (incremental) {
    if (!w3c_slidy.view_all)
    {
      var slide, last = w3c_slidy.last_shown;

      if (incremental || w3c_slidy.slide_number == w3c_slidy.slides.length - 1)
         w3c_slidy.last_shown = w3c_slidy.reveal_next_item(w3c_slidy.last_shown);

      if ((!incremental || w3c_slidy.last_shown == null) &&
             w3c_slidy.slide_number < w3c_slidy.slides.length - 1)
      {
         slide = w3c_slidy.slides[w3c_slidy.slide_number];
         w3c_slidy.hide_slide(slide);

         w3c_slidy.slide_number = w3c_slidy.slide_number + 1;
         slide = w3c_slidy.slides[w3c_slidy.slide_number];
         w3c_slidy.last_shown = null;
         w3c_slidy.set_visibility_all_incremental("hidden");
         w3c_slidy.show_slide(slide);
      }
      else if (!w3c_slidy.last_shown)
      {
         if (last && incremental)
           w3c_slidy.last_shown = last;
      }

      w3c_slidy.set_location();

      w3c_slidy.set_eos_status(!w3c_slidy.next_incremental_item(w3c_slidy.last_shown));

      if (!w3c_slidy.ns_pos)
         w3c_slidy.refresh_toolbar(200);
     }
  },

  // to first slide with nothing revealed
  // i.e. state at start of presentation
  first_slide: function () {
     if (!w3c_slidy.view_all)
     {
       var slide;

       if (w3c_slidy.slide_number != 0)
       {
         slide = w3c_slidy.slides[w3c_slidy.slide_number];
         w3c_slidy.hide_slide(slide);

         w3c_slidy.slide_number = 0;
         slide = w3c_slidy.slides[w3c_slidy.slide_number];
         w3c_slidy.last_shown = null;
         w3c_slidy.set_visibility_all_incremental("hidden");
         w3c_slidy.show_slide(slide);
       }

       w3c_slidy.set_eos_status(
         !w3c_slidy.next_incremental_item(w3c_slidy.last_shown));
       w3c_slidy.set_location();
     }
  },

  // goto last slide with everything revealed
  // i.e. state at end of presentation
  last_slide: function () {
    if (!w3c_slidy.view_all)
    {
      var slide;

      w3c_slidy.last_shown = null; //revealNextItem(lastShown);

      if (w3c_slidy.last_shown == null &&
          w3c_slidy.slide_number < w3c_slidy.slides.length - 1)
      {
         slide = w3c_slidy.slides[w3c_slidy.slide_number];
         w3c_slidy.hide_slide(slide);
         w3c_slidy.slide_number = w3c_slidy.slides.length - 1;
         slide = w3c_slidy.slides[w3c_slidy.slide_number];
         w3c_slidy.set_visibility_all_incremental("visible");
         w3c_slidy.last_shown = w3c_slidy.previous_incremental_item(null);

         w3c_slidy.show_slide(slide);
      }
      else
      {
         w3c_slidy.set_visibility_all_incremental("visible");
         w3c_slidy.last_shown = w3c_slidy.previous_incremental_item(null);
      }

      w3c_slidy.set_eos_status(true);
      w3c_slidy.set_location();
    }
  },


  // ### check this and consider add/remove class
  set_eos_status: function (state) {
    if (this.eos)
      this.eos.style.color = (state ? "rgb(240,240,240)" : "red");
  },

  // first slide is 0
  goto_slide: function (num) {
    //alert("going to slide " + (num+1));
    var slide = w3c_slidy.slides[w3c_slidy.slide_number];
    w3c_slidy.hide_slide(slide);
    w3c_slidy.slide_number = num;
    slide = w3c_slidy.slides[w3c_slidy.slide_number];
    w3c_slidy.last_shown = null;
    w3c_slidy.set_visibility_all_incremental("hidden");
    w3c_slidy.set_eos_status(!w3c_slidy.next_incremental_item(w3c_slidy.last_shown));
    document.title = w3c_slidy.title + " (" + (w3c_slidy.slide_number+1) + ")";
    w3c_slidy.show_slide(slide);
    w3c_slidy.show_slide_number();
  },


  show_slide: function (slide) {
    this.sync_background(slide);
    this.remove_class(slide, "hidden");

    // work around IE9 object rendering bug
    setTimeout("window.scrollTo(0,0);", 1);
  },

  hide_slide: function (slide) {
    this.add_class(slide, "hidden");
  },

  set_focus: function (element)
  {
    if (element)
      element.focus();
    else
    {
      w3c_slidy.help_anchor.focus();

      setTimeout(function() {
        w3c_slidy.help_anchor.blur();
      }, 1);
    }
  },

  // show just the backgrounds pertinent to this slide
  // when slide background-color is transparent
  // this should now work with rgba color values
  sync_background: function (slide) {
    var background;
    var bgColor;

    if (slide.currentStyle)
      bgColor = slide.currentStyle["backgroundColor"];
    else if (document.defaultView)
    {
      var styles = document.defaultView.getComputedStyle(slide,null);

      if (styles)
        bgColor = styles.getPropertyValue("background-color");
      else // broken implementation probably due Safari or Konqueror
      {
        //alert("defective implementation of getComputedStyle()");
        bgColor = "transparent";
      }
    }
    else
      bgColor == "transparent";

    if (bgColor == "transparent" ||
        bgColor.indexOf("rgba") >= 0 ||
        bgColor.indexOf("opacity") >= 0)
    {
      var slideClass = this.get_class_list(slide);

      for (var i = 0; i < this.backgrounds.length; i++)
      {
        background = this.backgrounds[i];

        var bgClass = this.get_class_list(background);

        if (this.matching_background(slideClass, bgClass))
          this.remove_class(background, "hidden");
        else
          this.add_class(background, "hidden");
      }
    }
    else // forcibly hide all backgrounds
      this.hide_backgrounds();
  },

  hide_backgrounds: function () {
    for (var i = 0; i < this.backgrounds.length; i++)
    {
      background = this.backgrounds[i];
      this.add_class(background, "hidden");
    }
  },

  // compare classes for slide and background
  matching_background: function (slideClass, bgClass) {
    var i, count, pattern, result;

    // define pattern as regular expression
    pattern = /\w+/g;

    // check background class names
    result = bgClass.match(pattern);

    for (i = count = 0; i < result.length; i++)
    {
      if (result[i] == "hidden")
        continue;

      if (result[i] == "background")
	continue;

      ++count;
    }

    if (count == 0)  // default match
      return true;

    // check for matches and place result in array
    result = slideClass.match(pattern);

    // now check if desired name is present for background
    for (i = count = 0; i < result.length; i++)
    {
      if (result[i] == "hidden")
        continue;

      if (this.has_token(bgClass, result[i]))
        return true;
    }

    return false;
  },

  resized: function () {
     var width = 0;

     if ( typeof( window.innerWidth ) == 'number' )
       width = window.innerWidth;  // Non IE browser
     else if (document.documentElement && document.documentElement.clientWidth)
       width = document.documentElement.clientWidth;  // IE6
     else if (document.body && document.body.clientWidth)
       width = document.body.clientWidth; // IE4

     var height = 0;

     if ( typeof( window.innerHeight ) == 'number' )
       height = window.innerHeight;  // Non IE browser
     else if (document.documentElement && document.documentElement.clientHeight)
       height = document.documentElement.clientHeight;  // IE6
     else if (document.body && document.body.clientHeight)
       height = document.body.clientHeight; // IE4

     if (height && (width/height > 1.05*1024/768))
     {
       width = height * 1024.0/768;
     }

     // IE fires onresize even when only font size is changed!
     // so we do a check to avoid blocking < and > actions
     if (width != w3c_slidy.last_width || height != w3c_slidy.last_height)
     {
       if (width >= 1100)
         w3c_slidy.size_index = 5;    // 4
       else if (width >= 1000)
         w3c_slidy.size_index = 4;    // 3
       else if (width >= 800)
         w3c_slidy.size_index = 3;    // 2
       else if (width >= 600)
         w3c_slidy.size_index = 2;    // 1
       else if (width)
         w3c_slidy.size_index = 0;

       // add in font size adjustment from meta element e.g.
       // <meta name="font-size-adjustment" content="-2" />
       // useful when slides have too much content ;-)

       if (0 <= w3c_slidy.size_index + w3c_slidy.size_adjustment &&
             w3c_slidy.size_index + w3c_slidy.size_adjustment < w3c_slidy.sizes.length)
         w3c_slidy.size_index = w3c_slidy.size_index + w3c_slidy.size_adjustment;

       // enables cross browser use of relative width/height
       // on object elements for use with SVG and Flash media
       w3c_slidy.adjust_object_dimensions(width, height);

       if (document.body.style.fontSize != w3c_slidy.sizes[w3c_slidy.size_index])
       {
         document.body.style.fontSize = w3c_slidy.sizes[w3c_slidy.size_index];
       }

       w3c_slidy.last_width = width;
       w3c_slidy.last_height = height;

       // force reflow to work around Mozilla bug
       if (w3c_slidy.ns_pos)
       {
         var slide = w3c_slidy.slides[w3c_slidy.slide_number];
         w3c_slidy.hide_slide(slide);
         w3c_slidy.show_slide(slide);
       }

       // force correct positioning of toolbar
       w3c_slidy.refresh_toolbar(200);
     }
  },

  scrolled: function () {
    if (w3c_slidy.toolbar && !w3c_slidy.ns_pos && !w3c_slidy.ie7)
    {
      w3c_slidy.hack_offset = w3c_slidy.scroll_x_offset();
      // hide toolbar
      w3c_slidy.toolbar.style.display = "none";

      // make it reappear later
      if (w3c_slidy.scrollhack == 0 && !w3c_slidy.view_all)
      {
        setTimeout(function () {w3c_slidy.show_toolbar(); }, 1000);
        w3c_slidy.scrollhack = 1;
      }
    }
  },

  hide_toolbar: function () {
    w3c_slidy.add_class(w3c_slidy.toolbar, "hidden");
    window.focus();
  },

  // used to ensure IE refreshes toolbar in correct position
  refresh_toolbar: function (interval) {
    if (!w3c_slidy.ns_pos && !w3c_slidy.ie7)
    {
      w3c_slidy.hide_toolbar();
      setTimeout(function () {w3c_slidy.show_toolbar();}, interval);
    }
  },

  // restores toolbar after short delay
  show_toolbar: function () {
    if (w3c_slidy.want_toolbar)
    {
      w3c_slidy.toolbar.style.display = "block";

      if (!w3c_slidy.ns_pos)
      {
        // adjust position to allow for scrolling
        var xoffset = w3c_slidy.scroll_x_offset();
        w3c_slidy.toolbar.style.left = xoffset;
        w3c_slidy.toolbar.style.right = xoffset;

        // determine vertical scroll offset
        //var yoffset = scrollYOffset();

        // bottom is doc height - window height - scroll offset
        //var bottom = documentHeight() - lastHeight - yoffset

        //if (yoffset > 0 || documentHeight() > lastHeight)
        //   bottom += 16;  // allow for height of scrollbar

        w3c_slidy.toolbar.style.bottom = 0; //bottom;
      }

      w3c_slidy.remove_class(w3c_slidy.toolbar, "hidden");
    }

    w3c_slidy.scrollhack = 0;


    // set the keyboard focus to the help link on the
    // toolbar to ensure that document has the focus
    // IE doesn't always work with window.focus()
    // and this hack has benefit of Enter for help

    try
    {
      if (!w3c_slidy.opera)
        w3c_slidy.set_focus();
    }
    catch (e)
    {
    }
  },

// invoked via F key
  toggle_toolbar: function () {
    if (!w3c_slidy.view_all)
    {
      if (w3c_slidy.has_class(w3c_slidy.toolbar, "hidden"))
      {
        w3c_slidy.remove_class(w3c_slidy.toolbar, "hidden")
        w3c_slidy.want_toolbar = 1;
      }
      else
      {
        w3c_slidy.add_class(w3c_slidy.toolbar, "hidden")
        w3c_slidy.want_toolbar = 0;
      }
    }
  },

  scroll_x_offset: function () {
    if (window.pageXOffset)
      return self.pageXOffset;

    if (document.documentElement && 
             document.documentElement.scrollLeft)
      return document.documentElement.scrollLeft;

    if (document.body)
      return document.body.scrollLeft;

    return 0;
  },

  scroll_y_offset: function () {
    if (window.pageYOffset)
      return self.pageYOffset;

    if (document.documentElement && 
             document.documentElement.scrollTop)
      return document.documentElement.scrollTop;

    if (document.body)
      return document.body.scrollTop;

    return 0;
  },

  // looking for a way to determine height of slide content
  // the slide itself is set to the height of the window
  optimize_font_size: function () {
    var slide = w3c_slidy.slides[w3c_slidy.slide_number];

    //var dh = documentHeight(); //getDocHeight(document);
    var dh = slide.scrollHeight;
    var wh = getWindowHeight();
    var u = 100 * dh / wh;

    alert("window utilization = " + u + "% (doc "
      + dh + " win " + wh + ")");
  },

  // from document object
  get_doc_height: function (doc) {
    if (!doc)
      doc = document;

    if (doc && doc.body && doc.body.offsetHeight)
      return doc.body.offsetHeight;  // ns/gecko syntax

    if (doc && doc.body && doc.body.scrollHeight)
      return doc.body.scrollHeight;

    alert("couldn't determine document height");
  },

  get_window_height: function () {
    if ( typeof( window.innerHeight ) == 'number' )
      return window.innerHeight;  // Non IE browser

    if (document.documentElement && document.documentElement.clientHeight)
      return document.documentElement.clientHeight;  // IE6

    if (document.body && document.body.clientHeight)
      return document.body.clientHeight; // IE4
  },

  document_height: function () {
    var sh, oh;

    sh = document.body.scrollHeight;
    oh = document.body.offsetHeight;

    if (sh && oh)
    {
      return (sh > oh ? sh : oh);
    }

    // no idea!
    return 0;
  },

  smaller: function () {
    if (w3c_slidy.size_index > 0)
    {
      --w3c_slidy.size_index;
    }

    w3c_slidy.toolbar.style.display = "none";
    document.body.style.fontSize = w3c_slidy.sizes[w3c_slidy.size_index];
    var slide = w3c_slidy.slides[w3c_slidy.slide_number];
    w3c_slidy.hide_slide(slide);
    w3c_slidy.show_slide(slide);
    setTimeout(function () {w3c_slidy.show_toolbar(); }, 50);
  },

  bigger: function () {
    if (w3c_slidy.size_index < w3c_slidy.sizes.length - 1)
    {
      ++w3c_slidy.size_index;
    }

    w3c_slidy.toolbar.style.display = "none";
    document.body.style.fontSize = w3c_slidy.sizes[w3c_slidy.size_index];
    var slide = w3c_slidy.slides[w3c_slidy.slide_number];
    w3c_slidy.hide_slide(slide);
    w3c_slidy.show_slide(slide);
    setTimeout(function () {w3c_slidy.show_toolbar(); }, 50);
  },

  // enables cross browser use of relative width/height
  // on object elements for use with SVG and Flash media
  // with thanks to Ivan Herman for the suggestion
  adjust_object_dimensions: function (width, height) {
    for( var i = 0; i < w3c_slidy.objects.length; i++ )
    {
      var obj = this.objects[i];
      var mimeType = obj.getAttribute("type");

      if (mimeType == "image/svg+xml" || mimeType == "application/x-shockwave-flash")
      {
        if ( !obj.initialWidth ) 
          obj.initialWidth = obj.getAttribute("width");

        if ( !obj.initialHeight ) 
          obj.initialHeight = obj.getAttribute("height");

        if ( obj.initialWidth && obj.initialWidth.charAt(obj.initialWidth.length-1) == "%" )
        {
          var w = parseInt(obj.initialWidth.slice(0, obj.initialWidth.length-1));
          var newW = width * (w/100.0);
          obj.setAttribute("width",newW);
        }

        if ( obj.initialHeight &&
             obj.initialHeight.charAt(obj.initialHeight.length-1) == "%" )
        {
          var h = parseInt(obj.initialHeight.slice(0, obj.initialHeight.length-1));
          var newH = height * (h/100.0);
          obj.setAttribute("height", newH);
        }
      }
    }
  },

  // needed for Opera to inhibit default behavior
  // since Opera delivers keyPress even if keyDown
  // was cancelled
  key_press: function (event) {
    if (!event)
      event = window.event;

    if (!w3c_slidy.key_wanted)
      return w3c_slidy.cancel(event);

    return true;
  },

  //  See e.g. http://www.quirksmode.org/js/events/keys.html for keycodes
  key_down: function (event) {
    var key, target, tag;

    w3c_slidy.key_wanted = true;

    if (!event)
      event = window.event;

    // kludge around NS/IE differences 
    if (window.event)
    {
      key = window.event.keyCode;
      target = window.event.srcElement;
    }
    else if (event.which)
    {
      key = event.which;
      target = event.target;
    }
    else
      return true; // Yikes! unknown browser

    // ignore event if key value is zero
    // as for alt on Opera and Konqueror
    if (!key)
       return true;

    // avoid interfering with keystroke
    // behavior for non-slidy chrome elements
    if (!w3c_slidy.slidy_chrome(target) &&
        w3c_slidy.special_element(target))
      return true;

    // check for concurrent control/command/alt key
    // but are these only present on mouse events?

    if (event.ctrlKey || event.altKey || event.metaKey)
       return true;

    // dismiss table of contents if visible
    if (w3c_slidy.is_shown_toc() && key != 9 && key != 16 && key != 38 && key != 40)
    {
      w3c_slidy.hide_table_of_contents(true);

      if (key == 27 || key == 84 || key == 67)
        return w3c_slidy.cancel(event);
    }

    if (key == 34) // Page Down
    {
      if (w3c_slidy.view_all)
        return true;

      w3c_slidy.next_slide(false);
      return w3c_slidy.cancel(event);
    }
    else if (key == 33) // Page Up
    {
      if (w3c_slidy.view_all)
        return true;

      w3c_slidy.previous_slide(false);
      return w3c_slidy.cancel(event);
    }
    else if (key == 32) // space bar
    {
      w3c_slidy.next_slide(true);
      return w3c_slidy.cancel(event);
    }
    else if (key == 37) // Left arrow
    {
      w3c_slidy.previous_slide(!event.shiftKey);
      return w3c_slidy.cancel(event);
    }
    else if (key == 36) // Home
    {
      w3c_slidy.first_slide();
      return w3c_slidy.cancel(event);
    }
    else if (key == 35) // End
    {
      w3c_slidy.last_slide();
      return w3c_slidy.cancel(event);
    }
    else if (key == 39) // Right arrow
    {
      w3c_slidy.next_slide(!event.shiftKey);
      return w3c_slidy.cancel(event);
    }
    else if (key == 13) // Enter
    {
      if (w3c_slidy.outline)
      {
        if (w3c_slidy.outline.visible)
          w3c_slidy.fold(w3c_slidy.outline);
        else
          w3c_slidy.unfold(w3c_slidy.outline);
          
       return w3c_slidy.cancel(event);
      }
    }
    else if (key == 188)  // < for smaller fonts
    {
      w3c_slidy.smaller();
      return w3c_slidy.cancel(event);
    }
    else if (key == 190)  // > for larger fonts
    {
      w3c_slidy.bigger();
      return w3c_slidy.cancel(event);
    }
    else if (key == 189 || key == 109)  // - for smaller fonts
    {
      w3c_slidy.smaller();
      return w3c_slidy.cancel(event);
    }
    else if (key == 187 || key == 191 || key == 107)  // = +  for larger fonts
    {
      w3c_slidy.bigger();
      return w3c_slidy.cancel(event);
    }
    else if (key == 83)  // S for smaller fonts
    {
      w3c_slidy.smaller();
      return w3c_slidy.cancel(event);
    }
    else if (key == 66)  // B for larger fonts
    {
      w3c_slidy.bigger();
      return w3c_slidy.cancel(event);
    }
    else if (key == 90)  // Z for last slide
    {
      w3c_slidy.last_slide();
      return w3c_slidy.cancel(event);
    }
    else if (key == 70)  // F for toggle toolbar
    {
      w3c_slidy.toggle_toolbar();
      return w3c_slidy.cancel(event);
    }
    else if (key == 65)  // A for toggle view single/all slides
    {
      w3c_slidy.toggle_view();
      return w3c_slidy.cancel(event);
    }
    else if (key == 75)  // toggle action of left click for next page
    {
      w3c_slidy.mouse_click_enabled = !w3c_slidy.mouse_click_enabled;
      var alert_msg = (w3c_slidy.mouse_click_enabled ?
                "enabled" : "disabled") +  " mouse click advance";

      alert(w3c_slidy.localize(alert_msg));
      return w3c_slidy.cancel(event);
    }
    else if (key == 84 || key == 67)  // T or C for table of contents
    {
      if (w3c_slidy.toc)
        w3c_slidy.toggle_table_of_contents();

      return w3c_slidy.cancel(event);
    }
    else if (key == 72) // H for help
    {
      window.location = w3c_slidy.help_page;
      return w3c_slidy.cancel(event);
    }
    //else alert("key code is "+ key);

    return true;
  },

  // safe for both text/html and application/xhtml+xml
  create_element: function (name) {
    if (this.xhtml && (typeof document.createElementNS != 'undefined'))
      return document.createElementNS("http://www.w3.org/1999/xhtml", name)

    return document.createElement(name);
  },

  get_element_style: function (elem, IEStyleProp, CSSStyleProp) {
    if (elem.currentStyle)
    {
      return elem.currentStyle[IEStyleProp];
    }
    else if (window.getComputedStyle)
    {
      var compStyle = window.getComputedStyle(elem, "");
      return compStyle.getPropertyValue(CSSStyleProp);
    }
    return "";
  },

  // the string str is a whitespace separated list of tokens
  // test if str contains a particular token, e.g. "slide"
  has_token: function (str, token) {
    if (str)
    {
      // define pattern as regular expression
      var pattern = /\w+/g;

      // check for matches
      // place result in array
      var result = str.match(pattern);

      // now check if desired token is present
      for (var i = 0; i < result.length; i++)
      {
        if (result[i] == token)
          return true;
      }
    }

    return false;
  },

  get_class_list: function (element) {
    if (typeof element.className != 'undefined')
      return element.className;

    return element.getAttribute("class");
  },

  has_class: function (element, name) {
    if (element.nodeType != 1)
      return false;

    var regexp = new RegExp("(^| )" + name + "\W*");

    if (typeof element.className != 'undefined')
      return regexp.test(element.className);

    return regexp.test(element.getAttribute("class"));
  },

  remove_class: function (element, name) {
    var regexp = new RegExp("(^| )" + name + "\W*");
    var clsval = "";

    if (typeof element.className != 'undefined')
    {
      clsval = element.className;

      if (clsval)
      {
        clsval = clsval.replace(regexp, "");
        element.className = clsval;
      }
    }
    else
    {
      clsval = element.getAttribute("class");

      if (clsval)
      {
        clsval = clsval.replace(regexp, "");
        element.setAttribute("class", clsval);
      }
    }
  },

  add_class: function (element, name) {
    if (!this.has_class(element, name))
    {
      if (typeof element.className != 'undefined')
        element.className += " " + name;
      else
      {
        var clsval = element.getAttribute("class");
        clsval = clsval ? clsval + " " + name : name;
        element.setAttribute("class", clsval);
      }
    }
  },

  // HTML elements that can be used with class="incremental"
  // note that you can also put the class on containers like
  // up, ol, dl, and div to make their contents appear
  // incrementally. Upper case is used since this is what
  // browsers report for HTML node names (text/html).
  incremental_elements: null,
  okay_for_incremental: function (name) {
    if (!this.incremental_elements)
    {
      var inclist = new Array();
      inclist["p"] = true;
      inclist["pre"] = true;
      inclist["li"] = true;
      inclist["blockquote"] = true;
      inclist["dt"] = true;
      inclist["dd"] = true;
      inclist["h2"] = true;
      inclist["h3"] = true;
      inclist["h4"] = true;
      inclist["h5"] = true;
      inclist["h6"] = true;
      inclist["span"] = true;
      inclist["address"] = true;
      inclist["table"] = true;
      inclist["tr"] = true;
      inclist["th"] = true;
      inclist["td"] = true;
      inclist["img"] = true;
      inclist["object"] = true;
      this.incremental_elements = inclist;
    }
    return this.incremental_elements[name.toLowerCase()];
  },

  next_incremental_item: function (node) {
    var br = this.is_xhtml ? "br" : "BR";
    var slide = w3c_slidy.slides[w3c_slidy.slide_number];

    for (;;)
    {
      node = w3c_slidy.next_node(slide, node);

      if (node == null || node.parentNode == null)
        break;

      if (node.nodeType == 1)  // ELEMENT
      {
        if (node.nodeName == br)
          continue;

        if (w3c_slidy.has_class(node, "incremental")
             && w3c_slidy.okay_for_incremental(node.nodeName))
          return node;

        if (w3c_slidy.has_class(node.parentNode, "incremental")
             && !w3c_slidy.has_class(node, "non-incremental"))
          return node;
      }
    }

    return node;
  },

  previous_incremental_item: function (node) {
    var br = this.is_xhtml ? "br" : "BR";
    var slide = w3c_slidy.slides[w3c_slidy.slide_number];

    for (;;)
    {
      node = w3c_slidy.previous_node(slide, node);

      if (node == null || node.parentNode == null)
        break;

      if (node.nodeType == 1)
      {
        if (node.nodeName == br)
          continue;

        if (w3c_slidy.has_class(node, "incremental")
             && w3c_slidy.okay_for_incremental(node.nodeName))
          return node;

        if (w3c_slidy.has_class(node.parentNode, "incremental")
             && !w3c_slidy.has_class(node, "non-incremental"))
          return node;
      }
    }

    return node;
  },

  // set visibility for all elements on current slide with
  // a parent element with attribute class="incremental"
  set_visibility_all_incremental: function (value) {
    var node = this.next_incremental_item(null);

    if (value == "hidden")
    {
      while (node)
      {
        w3c_slidy.add_class(node, "invisible");
        node = w3c_slidy.next_incremental_item(node);
      }
    }
    else // value == "visible"
    {
      while (node)
      {
        w3c_slidy.remove_class(node, "invisible");
        node = w3c_slidy.next_incremental_item(node);
      }
    }
  },

  // reveal the next hidden item on the slide
  // node is null or the node that was last revealed
  reveal_next_item: function (node) {
    node = w3c_slidy.next_incremental_item(node);

    if (node && node.nodeType == 1)  // an element
      w3c_slidy.remove_class(node, "invisible");

    return node;
  },

  // exact inverse of revealNextItem(node)
  hide_previous_item: function (node) {
    if (node && node.nodeType == 1)  // an element
      w3c_slidy.add_class(node, "invisible");

    return this.previous_incremental_item(node);
  },

  // left to right traversal of root's content
  next_node: function (root, node) {
    if (node == null)
      return root.firstChild;

    if (node.firstChild)
      return node.firstChild;

    if (node.nextSibling)
      return node.nextSibling;

    for (;;)
    {
      node = node.parentNode;

      if (!node || node == root)
        break;

      if (node && node.nextSibling)
        return node.nextSibling;
    }

    return null;
  },

  // right to left traversal of root's content
  previous_node: function (root, node) {
    if (node == null)
    {
      node = root.lastChild;

      if (node)
      {
        while (node.lastChild)
          node = node.lastChild;
      }

      return node;
    }

    if (node.previousSibling)
    {
      node = node.previousSibling;

      while (node.lastChild)
        node = node.lastChild;

      return node;
    }

    if (node.parentNode != root)
      return node.parentNode;

    return null;
  },

  previous_sibling_element: function (el) {
    el = el.previousSibling;

    while (el && el.nodeType != 1)
      el = el.previousSibling;

    return el;
  },

  next_sibling_element: function (el) {
    el = el.nextSibling;

    while (el && el.nodeType != 1)
      el = el.nextSibling;

    return el;
  },

  first_child_element: function (el) {
    var node;

    for (node = el.firstChild; node; node = node.nextSibling)
    {
      if (node.nodeType == 1)
        break;
    }

    return node;
  },

  first_tag: function (element, tag) {
    var node;

    if (!this.is_xhtml)
      tag = tag.toUpperCase();

    for (node = element.firstChild; node; node = node.nextSibling)
    {
      if (node.nodeType == 1 && node.nodeName == tag)
        break;
    }

    return node;
  },

  hide_selection: function () {
    if (window.getSelection) // Firefox, Chromium, Safari, Opera
    {
      var selection = window.getSelection();

      if (selection.rangeCount > 0)
      {
        var range = selection.getRangeAt(0);
        range.collapse (false);
      }
    }
    else // Internet Explorer
    {
      var textRange = document.selection.createRange ();
      textRange.collapse (false);
    }
  },

  get_selected_text: function () {
    try
    {
      if (window.getSelection)
        return window.getSelection().toString();

      if (document.getSelection)
        return document.getSelection().toString();

      if (document.selection)
        return document.selection.createRange().text;
    }
    catch (e)
    {
    }

    return "";
  },

  // make note of length of selected text
  // as this evaluates to zero in click event
  mouse_button_up: function (e) {
    w3c_slidy.selected_text_len = w3c_slidy.get_selected_text().length;
  },

  mouse_button_down: function (e) {
    w3c_slidy.selected_text_len = w3c_slidy.get_selected_text().length;
    w3c_slidy.mouse_x = e.clientX;
    w3c_slidy.mouse_y = e.clientY;
  },

  // right mouse button click is reserved for context menus
  // it is more reliable to detect rightclick than leftclick
  mouse_button_click: function (e) {
    if (!e)
      var e = window.event;

    if (Math.abs(e.clientX -w3c_slidy.mouse_x) +
        Math.abs(e.clientY -w3c_slidy.mouse_y) > 10)
      return true;

    if (w3c_slidy.selected_text_len > 0)
      return true;

    var rightclick = false;
    var leftclick = false;
    var middleclick = false;
    var target;

    if (!e)
      var e = window.event;

    if (e.target)
      target = e.target;
    else if (e.srcElement)
      target = e.srcElement;

    // work around Safari bug
    if (target.nodeType == 3)
      target = target.parentNode;

    if (e.which) // all browsers except IE
    {
      leftclick = (e.which == 1);
      middleclick = (e.which == 2);
      rightclick = (e.which == 3);
    }
    else if (e.button)
    {
      // Konqueror gives 1 for left, 4 for middle
      // IE6 gives 0 for left and not 1 as I expected

      if (e.button == 4)
        middleclick = true;

      // all browsers agree on 2 for right button
      rightclick = (e.button == 2);
    }
    else
      leftclick = true;

    if (w3c_slidy.selected_text_len > 0)
    {
      w3c_slidy.stop_propagation(e);
      e.cancel = true;
      e.returnValue = false;
      return false;
    }

    // dismiss table of contents
    w3c_slidy.hide_table_of_contents(false);

    // check if target is something that probably want's clicks
    // e.g. a, embed, object, input, textarea, select, option
    var tag = target.nodeName.toLowerCase();

    if (w3c_slidy.mouse_click_enabled && leftclick &&
        !w3c_slidy.special_element(target) &&
        !target.onclick)
    {
      w3c_slidy.next_slide(true);
      w3c_slidy.stop_propagation(e);
      e.cancel = true;
      e.returnValue = false;
      return false;
    }

    return true;
  },

  special_element: function (element) {
    if (this.has_class(element, "non-interactive"))
      return false;

    var tag = element.nodeName.toLowerCase();

    return element.onkeydown ||
      element.onclick ||
      tag == "a" ||
      tag == "embed" ||
      tag == "object" ||
      tag == "video" ||
      tag == "audio" ||
      tag == "svg" ||
      tag == "canvas" ||
      tag == "input" ||
      tag == "textarea" ||
      tag == "select" ||
      tag == "option";
  },

  slidy_chrome: function (el) {
    while (el)
    {
      if (el == w3c_slidy.toc ||
          el == w3c_slidy.toolbar ||
          w3c_slidy.has_class(el, "outline"))
        return true;

      el = el.parentNode;
    }

    return false;
  },

  get_key: function (e)
  {
    var key;

    // kludge around NS/IE differences 
    if (typeof window.event != "undefined")
      key = window.event.keyCode;
    else if (e.which)
      key = e.which;

    return key;
  },

  get_target: function (e) {
    var target;

    if (!e)
      e = window.event;

    if (e.target)
      target = e.target;
    else if (e.srcElement)
      target = e.srcElement;

    if (target.nodeType != 1)
      target = target.parentNode;

    return target;
  },

  // does display property provide correct defaults?
  is_block: function (elem) {
    var tag = elem.nodeName.toLowerCase();

    return tag == "ol" || tag == "ul" || tag == "p" || tag == "dl" ||
           tag == "li" || tag == "table" || tag == "pre" ||
           tag == "h1" || tag == "h2" || tag == "h3" ||
           tag == "h4" || tag == "h5" || tag == "h6" ||
           tag == "blockquote" || tag == "address"; 
  },

  add_listener: function (element, event, handler) {
    if (window.addEventListener)
      element.addEventListener(event, handler, false);
    else
      element.attachEvent("on"+event, handler);
  },

  // used to prevent event propagation from field controls
  stop_propagation: function (event) {
    event = event ? event : window.event;
    event.cancelBubble = true;  // for IE

    if (event.stopPropagation)
      event.stopPropagation();

    return true;
  },

  cancel: function (event) {
    if (event)
    {
       event.cancel = true;
       event.returnValue = false;

      if (event.preventDefault)
        event.preventDefault();
    }

    w3c_slidy.key_wanted = false;
    return false;
  },

// for each language define an associative array
// and also the help text which is longer

  strings_es: {
    "slide":"pág.",
    "help?":"Ayuda",
    "contents?":"Índice",
    "table of contents":"tabla de contenidos",
    "Table of Contents":"Tabla de Contenidos",
    "restart presentation":"Reiniciar presentación",
    "restart?":"Inicio"
  },
  help_es:
    "Utilice el ratón, barra espaciadora, teclas Izda/Dcha, " +
    "o Re pág y Av pág. Use S y B para cambiar el tamaño de fuente.",

  strings_ca: {
    "slide":"pàg..",
    "help?":"Ajuda",
    "contents?":"Índex",
    "table of contents":"taula de continguts",
    "Table of Contents":"Taula de Continguts",
    "restart presentation":"Reiniciar presentació",
    "restart?":"Inici"
  },
  help_ca:
    "Utilitzi el ratolí, barra espaiadora, tecles Esq./Dta. " +
    "o Re pàg y Av pàg. Usi S i B per canviar grandària de font.",

  strings_cs: {
    "slide":"snímek",
    "help?":"nápověda",
    "contents?":"obsah",
    "table of contents":"obsah prezentace",
    "Table of Contents":"Obsah prezentace",
    "restart presentation":"znovu spustit prezentaci",
    "restart?":"restart"
  },
  help_cs:
    "Prezentaci můžete procházet pomocí kliknutí myši, mezerníku, " +
    "šipek vlevo a vpravo nebo kláves PageUp a PageDown. Písmo se " +
    "dá zvětšit a zmenšit pomocí kláves B a S.",

  strings_nl: {
    "slide":"pagina",
    "help?":"Help?",
    "contents?":"Inhoud?",
    "table of contents":"inhoudsopgave",
    "Table of Contents":"Inhoudsopgave",
    "restart presentation":"herstart presentatie",
    "restart?":"Herstart?"
  },
  help_nl:
     "Navigeer d.m.v. het muis, spatiebar, Links/Rechts toetsen, " +
     "of PgUp en PgDn. Gebruik S en B om de karaktergrootte te veranderen.",

  strings_de: {
    "slide":"Seite",
    "help?":"Hilfe",
    "contents?":"Übersicht",
    "table of contents":"Inhaltsverzeichnis",
    "Table of Contents":"Inhaltsverzeichnis",
    "restart presentation":"Präsentation neu starten",
    "restart?":"Neustart"
  },
  help_de:
    "Benutzen Sie die Maus, Leerschlag, die Cursortasten links/rechts oder " +
    "Page up/Page Down zum Wechseln der Seiten und S und B für die Schriftgrösse.",

  strings_pl: {
    "slide":"slajd",
    "help?":"pomoc?",
    "contents?":"spis treści?",
    "table of contents":"spis treści",
    "Table of Contents":"Spis Treści",
    "restart presentation":"Restartuj prezentację",
    "restart?":"restart?"
  },
  help_pl:
    "Zmieniaj slajdy klikając myszą, naciskając spację, strzałki lewo/prawo" +
    "lub PgUp / PgDn. Użyj klawiszy S i B, aby zmienić rozmiar czczionki.",

  strings_fr: {
    "slide":"page",
    "help?":"Aide",
    "contents?":"Index",
    "table of contents":"table des matières",
    "Table of Contents":"Table des matières",
    "restart presentation":"Recommencer l'exposé",
    "restart?":"Début"
  },
  help_fr:
    "Naviguez avec la souris, la barre d'espace, les flèches " +
    "gauche/droite ou les touches Pg Up, Pg Dn. Utilisez " +
    "les touches S et B pour modifier la taille de la police.",

  strings_hu: {
    "slide":"oldal",
    "help?":"segítség",
    "contents?":"tartalom",
    "table of contents":"tartalomjegyzék",
    "Table of Contents":"Tartalomjegyzék",
    "restart presentation":"bemutató újraindítása",
    "restart?":"újraindítás"
  },
  help_hu:
    "Az oldalak közti lépkedéshez kattintson az egérrel, vagy " +
    "használja a szóköz, a bal, vagy a jobb nyíl, illetve a Page Down, " +
    "Page Up billentyűket. Az S és a B billentyűkkel változtathatja " +
    "a szöveg méretét.",

  strings_it: {
    "slide":"pag.",
    "help?":"Aiuto",
    "contents?":"Indice",
    "table of contents":"indice",
    "Table of Contents":"Indice",
    "restart presentation":"Ricominciare la presentazione",
    "restart?":"Inizio"
  },
  help_it:
    "Navigare con mouse, barra spazio, frecce sinistra/destra o " +
    "PgUp e PgDn. Usare S e B per cambiare la dimensione dei caratteri.",

  strings_el: {
    "slide":"σελίδα",
    "help?":"βοήθεια;",
    "contents?":"περιεχόμενα;",
    "table of contents":"πίνακας περιεχομένων",
    "Table of Contents":"Πίνακας Περιεχομένων",
    "restart presentation":"επανεκκίνηση παρουσίασης",
    "restart?":"επανεκκίνηση;"
  },
  help_el:
    "Πλοηγηθείτε με το κλίκ του ποντικιού, το space, τα βέλη αριστερά/δεξιά, " +
    "ή Page Up και Page Down. Χρησιμοποιήστε τα πλήκτρα S και B για να αλλάξετε " +
    "το μέγεθος της γραμματοσειράς.",

  strings_ja: {
    "slide":"スライド",
    "help?":"ヘルプ",
    "contents?":"目次",
    "table of contents":"目次を表示",
    "Table of Contents":"目次",
    "restart presentation":"最初から再生",
    "restart?":"最初から"
  },
  help_ja:
     "マウス左クリック ・ スペース ・ 左右キー " +
     "または Page Up ・ Page Downで操作， S ・ Bでフォントサイズ変更",

  strings_zh: {
    "slide":"幻灯片",
    "help?":"帮助?",
    "contents?":"内容?",
    "table of contents":"目录",
    "Table of Contents":"目录",
    "restart presentation":"重新启动展示",
    "restart?":"重新启动?"
  },
  help_zh:
    "用鼠标点击, 空格条, 左右箭头, Pg Up 和 Pg Dn 导航. " +
    "用 S, B 改变字体大小.",

  strings_ru: {
    "slide":"слайд",
    "help?":"помощь?",
    "contents?":"содержание?",
    "table of contents":"оглавление",
    "Table of Contents":"Оглавление",
    "restart presentation":"перезапустить презентацию",
    "restart?":"перезапуск?"
  },
  help_ru:
    "Перемещайтесь кликая мышкой, используя клавишу пробел, стрелки" +
    "влево/вправо или Pg Up и Pg Dn. Клавиши S и B меняют размер шрифта.",

  strings_sv: {
    "slide":"sida",
    "help?":"hjälp",
    "contents?":"innehåll",
    "table of contents":"innehållsförteckning",
    "Table of Contents":"Innehållsförteckning",
    "restart presentation":"visa presentationen från början",
    "restart?":"börja om"
  },
  help_sv:
    "Bläddra med ett klick med vänstra musknappen, mellanslagstangenten, " +
    "vänster- och högerpiltangenterna eller tangenterna Pg Up, Pg Dn. " +
    "Använd tangenterna S och B för att ändra textens storlek.",

  strings: { },

  localize: function (src) {
    if (src == "")
      return src;

     // try full language code, e.g. en-US
     var s, lookup = w3c_slidy.strings[w3c_slidy.lang];

     if (lookup)
     {
       s = lookup[src];

       if (s)
        return s;
     }

     // strip country code suffix, e.g.
     // try en if undefined for en-US
     var lg = w3c_slidy.lang.split("-");

     if (lg.length > 1)
     {
       lookup = w3c_slidy.strings[lg[0]];

       if (lookup)
       {
         s = lookup[src];

         if (s)
          return s;
       }
     }

     // otherwise string as is
     return src;
  },

  init_localization: function () {
    var i18n = w3c_slidy;
    var help_text = w3c_slidy.help_text;

    // each such language array is declared in the localize array
    // this is used as in  w3c_slidy.localize("foo");
    this.strings = {
      "es":this.strings_es,
      "ca":this.strings_ca,
      "cs":this.strings_cs,
      "nl":this.strings_nl,
      "de":this.strings_de,
      "pl":this.strings_pl,
      "fr":this.strings_fr,
      "hu":this.strings_hu,
      "it":this.strings_it,
      "el":this.strings_el,
      "jp":this.strings_ja,
      "zh":this.strings_zh,
      "ru":this.strings_ru,
      "sv":this.strings_sv
    },

    i18n.strings_es[help_text] = i18n.help_es;
    i18n.strings_ca[help_text] = i18n.help_ca;
    i18n.strings_cs[help_text] = i18n.help_cs;
    i18n.strings_nl[help_text] = i18n.help_nl;
    i18n.strings_de[help_text] = i18n.help_de;
    i18n.strings_pl[help_text] = i18n.help_pl;
    i18n.strings_fr[help_text] = i18n.help_fr;
    i18n.strings_hu[help_text] = i18n.help_hu;
    i18n.strings_it[help_text] = i18n.help_it;
    i18n.strings_el[help_text] = i18n.help_el;
    i18n.strings_ja[help_text] = i18n.help_ja;
    i18n.strings_zh[help_text] = i18n.help_zh;
    i18n.strings_ru[help_text] = i18n.help_ru;
    i18n.strings_sv[help_text] = i18n.help_sv;

    w3c_slidy.lang = document.body.parentNode.getAttribute("lang");

    if (!w3c_slidy.lang)
      w3c_slidy.lang = document.body.parentNode.getAttribute("xml:lang");

    if (!w3c_slidy.lang)
      w3c_slidy.lang = "en";
  }
};

// hack for back button behavior
if (w3c_slidy.ie6 || w3c_slidy.ie7)
{
  document.write("<iframe id='historyFrame' " +
  "src='javascript:\"<html"+"></"+"html>\"' " +
  "height='1' width='1' " +
  "style='position:absolute;left:-800px'></iframe>");
}

// attach event listeners for initialization
w3c_slidy.set_up();

// hide the slides as soon as body element is available
// to reduce annoying screen mess before the onload event
setTimeout(w3c_slidy.hide_slides, 50);

