', {
      'class': 'h5p-joubelui-progressbar-background'
    }).appendTo(this.$progressbar);
    $('body').click(function () {
      self.toggleTooltip(true);
    });
  }
  JoubelProgressbar.prototype = Object.create(H5P.EventDispatcher.prototype);
  JoubelProgressbar.prototype.constructor = JoubelProgressbar;
  /**
   * Display tooltip
   * @method showTooltip
   */
  JoubelProgressbar.prototype.showTooltip = function () {
    var self = this;
    if (this.currentStep === 0 || this.tooltip !== undefined) {
      return;
    }
    var parentWidth = self.$progressbar.width();
    this.tooltip = new H5P.Drop({
      target: this.$background.get(0),
      content: this.currentStep + '/' + this.steps,
      classes: 'drop-theme-arrows-bounce h5p-joubelui-drop',
      position: 'top right',
      openOn: 'always',
      tetherOptions: {
        attachment: 'bottom center',
        targetAttachment: 'top right'
      }
    });
    this.tooltip.on('open', function () {
      var $drop = $(self.tooltip.drop);
      var left = $drop.position().left;
      var dropWidth = $drop.width();
      // Need to handle drops getting outside of the progressbar:
      if (left < 0) {
        $drop.css({marginLeft: (-left) + 'px'});
      }
      else if (left + dropWidth > parentWidth) {
        $drop.css({marginLeft: (parentWidth - (left + dropWidth)) + 'px'});
      }
    });
  };
  JoubelProgressbar.prototype.updateAria = function () {
    var self = this;
    if (this.options.disableAria) {
      return;
    }
    if (!this.$currentStatus) {
      this.$currentStatus = $('
', {
        'class': 'h5p-joubelui-progressbar-slide-status-text',
        'aria-live': 'assertive'
      }).appendTo(this.$progressbar);
    }
    var interpolatedProgressText = self.options.progressText
      .replace(':num', self.currentStep)
      .replace(':total', self.steps);
    this.$currentStatus.html(interpolatedProgressText);
  };
  /**
   * Hides tooltip
   * @method hideTooltip
   */
  JoubelProgressbar.prototype.hideTooltip = function () {
    if (this.tooltip !== undefined) {
      this.tooltip.remove();
      this.tooltip.destroy();
      this.tooltip = undefined;
    }
  };
  /**
   * Toggles tooltip-visibility
   * @method toggleTooltip
   * @param  {boolean} [closeOnly] Don't show, only close if open
   */
  JoubelProgressbar.prototype.toggleTooltip = function (closeOnly) {
    if (this.tooltip === undefined && !closeOnly) {
      this.showTooltip();
    }
    else if (this.tooltip !== undefined) {
      this.hideTooltip();
    }
  };
  /**
   * Appends to a container
   * @method appendTo
   * @param  {H5P.jquery} $container
   */
  JoubelProgressbar.prototype.appendTo = function ($container) {
    this.$progressbar.appendTo($container);
  };
  /**
   * Update progress
   * @method setProgress
   * @param  {number}    step
   */
  JoubelProgressbar.prototype.setProgress = function (step) {
    // Check for valid value:
    if (step > this.steps || step < 0) {
      return;
    }
    this.currentStep = step;
    this.$background.css({
      width: ((this.currentStep/this.steps)*100) + '%'
    });
    this.updateAria();
  };
  /**
   * Increment progress with 1
   * @method next
   */
  JoubelProgressbar.prototype.next = function () {
    this.setProgress(this.currentStep+1);
  };
  /**
   * Reset progressbar
   * @method reset
   */
  JoubelProgressbar.prototype.reset = function () {
    this.setProgress(0);
  };
  /**
   * Check if last step is reached
   * @method isLastStep
   * @return {Boolean}
   */
  JoubelProgressbar.prototype.isLastStep = function () {
    return this.steps === this.currentStep;
  };
  return JoubelProgressbar;
})(H5P.jQuery);
;
var H5P = H5P || {};
/**
 * H5P Joubel UI library.
 *
 * This is a utility library, which does not implement attach. I.e, it has to bee actively used by
 * other libraries
 * @module
 */
H5P.JoubelUI = (function ($) {
  /**
   * The internal object to return
   * @class H5P.JoubelUI
   * @static
   */
  function JoubelUI() {}
  /* Public static functions */
  /**
   * Create a tip icon
   * @method H5P.JoubelUI.createTip
   * @param  {string}  text   The textual tip
   * @param  {Object}  params Parameters
   * @return {H5P.JoubelTip}
   */
  JoubelUI.createTip = function (text, params) {
    return new H5P.JoubelTip(text, params);
  };
  /**
   * Create message dialog
   * @method H5P.JoubelUI.createMessageDialog
   * @param  {H5P.jQuery}               $container The dom container
   * @param  {string}                   message    The message
   * @return {H5P.JoubelMessageDialog}
   */
  JoubelUI.createMessageDialog = function ($container, message) {
    return new H5P.JoubelMessageDialog($container, message);
  };
  /**
   * Create help text dialog
   * @method H5P.JoubelUI.createHelpTextDialog
   * @param  {string}             header  The textual header
   * @param  {string}             message The textual message
   * @return {H5P.JoubelHelpTextDialog}
   */
  JoubelUI.createHelpTextDialog = function (header, message) {
    return new H5P.JoubelHelpTextDialog(header, message);
  };
  /**
   * Create progress circle
   * @method H5P.JoubelUI.createProgressCircle
   * @param  {number}             number          The progress (0 to 100)
   * @param  {string}             progressColor   The progress color in hex value
   * @param  {string}             fillColor       The fill color in hex value
   * @param  {string}             backgroundColor The background color in hex value
   * @return {H5P.JoubelProgressCircle}
   */
  JoubelUI.createProgressCircle = function (number, progressColor, fillColor, backgroundColor) {
    return new H5P.JoubelProgressCircle(number, progressColor, fillColor, backgroundColor);
  };
  /**
   * Create throbber for loading
   * @method H5P.JoubelUI.createThrobber
   * @return {H5P.JoubelThrobber}
   */
  JoubelUI.createThrobber = function () {
    return new H5P.JoubelThrobber();
  };
  /**
   * Create simple rounded button
   * @method H5P.JoubelUI.createSimpleRoundedButton
   * @param  {string}                  text The button label
   * @return {H5P.SimpleRoundedButton}
   */
  JoubelUI.createSimpleRoundedButton = function (text) {
    return new H5P.SimpleRoundedButton(text);
  };
  /**
   * Create Slider
   * @method H5P.JoubelUI.createSlider
   * @param  {Object} [params] Parameters
   * @return {H5P.JoubelSlider}
   */
  JoubelUI.createSlider = function (params) {
    return new H5P.JoubelSlider(params);
  };
  /**
   * Create Score Bar
   * @method H5P.JoubelUI.createScoreBar
   * @param  {number=}       maxScore The maximum score
   * @param {string} [label] Makes it easier for readspeakers to identify the scorebar
   * @return {H5P.JoubelScoreBar}
   */
  JoubelUI.createScoreBar = function (maxScore, label) {
    return new H5P.JoubelScoreBar(maxScore, label);
  };
  /**
   * Create Progressbar
   * @method H5P.JoubelUI.createProgressbar
   * @param  {number=}       numSteps The total numer of steps
   * @param {Object} [options] Additional options
   * @param {boolean} [options.disableAria] Disable readspeaker assistance
   * @param {string} [options.progressText] A progress text for describing
   *  current progress out of total progress for readspeakers.
   *  e.g. "Slide :num of :total"
   * @return {H5P.JoubelProgressbar}
   */
  JoubelUI.createProgressbar = function (numSteps, options) {
    return new H5P.JoubelProgressbar(numSteps, options);
  };
  /**
   * Create standard Joubel button
   *
   * @method H5P.JoubelUI.createButton
   * @param {object} params
   *  May hold any properties allowed by jQuery. If href is set, an A tag
   *  is used, if not a button tag is used.
   * @return {H5P.jQuery} The jquery element created
   */
  JoubelUI.createButton = function(params) {
    var type = 'button';
    if (params.href) {
      type = 'a';
    }
    else {
      params.type = 'button';
    }
    if (params.class) {
      params.class += ' h5p-joubelui-button';
    }
    else {
      params.class = 'h5p-joubelui-button';
    }
    return $('<' + type + '/>', params);
  };
  return JoubelUI;
})(H5P.jQuery);
;
H5P.Question = (function ($, EventDispatcher, JoubelUI) {
  /**
   * Extending this class make it alot easier to create tasks for other
   * content types.
   *
   * @class H5P.Question
   * @extends H5P.EventDispatcher
   * @param {string} type
   */
  function Question(type) {
    var self = this;
    // Inheritance
    EventDispatcher.call(self);
    // Register default section order
    self.order = ['video', 'image', 'introduction', 'content', 'feedback', 'buttons', 'read'];
    // Keep track of registered sections
    var sections = {};
    // Buttons
    var buttons = {};
    var buttonOrder = [];
    // Wrapper when attached
    var $wrapper;
    // ScoreBar
    var scoreBar;
    // Keep track of the feedback's visual status.
    var showFeedback;
    // Keep track of which buttons are scheduled for hiding.
    var buttonsToHide = [];
    // Keep track of which buttons are scheduled for showing.
    var buttonsToShow = [];
    // Keep track of the hiding and showing of buttons.
    var toggleButtonsTimer;
    var toggleButtonsTransitionTimer;
    var buttonTruncationTimer;
    // Keeps track of initialization of question
    var initialized = false;
    /**
     * @type {Object} behaviour Behaviour of Question
     * @property {Boolean} behaviour.disableFeedback Set to true to disable feedback section
     */
    var behaviour = {
      disableFeedback: false,
      disableReadSpeaker: false
    };
    // Keeps track of thumb state
    var imageThumb = true;
    // Keeps track of image transitions
    var imageTransitionTimer;
    // Keep track of whether sections is transitioning.
    var sectionsIsTransitioning = false;
    // Keep track of auto play state
    var disableAutoPlay = false;
    // Feedback transition timer
    var feedbackTransitionTimer;
    // Used when reading messages to the user
    var $read, readText;
    /**
     * Register section with given content.
     *
     * @private
     * @param {string} section ID of the section
     * @param {(string|H5P.jQuery)} [content]
     */
    var register = function (section, content) {
      sections[section] = {};
      var $e = sections[section].$element = $('
', {
        'class': 'h5p-question-' + section,
      });
      if (content) {
        $e[content instanceof $ ? 'append' : 'html'](content);
      }
    };
    /**
     * Update registered section with content.
     *
     * @private
     * @param {string} section ID of the section
     * @param {(string|H5P.jQuery)} content
     */
    var update = function (section, content) {
      if (content instanceof $) {
        sections[section].$element.html('').append(content);
      }
      else {
        sections[section].$element.html(content);
      }
    };
    /**
     * Insert element with given ID into the DOM.
     *
     * @private
     * @param {array|Array|string[]} order
     * List with ordered element IDs
     * @param {string} id
     * ID of the element to be inserted
     * @param {Object} elements
     * Maps ID to the elements
     * @param {H5P.jQuery} $container
     * Parent container of the elements
     */
    var insert = function (order, id, elements, $container) {
      // Try to find an element id should be after
      for (var i = 0; i < order.length; i++) {
        if (order[i] === id) {
          // Found our pos
          while (i > 0 &&
          (elements[order[i - 1]] === undefined ||
          !elements[order[i - 1]].isVisible)) {
            i--;
          }
          if (i === 0) {
            // We are on top.
            elements[id].$element.prependTo($container);
          }
          else {
            // Add after element
            elements[id].$element.insertAfter(elements[order[i - 1]].$element);
          }
          elements[id].isVisible = true;
          break;
        }
      }
    };
    /**
     * Set element max height, used for animations.
     *
     * @param {H5P.jQuery} $element
     */
    var setElementHeight = function ($element) {
      if (!$element.is(':visible')) {
        // No animation
        $element.css('max-height', 'none');
        return;
      }
      // Get natural element height
      var $tmp = $element.clone()
        .css({
          'position': 'absolute',
          'max-height': 'none'
        }).appendTo($element.parent());
      // Apply height to element
      var h = Math.round($tmp.get(0).getBoundingClientRect().height);
      var fontSize = parseFloat($element.css('fontSize'));
      var relativeH = h / fontSize;
      $element.css('max-height', relativeH + 'em');
      $tmp.remove();
      if (h > 0 && sections.buttons && sections.buttons.$element === $element) {
        // Make sure buttons section is visible
        sections.buttons.$element.addClass('h5p-question-visible');
        // Resize buttons after resizing button section
        setTimeout(function () {
          resizeButtons();
        }, 150);
      }
      return h;
    };
    /**
     * Does the actual job of hiding the buttons scheduled for hiding.
     *
     * @private
     * @param {boolean} [relocateFocus] Find a new button to focus
     */
    var hideButtons = function (relocateFocus) {
      for (var i = 0; i < buttonsToHide.length; i++) {
        hideButton(buttonsToHide[i].id);
      }
      buttonsToHide = [];
      if (relocateFocus) {
        self.focusButton();
      }
    };
    /**
     * Does the actual hiding.
     * @private
     * @param {string} buttonId
     */
    var hideButton = function (buttonId) {
      // Using detach() vs hide() makes it harder to cheat.
      buttons[buttonId].$element.detach();
      buttons[buttonId].isVisible = false;
    };
    /**
     * Shows the buttons on the next tick. This is to avoid buttons flickering
     * If they're both added and removed on the same tick.
     *
     * @private
     */
    var toggleButtons = function () {
      // Clear transition timer, reevaluate if buttons will be detached
      clearTimeout(toggleButtonsTransitionTimer);
      // Show buttons
      for (var i = 0; i < buttonsToShow.length; i++) {
        insert(buttonOrder, buttonsToShow[i].id, buttons, sections.buttons.$element);
        buttons[buttonsToShow[i].id].isVisible = true;
      }
      buttonsToShow = [];
      // Hide buttons
      var numToHide = 0;
      var relocateFocus = false;
      for (var j = 0; j < buttonsToHide.length; j++) {
        var button = buttons[buttonsToHide[j].id];
        if (button.isVisible) {
          numToHide += 1;
        }
        if (button.$element.is(':focus')) {
          // Move focus to the first visible button.
          relocateFocus = true;
        }
      }
      if (sections.buttons && numToHide === sections.buttons.$element.children().length) {
        // All buttons are going to be hidden. Hide container using transition.
        sections.buttons.$element.removeClass('h5p-question-visible');
        sections.buttons.$element.css('max-height', '');
        sectionsIsTransitioning = true;
        // Wait for animations before detaching buttons
        toggleButtonsTransitionTimer = setTimeout(function () {
          hideButtons(relocateFocus);
          sectionsIsTransitioning = false;
        }, 150);
      }
      else {
        hideButtons(relocateFocus);
        // Show button section
        if (!sections.buttons.$element.is(':empty')) {
          sections.buttons.$element.addClass('h5p-question-visible');
          setElementHeight(sections.buttons.$element);
          // Trigger resize after animation
          toggleButtonsTransitionTimer = setTimeout(function () {
            self.trigger('resize');
          }, 150);
        }
      }
      // Resize buttons to fit container
      resizeButtons();
      toggleButtonsTimer = undefined;
    };
    /**
     * Allows for scaling of the question image.
     */
    var scaleImage = function () {
      var $imgSection = sections.image.$element;
      clearTimeout(imageTransitionTimer);
      // Add this here to avoid initial transition of the image making
      // content overflow. Alternatively we need to trigger a resize.
      $imgSection.addClass('animatable');
      if (imageThumb) {
        // Expand image
        $(this).attr('aria-expanded', true);
        $imgSection.addClass('h5p-question-image-fill-width');
        imageThumb = false;
        imageTransitionTimer = setTimeout(function () {
          self.trigger('resize');
        }, 600);
      }
      else {
        // Scale down image
        $(this).attr('aria-expanded', false);
        $imgSection.removeClass('h5p-question-image-fill-width');
        imageThumb = true;
        imageTransitionTimer = setTimeout(function () {
          self.trigger('resize');
        }, 600);
      }
    };
    /**
     * Get scrollable ancestor of element
     *
     * @private
     * @param {H5P.jQuery} $element
     * @param {Number} [currDepth=0] Current recursive calls to ancestor, stop at maxDepth
     * @param {Number} [maxDepth=5] Maximum depth for finding ancestor.
     * @returns {H5P.jQuery} Parent element that is scrollable
     */
    var findScrollableAncestor = function ($element, currDepth, maxDepth) {
      if (!currDepth) {
        currDepth = 0;
      }
      if (!maxDepth) {
        maxDepth = 5;
      }
      // Check validation of element or if we have reached document root
      if (!$element || !($element instanceof $) || document === $element.get(0) || currDepth >= maxDepth) {
        return;
      }
      if ($element.css('overflow-y') === 'auto') {
        return $element;
      }
      else {
        return findScrollableAncestor($element.parent(), currDepth + 1, maxDepth);
      }
    };
    /**
     * Scroll to bottom of Question.
     *
     * @private
     */
    var scrollToBottom = function () {
      if (!$wrapper || ($wrapper.hasClass('h5p-standalone') && !H5P.isFullscreen)) {
        return; // No scroll
      }
      var scrollableAncestor = findScrollableAncestor($wrapper);
      // Scroll to bottom of scrollable ancestor
      if (scrollableAncestor) {
        scrollableAncestor.animate({
          scrollTop: $wrapper.css('height')
        }, "slow");
      }
    };
    /**
     * Resize buttons to fit container width
     *
     * @private
     */
    var resizeButtons = function () {
      if (!buttons || !sections.buttons) {
        return;
      }
      // Clear button truncation timer if within a button truncation function
      if (buttonTruncationTimer) {
        clearTimeout(buttonTruncationTimer);
      }
      // Allow button section to attach before getting width
      buttonTruncationTimer = setTimeout(function () {
        // A static margin is added as buffer for smoother transitions
        var buttonsWidth = 0;
        for (var i in buttons) {
          var $element = buttons[i].$element;
          if (buttons[i].isVisible) {
            //Calculate exact button width
            var buttonInstanceWidth = $element.get(0).offsetWidth +
              parseFloat($element.css('margin-left')) +
              parseFloat($element.css('margin-right'));
            buttonsWidth += Math.ceil(buttonInstanceWidth) + 1;
          }
        }
        // Button section reduced by 1 pixel for cross-broswer consistency.
        var buttonSectionWidth = Math.floor(sections.buttons.$element.get(0).offsetWidth) - 1;
        // Remove button labels if width of buttons are too wide
        if (buttonsWidth >= buttonSectionWidth) {
          removeButtonLabels(buttonsWidth, buttonSectionWidth);
        }
        else {
          restoreButtonLabels(buttonsWidth, buttonSectionWidth);
        }
        buttonTruncationTimer = undefined;
      }, 0);
    };
    /**
     * Remove button labels until they use less than max width.
     *
     * @private
     * @param {Number} buttonsWidth Total width of all buttons
     * @param {Number} maxButtonsWidth Max width allowed for buttons
     */
    var removeButtonLabels = function (buttonsWidth, maxButtonsWidth) {
      // Reverse traversal
      for (var i = buttonOrder.length - 1; i >= 0; i--) {
        var buttonId = buttonOrder[i];
        if (!buttons[buttonId].isTruncated && buttons[buttonId].isVisible) {
          var $button = buttons[buttonId].$element;
          var $tmp = $button.clone()
            .css({
              'position': 'absolute',
              'white-space': 'nowrap',
              'max-width': 'none'
            })
            .addClass('truncated')
            .html('')
            .appendTo($button.parent());
          // Calculate new total width of buttons
          buttonsWidth = buttonsWidth - $button.outerWidth(true) + $tmp.outerWidth(true);
          // Remove label
          $button.html('');
          $button.addClass('truncated');
          buttons[buttonId].isTruncated = true;
          $tmp.remove();
          if (buttonsWidth < maxButtonsWidth) {
            // Buttons are small enough.
            return;
          }
        }
      }
    };
    /**
     * Restore button labels until it fills maximum possible width without exceeding the max width.
     *
     * @private
     * @param {Number} buttonsWidth Total width of all buttons
     * @param {Number} maxButtonsWidth Max width allowed for buttons
     */
    var restoreButtonLabels = function (buttonsWidth, maxButtonsWidth) {
      for (var i = 0; i < buttonOrder.length; i++) {
        var buttonId = buttonOrder[i];
        if (buttons[buttonId].isTruncated && buttons[buttonId].isVisible) {
          // Check if adding label exceeds allowed width
          var $button = buttons[buttonId].$element;
          var $tmp = $button.clone()
            .css({
              'position': 'absolute',
              'white-space': 'nowrap',
              'max-width': 'none'
            }).removeClass('truncated')
            .html(buttons[buttonId].text)
            .appendTo($button.parent());
          // Make sure clone was successfull
          if(!$button.length || !$tmp.length) {
            return;
          }
          var oldButtonSize = Math.floor($button.get(0).offsetWidth) - 1;
          var newButtonSize = Math.ceil($tmp.get(0).offsetWidth) + 1;
          // Calculate new total width of buttons with a static pixel for consistency cross-browser
          buttonsWidth = buttonsWidth - Math.floor(oldButtonSize) + Math.ceil(newButtonSize) + 1;
          $tmp.remove();
          if (buttonsWidth >= maxButtonsWidth) {
            return;
          }
          // Restore label
          $button.html(buttons[buttonId].text);
          $button.removeClass('truncated');
          buttons[buttonId].isTruncated = false;
        }
      }
    };
    /**
     * Helper function for finding index of keyValue in array
     *
     * @param {String} keyValue Value to be found
     * @param {String} key In key
     * @param {Array} array In array
     * @returns {number}
     */
    var existsInArray = function (keyValue, key, array) {
      var i;
      for (i = 0; i < array.length; i++) {
        if (array[i][key] === keyValue) {
          return i;
        }
      }
      return -1;
    };
    /**
     * Set behaviour for question.
     *
     * @param {Object} options An object containing behaviour that will be extended by Question
     */
    self.setBehaviour = function (options) {
      $.extend(behaviour, options);
    };
    /**
     * A video to display above the task.
     *
     * @param {object} params
     */
    self.setVideo = function (params) {
      sections.video = {
        $element: $('
', {
          'class': 'h5p-question-video'
        })
      };
      if (disableAutoPlay) {
        params.params.autoplay = false;
      }
      // Never fit to wrapper
      params.params.fit = false;
      sections.video.instance = H5P.newRunnable(params, self.contentId, sections.video.$element, true);
      var fromVideo = false; // Hack to avoid never ending loop
      sections.video.instance.on('resize', function () {
        fromVideo = true;
        self.trigger('resize');
        fromVideo = false;
      });
      self.on('resize', function () {
        if (!fromVideo) {
          sections.video.instance.trigger('resize');
        }
      });
      return self;
    };
    /**
     * Will stop any playback going on in the task.
     */
    self.pause = function () {
      if (sections.video && sections.video.isVisible) {
        sections.video.instance.pause();
      }
    };
    /**
     * Start playback of video
     */
    self.play = function () {
      if (sections.video && sections.video.isVisible) {
        sections.video.instance.play();
      }
    };
    /**
     * Disable auto play, useful in editors.
     */
    self.disableAutoPlay = function () {
      disableAutoPlay = true;
    };
    /**
     * Add task image.
     *
     * @param {string} path Relative
     * @param {Object} [options] Options object
     * @param {string} [options.alt] Text representation
     * @param {Boolean} [options.disableImageZooming] Set as true to disable image zooming
     */
    self.setImage = function (path, options) {
      options = options ? options : {};
      sections.image = {};
      // Image container
      sections.image.$element = $('
', {
        'class': 'h5p-question-image h5p-question-image-fill-width'
      });
      // Inner wrap
      var $imgWrap = $('
', {
        'class': 'h5p-question-image-wrap',
        appendTo: sections.image.$element
      });
      // Image element
      var $img = $('
![]()
', {
        src: H5P.getPath(path, self.contentId),
        alt: (options.alt === undefined ? '' : options.alt),
        on: {
          load: function () {
            self.trigger('imageLoaded', this);
            self.trigger('resize');
          }
        },
        appendTo: $imgWrap
      });
      // Disable image zooming
      if (options.disableImageZooming) {
        $img.css('maxHeight', 'none');
        // Make sure we are using the correct amount of width at all times
        var determineImgWidth = function () {
          // Remove margins if natural image width is bigger than section width
          var imageSectionWidth = sections.image.$element.get(0).getBoundingClientRect().width;
          // Do not transition, for instant measurements
          $imgWrap.css({
            '-webkit-transition': 'none',
            'transition': 'none'
          });
          // Margin as translateX on both sides of image.
          var diffX = 2 * ($imgWrap.get(0).getBoundingClientRect().left -
            sections.image.$element.get(0).getBoundingClientRect().left);
          if ($img.get(0).naturalWidth >= imageSectionWidth - diffX) {
            sections.image.$element.addClass('h5p-question-image-fill-width');
          }
          else { // Use margin for small res images
            sections.image.$element.removeClass('h5p-question-image-fill-width');
          }
          // Reset transition rules
          $imgWrap.css({
            '-webkit-transition': '',
            'transition': ''
          });
        };
        // Determine image width
        if ($img.is(':visible')) {
          determineImgWidth();
        }
        else {
          $img.load(function () {
            determineImgWidth();
          });
        }
        // Skip adding zoom functionality
        return;
      }
      var sizeDetermined = false;
      var determineSize = function () {
        if (sizeDetermined || !$img.is(':visible')) {
          return; // Try again next time.
        }
        $imgWrap.addClass('h5p-question-image-scalable')
          .attr('aria-expanded', false)
          .attr('role', 'button')
          .attr('tabIndex', '0')
          .on('click', function (event) {
            if (event.which === 1) {
              scaleImage.apply(this); // Left mouse button click
            }
          }).on('keypress', function (event) {
          if (event.which === 32) {
            scaleImage.apply(this); // Space bar pressed
          }
        });
        sections.image.$element.removeClass('h5p-question-image-fill-width');
        sizeDetermined  = true; // Prevent any futher events
      };
      self.on('resize', determineSize);
      return self;
    };
    /**
     * Add the introduction section.
     *
     * @param {(string|H5P.jQuery)} content
     */
    self.setIntroduction = function (content) {
      register('introduction', content);
      return self;
    };
    /**
     * Add the content section.
     *
     * @param {(string|H5P.jQuery)} content
     * @param {Object} [options]
     * @param {string} [options.class]
     */
    self.setContent = function (content, options) {
      register('content', content);
      if (options && options.class) {
        sections.content.$element.addClass(options.class);
      }
      return self;
    };
    /**
     * Force readspeaker to read text. Useful when you have to use
     * setTimeout for animations.
     */
    self.read = function (content) {
      if (!$read) {
        return; // Not ready yet
      }
      if (readText) {
        // Combine texts if called multiple times
        readText += (readText.substr(-1, 1) === '.' ? ' ' : '. ') + content
      }
      else {
        readText = content;
      }
      // Set text
      $read.html(readText);
      setTimeout(function () {
        // Stop combining when done reading
        readText = null;
        $read.html('');
      }, 100);
    };
    /**
     * Read feedback
     */
    self.readFeedback = function () {
      var invalidFeedback =
        behaviour.disableReadSpeaker ||
        !showFeedback ||
        !sections.feedback ||
        !sections.feedback.$element;
      if (invalidFeedback) {
        return;
      }
      var $feedback = $('.h5p-question-feedback-content', sections.feedback.$element);
      if ($feedback && $feedback.html() && $feedback.html().length) {
        self.read($feedback.html());
      }
    };
    /**
     * Set feedback message.
     * Setting the message to blank or undefined will hide it again.
     *
     * @param {string} content
     * @param {number} score The score
     * @param {number} maxScore The maximum score for this question
     * @param {string} [scoreBarLabel] Makes it easier for readspeakers to identify the scorebar
     */
    self.setFeedback = function (content, score, maxScore, scoreBarLabel) {
      // Feedback is disabled
      if (behaviour.disableFeedback) {
        return self;
      }
      clearTimeout(feedbackTransitionTimer);
      if (content) {
        var $feedback = $('
', {
          'class': 'h5p-question-feedback-container'
        });
        if (scoreBar === undefined) {
          scoreBar = JoubelUI.createScoreBar(maxScore, scoreBarLabel);
        }
        scoreBar.appendTo($feedback);
        scoreBar.setScore(score);
        $feedback.append($('
', {
          'class': 'h5p-question-feedback-content',
          'html': content
        }));
        // Feedback for readspeakers
        if (!behaviour.disableReadSpeaker) {
          self.read(content);
        }
        showFeedback = true;
        if (sections.feedback) {
          // Update section
          update('feedback', $feedback);
        }
        else {
          // Create section
          register('feedback', $feedback);
          if (initialized && $wrapper) {
            insert(self.order, 'feedback', sections, $wrapper);
          }
        }
        // Show feedback section
        feedbackTransitionTimer = setTimeout(function () {
          sections.feedback.$element.addClass('h5p-question-visible');
          setElementHeight(sections.feedback.$element);
          sectionsIsTransitioning = true;
          // Scroll to bottom after showing feedback
          scrollToBottom();
          // Trigger resize after animation
          feedbackTransitionTimer = setTimeout(function () {
            sectionsIsTransitioning = false;
            self.trigger('resize');
          }, 150);
        }, 0);
      }
      else if (sections.feedback && showFeedback) {
        showFeedback = false;
        // Hide feedback section
        sections.feedback.$element.removeClass('h5p-question-visible');
        sections.feedback.$element.css('max-height', '');
        sectionsIsTransitioning = true;
        // Detach after transition
        feedbackTransitionTimer = setTimeout(function () {
          // Avoiding Transition.onTransitionEnd since it will register multiple events, and there's no way to cancel it if the transition changes back to "show" while the animation is happening.
          if (!showFeedback) {
            sections.feedback.$element.children().detach();
            // Trigger resize after animation
            self.trigger('resize');
          }
          sectionsIsTransitioning = false;
          scoreBar.setScore(0);
        }, 150);
      }
      return self;
    };
    /**
     * Set feedback content (no animation).
     *
     * @param {string} content
     * @param {boolean} [extendContent] True will extend content, instead of replacing it
     */
    self.updateFeedbackContent = function (content, extendContent) {
      if (sections.feedback && sections.feedback.$element) {
        if (extendContent) {
          content = $('.h5p-question-feedback-content', sections.feedback.$element).html() + ' ' + content;
        }
        // Update feedback content html
        $('.h5p-question-feedback-content', sections.feedback.$element).html(content);
      }
      return self;
    };
    /**
     * Checks to see if button is registered.
     *
     * @param {string} id
     * @returns {boolean}
     */
    self.hasButton = function (id) {
      return (buttons[id] !== undefined);
    };
    /**
     * @typedef {Object} ConfirmationDialog
     * @property {boolean} [enable] Must be true to show confirmation dialog
     * @property {Object} [instance] Instance that uses confirmation dialog
     * @property {jQuery} [$parentElement] Append to this element.
     * @property {Object} [l10n] Translatable fields
     * @property {string} [l10n.header] Header text
     * @property {string} [l10n.body] Body text
     * @property {string} [l10n.cancelLabel]
     * @property {string} [l10n.confirmLabel]
     */
    /**
     * Register buttons for the task.
     *
     * @param {string} id
     * @param {string} text label
     * @param {function} clicked
     * @param {boolean} [visible=true]
     * @param {Object} [options] Options for button
     * @param {Object} [extras] Extra options
     * @param {ConfirmationDialog} [extras.confirmationDialog] Confirmation dialog
     */
    self.addButton = function (id, text, clicked, visible, options, extras) {
      if (buttons[id]) {
        return self; // Already registered
      }
      if (sections.buttons === undefined)  {
        // We have buttons, register wrapper
        register('buttons');
        if (initialized) {
          insert(self.order, 'buttons', sections, $wrapper);
        }
      }
      extras = extras || {};
      extras.confirmationDialog = extras.confirmationDialog || {};
      options = options || {};
      var confirmationDialog =
        self.addConfirmationDialogToButton(extras.confirmationDialog, clicked);
      /**
       * Handle button clicks through both mouse and keyboard
       * @private
       */
      var handleButtonClick = function () {
        if (extras.confirmationDialog.enable && confirmationDialog) {
          // Show popups section if used
          if (!extras.confirmationDialog.$parentElement) {
            sections.popups.$element.removeClass('hidden');
          }
          confirmationDialog.show($e.position().top);
        }
        else {
          clicked();
        }
      };
      buttons[id] = {
        isTruncated: false,
        text: text
      };
      var $e = buttons[id].$element = JoubelUI.createButton($.extend({
        'class': 'h5p-question-' + id,
        html: text,
        on: {
          click: function (event) {
            handleButtonClick();
            if (options.href !== undefined) {
              event.preventDefault();
            }
          },
          keydown: function (event) {
            switch (event.which) {
              case 13: // Enter
              case 32: // Space
                handleButtonClick();
                event.preventDefault();
            }
          }
        }
      }, options));
      buttonOrder.push(id);
      if (visible === undefined || visible) {
        // Button should be visible
        $e.appendTo(sections.buttons.$element);
        buttons[id].isVisible = true;
        sections.buttons.$element.addClass('h5p-question-visible');
      }
      return self;
    };
    /**
     * Add confirmation dialog to button
     * @param {ConfirmationDialog} options
     *  A confirmation dialog that will be shown before click handler of button
     *  is triggered
     * @param {function} clicked
     *  Click handler of button
     * @return {H5P.ConfirmationDialog|undefined}
     *  Confirmation dialog if enabled
     */
    self.addConfirmationDialogToButton = function (options, clicked) {
      options = options || {};
      if (!options.enable) {
        return;
      }
      // Confirmation dialog
      var confirmationDialog = new H5P.ConfirmationDialog({
        instance: options.instance,
        headerText: options.l10n.header,
        dialogText: options.l10n.body,
        cancelText: options.l10n.cancelLabel,
        confirmText: options.l10n.confirmLabel
      });
      // Determine parent element
      if (options.$parentElement) {
        confirmationDialog.appendTo(options.$parentElement.get(0));
      }
      else {
        // Create popup section and append to that
        if (sections.popups === undefined) {
          register('popups');
          if (initialized) {
            insert(self.order, 'popups', sections, $wrapper);
          }
          sections.popups.$element.addClass('hidden');
          self.order.push('popups');
        }
        confirmationDialog.appendTo(sections.popups.$element.get(0));
      }
      // Add event listeners
      confirmationDialog.on('confirmed', function () {
        if (!options.$parentElement) {
          sections.popups.$element.addClass('hidden');
        }
        clicked();
        // Trigger to content type
        self.trigger('confirmed');
      });
      confirmationDialog.on('canceled', function () {
        if (!options.$parentElement) {
          sections.popups.$element.addClass('hidden');
        }
        // Trigger to content type
        self.trigger('canceled');
      });
      return confirmationDialog;
    };
    /**
     * Show registered button with given identifier.
     *
     * @param {string} id
     * @param {Number} [priority]
     */
    self.showButton = function (id, priority) {
      if (buttons[id] === undefined) {
        return self;
      }
      priority = priority || 0;
      // Skip if already being shown
      var indexToShow = existsInArray(id, 'id', buttonsToShow);
      if (indexToShow !== -1) {
        // Update priority
        if (buttonsToShow[indexToShow].priority < priority) {
          buttonsToShow[indexToShow].priority = priority;
        }
        return self;
      }
      // Check if button is going to be hidden on next tick
      var exists = existsInArray(id, 'id', buttonsToHide);
      if (exists !== -1) {
        // Skip hiding if higher priority
        if (buttonsToHide[exists].priority <= priority) {
          buttonsToHide.splice(exists, 1);
          buttonsToShow.push({id: id, priority: priority});
        }
      } // If button is not shown
      else if (!buttons[id].$element.is(':visible')) {
        // Show button on next tick
        buttonsToShow.push({id: id, priority: priority});
      }
      if (!toggleButtonsTimer) {
        toggleButtonsTimer = setTimeout(toggleButtons, 0);
      }
      return self;
    };
    /**
     * Hide registered button with given identifier.
     *
     * @param {string} id
     * @param {number} [priority]
     */
    self.hideButton = function (id, priority) {
      if (buttons[id] === undefined) {
        return self;
      }
      priority = priority || 0;
      // Skip if already being hidden
      var indexToHide = existsInArray(id, 'id', buttonsToHide);
      if (indexToHide !== -1) {
        // Update priority
        if (buttonsToHide[indexToHide].priority < priority) {
          buttonsToHide[indexToHide].priority = priority;
        }
        return self;
      }
      // Check if buttons is going to be shown on next tick
      var exists = existsInArray(id, 'id', buttonsToShow);
      if (exists !== -1) {
        // Skip showing if higher priority
        if (buttonsToShow[exists].priority <= priority) {
          buttonsToShow.splice(exists, 1);
          buttonsToHide.push({id: id, priority: priority});
        }
      }
      else if (!buttons[id].$element.is(':visible')) {
        // Make sure it is detached in case the container is hidden.
        hideButton(id);
      }
      else {
        // Hide button on next tick.
        buttonsToHide.push({id: id, priority: priority});
      }
      if (!toggleButtonsTimer) {
        toggleButtonsTimer = setTimeout(toggleButtons, 0);
      }
      return self;
    };
    /**
     * Set focus to the given button. If no button is given the first visible
     * button gets focused. This is useful if you lose focus.
     *
     * @param {string} [id]
     */
    self.focusButton = function (id) {
      if (id === undefined) {
        // Find first button that is visible.
        for (var i = 0; i < buttonOrder.length; i++) {
          if (buttons[buttonOrder[i]].isVisible) {
            // Give that button focus
            buttons[buttonOrder[i]].$element.focus();
            break;
          }
        }
      }
      else if (buttons[id].$element.is(':visible')) {
        // Set focus to requested button
        buttons[id].$element.focus();
      }
      return self;
    };
    /**
     * Toggle readspeaker functionality
     * @param {boolean} [disable] True to disable, false to enable.
     */
    self.toggleReadSpeaker = function (disable) {
      behaviour.disableReadSpeaker = disable || !behaviour.disableReadSpeaker;
    };
    /**
     * Set new element for section.
     *
     * @param {String} id
     * @param {H5P.jQuery} $element
     */
    self.insertSectionAtElement = function (id, $element) {
      if (sections[id] === undefined) {
        register(id);
      }
      sections[id].parent = $element;
      // Insert section if question is not initialized
      if (!initialized) {
        insert([id], id, sections, $element);
      }
      return self;
    };
    /**
     * Attach content to given container.
     *
     * @param {H5P.jQuery} $container
     */
    self.attach = function ($container) {
      if (self.isRoot()) {
        self.setActivityStarted();
      }
      // The first time we attach we also create our DOM elements.
      if ($wrapper === undefined) {
        if (self.registerDomElements !== undefined &&
           (self.registerDomElements instanceof Function ||
           typeof self.registerDomElements === 'function')) {
           // Give the question type a chance to register before attaching
          self.registerDomElements();
        }
        // Create section for reading messages
        $read = $('
', {
          'aria-live': 'polite',
          'class': 'h5p-hidden-read'
        });
        register('read', $read);
        self.trigger('registerDomElements');
      }
      // Prepare container
      $wrapper = $container;
      $container.html('')
        .addClass('h5p-question h5p-' + type);
      // Add sections in given order
      var $sections = [];
      for (var i = 0; i < self.order.length; i++) {
        var section = self.order[i];
        if (sections[section]) {
          if (sections[section].parent) {
            // Section has a different parent
            sections[section].$element.appendTo(sections[section].parent);
          }
          else {
            $sections.push(sections[section].$element);
          }
          sections[section].isVisible = true;
        }
      }
      // Only append once to DOM for optimal performance
      $container.append($sections);
      // Let others react to dom changes
      self.trigger('domChanged', {
        '$target': $container,
        'library': self.libraryInfo.machineName,
        'contentId': self.contentId,
        'key': 'newLibrary'
      }, {'bubbles': true, 'external': true});
      // ??
      initialized = true;
      return self;
    };
    /**
     * Detach all sections from their parents
     */
    self.detachSections = function () {
      // Deinit Question
      initialized = false;
      // Detach sections
      for (var section in sections) {
        sections[section].$element.detach();
      }
      return self;
    };
    // Listen for resize
    self.on('resize', function () {
      // Allow elements to attach and set their height before resizing
      if (!sectionsIsTransitioning && sections.feedback && showFeedback) {
        // Resize feedback to fit
        setElementHeight(sections.feedback.$element);
      }
      resizeButtons();
    });
  }
  // Inheritance
  Question.prototype = Object.create(EventDispatcher.prototype);
  Question.prototype.constructor = Question;
  return Question;
})(H5P.jQuery, H5P.EventDispatcher, H5P.JoubelUI);
;
var H5P = H5P || {};
/**
 * Dialogcards module
 * author "papi Jo" Joseph Rézeau - based on H5P original Dialog Cards library
 *
 * @param {jQuery} $
 */
H5P.DialogcardsJR = (function ($, Audio, JoubelUI) {
  /**
   * Initialize module.
   *
   * @param {Object} params Behavior settings
   * @param {Number} id Content identification
   * @returns {C} self
   */
  function C(params, id) {
    var self = this;
    H5P.EventDispatcher.call(this);
    self.contentId = self.id = id;
    // Set default behavior. 
    
    self.params = $.extend({
      title: "Matching",
      description: "Look at the front of each card and try to guess what's on its back.",
      next: "Next",
      prev: "Previous",
      retry: "Retry",
      answer: "Turn",
      // JR added strings
      gotit: "OK",
      shufflecardsQuestion: "Shuffle cards?",
      no: "No",
      yes: "Yes",
      finished: "You have finished. Congratulations!",
      nbCardsQuestion: "How many cards do you want?",
      allCards: "all",
      // END JR
      progressText: "Card @card of @total",
      dialogs: [
        {
          text: 'Horse',
          answer: 'Hest'
        },
        {
          text: 'Cow',
          answer: 'Ku'
        }
      ],
      behaviour: {
        enableRetry: true,
        //randomAnswers: false, // JR NOT USED!
        scaleTextNotCard: false,
        shuffleCards: "no", // JR added param
        numberCards: 999 // JR set default nb cards large number
      }
    }, params);
    self._current = -1;
    self._turned = [];
    self.$images = [];
    self.audios = [];
  }
  C.prototype = Object.create(H5P.EventDispatcher.prototype);
  C.prototype.constructor = C;
  /**
   * Attach h5p inside the given container.
   *
   * @param {jQuery} $container
   */
  C.prototype.attach = function ($container) {
    var self = this;
    self.$inner = $container
      .addClass('h5p-dialogcards')
      .append($('' +
      '
' + self.params.title + '
' +
      '
' + self.params.description + '
'
      ));
    switch (self.params.behaviour.shuffleCards) {
      case "user":
        self.createOrder().appendTo(self.$inner);
        break;
      case "yes":
        self.attachContinue();
        break;
      case "no":
        self.attachContinue();
        break;
    }
};
C.prototype.attachContinue = function ($container) {
    var self = this;
    // Remove elements from DOM
    var $el = '.h5p-dialogcards-number';
    $( $el ).remove();
    self.initCards(self.params.dialogs)
      .appendTo(self.$inner);
    self.createFooter()
      .appendTo(self.$inner);
    $feedback = $('
' + self.params.finished + '
').appendTo(self.$inner);
    self.updateNavigation();
    self.on('reset', function () {
      self.reset();
    });
    self.on('resize', self.resize);
    //self.trigger('resize');
  };
  /**
   * Create orderCards option request
   *
   * @returns {*|jQuery|HTMLElement} Order element
   */
  C.prototype.createOrder = function () {
    var self = this;
    var $order = $('
', {
      'class': 'h5p-dialogcards-order',
      'html': self.params.shufflecardsQuestion + "  "
    });
    self.$normalOrder = JoubelUI.createButton({
      'class': 'h5p-dialogcards-order-button',
      'title': self.params.no,
      'html': self.params.no
    }).click(function () {
      self.shuffleOrder("no");
    }).appendTo($order);
    self.$shuffleOrder = JoubelUI.createButton({
      'class': 'h5p-dialogcards-order-button',
      'title': self.params.yes,
      'html': self.params.yes
    }).click(function () {
      self.shuffleOrder("yes");
    }).appendTo($order);
    return $order;
  };
  C.prototype.createNumberCards = function () {
    var self = this;
    var $nbAllCards = self.params.dialogs.length;
    var $numberCards = $('
', {
      'class': 'h5p-dialogcards-number',
      'html': self.params.nbCardsQuestion + "
"
    });
    for (var i = 5; i < $nbAllCards; i+= 5) {
      self.$Button = JoubelUI.createButton({
          'class': 'h5p-dialogcards-number-button',
          'title': i,
          'html': i,
          'id': i
        }).click(function () {
            self.params.behaviour.nbCards = this.id;
            self.attachContinue();
          }).appendTo($numberCards);
      };
      self.$Button = JoubelUI.createButton({
        'class': 'h5p-dialogcards-number-button',
        'title': $nbAllCards,
        'html': self.params.allCards + " (" + $nbAllCards + ")"
        }).click(function () {
          self.attachContinue();
        }).appendTo($numberCards);
    return $numberCards;
  };
  /**
   * Create footer/navigation line
   *
   * @returns {*|jQuery|HTMLElement} Footer element
   */
  C.prototype.createFooter = function () {
    var self = this;
    var $footer = $('
', {
      'class': 'h5p-dialogcards-footer'
    });
    self.$prev = JoubelUI.createButton({
      'class': 'h5p-dialogcards-footer-button h5p-dialogcards-prev truncated',
      'title': self.params.prev
    }).click(function () {
      self.prevCard();
    }).appendTo($footer);
    self.$next = JoubelUI.createButton({
      'class': 'h5p-dialogcards-footer-button h5p-dialogcards-next truncated',
      'title': self.params.next
    }).click(function () {
      self.nextCard();
    }).appendTo($footer);
    self.$retry = JoubelUI.createButton({
      'class': 'h5p-dialogcards-footer-button h5p-dialogcards-retry h5p-dialogcards-disabled',
      'title': self.params.retry,
      'html': self.params.retry
    }).click(function () {
      self.trigger('reset');
    }).appendTo($footer);
    self.$progress = $('
', {
      'class': 'h5p-dialogcards-progress'
    }).appendTo($footer);
    return $footer
  };
  /**
   * Called when all cards has been loaded.
   */
  C.prototype.updateImageSize = function () {
    var self = this;
    // Find highest card content
    var relativeHeightCap = 15;
    var height = 0;
    var i;
    var foundImage = false;
    for (i = 0; i < self.params.dialogs.length; i++) {
      var card = self.params.dialogs[i];
      var $card = self.$current.find('.h5p-dialogcards-card-content');
      if (card.image === undefined) {
        continue;
      }
      foundImage = true;
      var imageHeight = card.image.height / card.image.width * $card.get(0).getBoundingClientRect().width;
      if (imageHeight > height) {
        height = imageHeight;
      }
    }
    if (foundImage) {
      var relativeImageHeight = height / parseFloat(self.$inner.css('font-size'));
      if (relativeImageHeight > relativeHeightCap) {
        relativeImageHeight = relativeHeightCap;
      }
      self.$images.forEach(function ($img) {
        $img.parent().css('height', relativeImageHeight + 'em');
      });
    }
  };
  /**
   * Adds tip to a card
   *
   * @param {jQuery} $card The card
   * @param {String} [side=front] Which side of the card
   * @param {Number} [index] Index of card
   */
  C.prototype.addTipToCard = function($card, side, index) {
    var self = this;
    // Make sure we have a side
    if (side !== 'back') {
      side = 'front';
    }
    // Make sure we have an index
    if (index === undefined) {
      index = self.$current.index();
    }
    // Remove any old tips
    $card.find('.joubel-tip-container').remove();
    // Add new tip if set and has length after trim
    var tips = self.params.dialogs[index].tips;
    if (tips !== undefined && tips[side] !== undefined) {
      var tip = tips[side].trim();
      if (tip.length) {
        $card.find('.h5p-dialogcards-card-text-wrapper').append(JoubelUI.createTip(tip));
      }
    }
  };
  /**
   * Creates all cards and appends them to card wrapper.
   *
   * @param {Array} cards Card parameters
   * @returns {*|jQuery|HTMLElement} Card wrapper set
   */
  C.prototype.initCards = function (cards) {
    var self = this;
    var loaded = 0;
    var initLoad = 2;
    if (self.params.behaviour.shuffleCards == "yes") {
      cards = shuffle(cards); // JR
    }
    self.$cardwrapperSet = $('
', {
      'class': 'h5p-dialogcards-cardwrap-set'
    });
    var setCardSizeCallback = function () {
      loaded++;
      if (loaded === initLoad) {
        self.resize();
      }
    };
    //alert("line 339 self.params.behaviour.nbCards = " +self.params.behaviour.nbCards);
    // JR If student has selected a number of cards < total number then use that selected number.
    var $nbCards = self.params.behaviour.nbCards < cards.length ? self.params.behaviour.nbCards : cards.length;
    for (var i = 0; i < $nbCards; i++) {
      // JR load all cards, otherwise it breaks the JR gotit feature etc.
      /*
      // Load cards progressively
      if (i >= initLoad) {
        break;
      }
      */
      var $cardWrapper = self.createCard(cards[i], setCardSizeCallback);
      // Set current card
      if (i === 0) {
        $cardWrapper.addClass('h5p-dialogcards-current');
        self.$current = $cardWrapper;
      }
      self.addTipToCard($cardWrapper.find('.h5p-dialogcards-card-content'), 'front', i);
      self.$cardwrapperSet.append($cardWrapper);
    }
    return self.$cardwrapperSet;
  };
  /**
   * Create a single card card
   *
   * @param {Object} card Card parameters
   * @param {Function} [setCardSizeCallback] Set card size callback
   * @returns {*|jQuery|HTMLElement} Card wrapper
   */
  C.prototype.createCard = function (card, setCardSizeCallback) {
    var self = this;
    var $cardWrapper = $('
', {
      'class': 'h5p-dialogcards-cardwrap'
    });
    var $cardHolder = $('
', {
      'class': 'h5p-dialogcards-cardholder'
    }).appendTo($cardWrapper);
    self.createCardContent(card, setCardSizeCallback)
      .appendTo($cardHolder);
    return $cardWrapper;
  };
  /**
   * Create content for a card
   *
   * @param {Object} card Card parameters
   * @param {Function} [setCardSizeCallback] Set card size callback
   * @returns {*|jQuery|HTMLElement} Card content wrapper
   */
  C.prototype.createCardContent = function (card, setCardSizeCallback) {
    var self = this;
    var $cardContent = $('
', {
      'class': 'h5p-dialogcards-card-content'
    });
    self.createCardImage(card, setCardSizeCallback)
      .appendTo($cardContent);
    var $cardTextWrapper = $('
', {
      'class': 'h5p-dialogcards-card-text-wrapper'
    }).appendTo($cardContent);
    var $cardTextInner = $('
', {
      'class': 'h5p-dialogcards-card-text-inner'
    }).appendTo($cardTextWrapper);
    var $cardTextInnerContent = $('
', {
      'class': 'h5p-dialogcards-card-text-inner-content'
    }).appendTo($cardTextInner);
    self.createCardAudio(card)
      .appendTo($cardTextInnerContent);
    var $cardText = $('
', {
      'class': 'h5p-dialogcards-card-text'
    }).appendTo($cardTextInnerContent);
    $('
', {
      'class': 'h5p-dialogcards-card-text-area',
      'html': card.text
    }).appendTo($cardText);
    if (!card.text || !card.text.length) {
      $cardText.addClass('hide');
    }
    self.createCardFooter()
      .appendTo($cardTextWrapper);
    return $cardContent;
  };
  /**
   * Create card footer
   *
   * @returns {*|jQuery|HTMLElement} Card footer element
   */
  C.prototype.createCardFooter = function () {
    var self = this;
    var $cardFooter = $('
', {
      'class': 'h5p-dialogcards-card-footer'
    });
    JoubelUI.createButton({
      'class': 'h5p-dialogcards-turn',
      'html': self.params.answer
    }).click(function () {
      self.turnCard($(this).parents('.h5p-dialogcards-cardwrap'));
    }).appendTo($cardFooter);
    
    // JR create gotit button hidden
    JoubelUI.createButton({
      'class': 'h5p-dialogcards-gotit hide truncated',
      'title': self.params.gotit
    }).click(function () {
          self.gotIt($(this).parents('.h5p-dialogcards-cardwrap'));
    }).appendTo($cardFooter);
    // end create gotit button hidden
    
    return $cardFooter;
  };
  /**
   * Create card image
   *
   * @param {Object} card Card parameters
   * @param {Function} [loadCallback] Function to call when loading image
   * @returns {*|jQuery|HTMLElement} Card image wrapper
   */
  C.prototype.createCardImage = function (card, loadCallback) {
    var self = this;
    var $image;
    var $imageWrapper = $('
', {
      'class': 'h5p-dialogcards-image-wrapper'
    });
    if (card.image !== undefined) {
      $image = $('
 + ')
');
      if (loadCallback) {
        $image.load(loadCallback);
      }
    }
    else {
      $image = $('
');
      if (loadCallback) {
        loadCallback();
      }
    }
    self.$images.push($image);
    $image.appendTo($imageWrapper);
    return $imageWrapper;
  };
  /**
   * Create card audio
   *
   * @param {Object} card Card parameters
   * @returns {*|jQuery|HTMLElement} Card audio element
   */
  C.prototype.createCardAudio = function (card) {
    var self = this;
    var audio;
    var $audioWrapper = $('
', {
      'class': 'h5p-dialogcards-audio-wrapper'
    });
    if (card.audio !== undefined) {
      var audioDefaults = {
        files: card.audio
      };
      audio = new Audio(audioDefaults, self.id);
      audio.attach($audioWrapper);
      // Have to stop else audio will take up a socket pending forever in chrome.
      if (audio.audio && audio.audio.preload) {
        audio.audio.preload = 'none';
      }
    }
    else {
      $audioWrapper.addClass('hide');
    }
    self.audios.push(audio);
    return $audioWrapper;
  };
  /**
   * Update navigation text and show or hide buttons.
   */
  C.prototype.updateNavigation = function () {
    var self = this;
    if (self.$current.next('.h5p-dialogcards-cardwrap').length) {
      self.$next.removeClass('h5p-dialogcards-disabled');
      self.$retry.addClass('h5p-dialogcards-disabled');
    }
    else {
      self.$next.addClass('h5p-dialogcards-disabled');
    }
    if (self.$current.prev('.h5p-dialogcards-cardwrap').length && !self.params.behaviour.disableBackwardsNavigation) {
      self.$prev.removeClass('h5p-dialogcards-disabled');
    }
    else {
      self.$prev.addClass('h5p-dialogcards-disabled');
    }
    if (self.params.behaviour.nbCards < self.params.dialogs.length) {
      $totalNumberCards = self.params.behaviour.nbCards;
    } else {
      $totalNumberCards = self.params.dialogs.length;
    }
    self.$progress.text(self.params.progressText.replace('@card', self.$current.index() + 1).replace('@total', $totalNumberCards));
    self.resizeOverflowingText();
  };
  /**
   * Show next card.
   */
  C.prototype.nextCard = function () {
    var self = this;
    var $next = self.$current.next('.h5p-dialogcards-cardwrap');
    self.updateImageSize();
    // JR TODO
    // Next card not loaded or end of cards
    if ($next.length) {
      self.stopAudio(self.$current.index());
      self.$current.removeClass('h5p-dialogcards-current').addClass('h5p-dialogcards-previous');
      self.$current = $next.addClass('h5p-dialogcards-current');
      // Add next card.   // JR removed because cause various problems
      /*
      var $loadCard = self.$current.next('.h5p-dialogcards-cardwrap');
      if (!$loadCard.length && self.$current.index() + 1 < self.params.dialogs.length) {
        var $cardWrapper = self.createCard(self.params.dialogs[self.$current.index() + 1])
          .appendTo(self.$cardwrapperSet);
        self.addTipToCard($cardWrapper.find('.h5p-dialogcards-card-content'), 'front', self.$current.index() + 1);
        self.resize();
      }
      */
        self.turnCardToFront (); // JR
      // Update navigation
      self.updateNavigation();
    }
  };
  /**
   * Show previous card.
   */
  C.prototype.prevCard = function () {
    var self = this;
    
    var $prev = self.$current.prev('.h5p-dialogcards-cardwrap');
    if ($prev.length) {
        self.stopAudio(self.$current.index());
        self.$current.removeClass('h5p-dialogcards-current');
        self.$current = $prev.addClass('h5p-dialogcards-current').removeClass('h5p-dialogcards-previous');
        self.turnCardToFront (); // JR
      self.updateNavigation();
    }
  };
  /**
   * User select order/shuffle option.
   */
  C.prototype.shuffleOrder = function (shuffleyesno) {
    var self = this;
    var $el = '.h5p-dialogcards-order';
    $( $el ).remove();
    if (shuffleyesno == 'yes') {
      self.createNumberCards()
      .appendTo(self.$inner);
    } else {
      self.attachContinue();
    }
    self.params.behaviour.shuffleCards = shuffleyesno;
  };
  C.prototype.numberCards = function (nbcards) {
    var self = this;
    self.attachContinue();
  };
  /**
   * Show the opposite site of the card.
   *
   * @param {jQuery} $card
   */
  C.prototype.turnCard = function ($card) {
    var self = this;
    var $c = $card.find('.h5p-dialogcards-card-content');
    var $ch = $card.find('.h5p-dialogcards-cardholder').addClass('h5p-dialogcards-collapse');
    var $cg = $card.find('.h5p-dialogcards-gotit'); // JR
    
    // Removes tip, since it destroys the animation:
    $c.find('.joubel-tip-container').remove();
    // Check if card has been turned before
    var turned = $c.hasClass('h5p-dialogcards-turned');
    // Update HTML class for card
    $c.toggleClass('h5p-dialogcards-turned', !turned);
    setTimeout(function () {
      $ch.removeClass('h5p-dialogcards-collapse');
      self.changeText($c, self.params.dialogs[$card.index()][turned ? 'text' : 'answer']);
      if (turned) {
        $ch.find('.h5p-audio-inner').removeClass('hide');
        $cg.addClass('hide'); // JR
      }
      else {
        self.removeAudio($ch);
        $cg.removeClass('hide'); // JR
      }
      // Add backside tip
      // Had to wait a little, if not Chrome will displace tip icon
      setTimeout(function () {
        self.addTipToCard($c, turned ? 'front' : 'back');
        if (!self.$current.next('.h5p-dialogcards-cardwrap').length) {
          if (self.params.behaviour.enableRetry) {
            self.$retry.removeClass('h5p-dialogcards-disabled');
            self.truncateRetryButton();
            self.resizeOverflowingText();
          }
        }
      }, 200);
    }, 200);
  };
  /**
   * Change text of card, used when turning cards.
   *
   * @param $card
   * @param text
   */
  C.prototype.changeText = function ($card, text) {
    var $cardText = $card.find('.h5p-dialogcards-card-text-area');
    $cardText.html(text);
    $cardText.toggleClass('hide', (!text || !text.length));
  };
  /**
   * Stop audio of card with cardindex
   * @param {Number} cardIndex Index of card
   */
  C.prototype.stopAudio = function (cardIndex) {
    var self = this;
    var audio = self.audios[cardIndex];
    if (audio && audio.stop) {
      audio.stop();
    }
  };
  /**
   * Hide audio button
   *
   * @param $card
   */
  C.prototype.removeAudio = function ($card) {
    var self = this;
    self.stopAudio($card.closest('.h5p-dialogcards-cardwrap').index());
    $card.find('.h5p-audio-inner')
      .addClass('hide');
  };
  /**
   * Show all audio buttons
   */
  C.prototype.showAllAudio = function () {
    var self = this;
    self.$cardwrapperSet.find('.h5p-audio-inner')
      .removeClass('hide');
  };
  /**
   * Reset the task so that the user can do it again.
   */
  C.prototype.reset = function () {
    var self = this;
    var $cards = self.$inner.find('.h5p-dialogcards-cardwrap');
    self.stopAudio(self.$current.index());
    self.$current.removeClass('h5p-dialogcards-current');
    self.$current = $cards.filter(':first').addClass('h5p-dialogcards-current');
    self.updateNavigation();
    $cards.each(function (index) {
      var $card = $(this).removeClass('h5p-dialogcards-previous');
      self.changeText($card, self.params.dialogs[$card.index()].text);
      var $cardContent = $card.find('.h5p-dialogcards-card-content');
      $cardContent.removeClass('h5p-dialogcards-turned');
      self.addTipToCard($cardContent, 'front', index);
      var $cg = $card.find('.h5p-dialogcards-gotit'); // JR
      $cg.addClass('hide'); // JR
    });
    self.$retry.addClass('h5p-dialogcards-disabled');
    self.showAllAudio();
    self.resizeOverflowingText();
  };
  /**
   * Update the dimensions of the task when resizing the task.
   */
  C.prototype.resize = function () {
    var self = this;
    var maxHeight = 0;
    self.updateImageSize();
    if (!self.params.behaviour.scaleTextNotCard) {
      self.determineCardSizes();
    }
    // Reset card-wrapper-set height
    self.$cardwrapperSet.css('height', 'auto');
    //Find max required height for all cards
    self.$cardwrapperSet.children().each( function () {
      var wrapperHeight = $(this).css('height', 'initial').outerHeight();
      $(this).css('height', 'inherit');
      maxHeight = wrapperHeight > maxHeight ? wrapperHeight : maxHeight;
      // Check height
      if (!$(this).next('.h5p-dialogcards-cardwrap').length) {
        var initialHeight = $(this).find('.h5p-dialogcards-cardholder').css('height', 'initial').outerHeight();
        maxHeight = initialHeight > maxHeight ? initialHeight : maxHeight;
        $(this).find('.h5p-dialogcards-cardholder').css('height', 'inherit');
      }
    });
    var relativeMaxHeight = maxHeight / parseFloat(self.$cardwrapperSet.css('font-size'));
    self.$cardwrapperSet.css('height', relativeMaxHeight + 'em');
    self.scaleToFitHeight();
    self.truncateRetryButton();
    self.resizeOverflowingText();
  };
  /**
   * Resizes each card to fit its text
   */
  C.prototype.determineCardSizes = function () {
    var self = this;
    if (self.cardSizeDetermined === undefined) {
      // Keep track of which cards we've already determined size for
      self.cardSizeDetermined = [];
    }
    // Go through each card
    self.$cardwrapperSet.children(':visible').each(function (i) {
      if (self.cardSizeDetermined.indexOf(i) !== -1) {
        return; // Already determined, no need to determine again.
      }
      self.cardSizeDetermined.push(i);
      var $content = $('.h5p-dialogcards-card-content', this);
      var $text = $('.h5p-dialogcards-card-text-inner-content', $content);
      // Grab size with text
      var textHeight = $text[0].getBoundingClientRect().height;
      // Change to answer
      self.changeText($content, self.params.dialogs[i].answer);
      // Grab size with answer
      var answerHeight = $text[0].getBoundingClientRect().height;
      // Use highest
      var useHeight = (textHeight > answerHeight ? textHeight : answerHeight);
      // Min. limit
      var minHeight = parseFloat($text.parent().parent().css('minHeight'));
      if (useHeight < minHeight) {
        useHeight =  minHeight;
      }
      // Convert to em
      var fontSize = parseFloat($content.css('fontSize'));
      useHeight /= fontSize;
      // Set height
      $text.parent().css('height', useHeight + 'em');
      // Change back to text
      self.changeText($content, self.params.dialogs[i].text);
    });
  };
  C.prototype.scaleToFitHeight = function () {
    var self = this;
    if (!self.$cardwrapperSet || !self.$cardwrapperSet.is(':visible') || !self.params.behaviour.scaleTextNotCard) {
      return;
    }
    // Resize font size to fit inside CP
    if (self.$inner.parents('.h5p-course-presentation').length) {
      var $parentContainer = self.$inner.parent();
      if (self.$inner.parents('.h5p-popup-container').length) {
        $parentContainer = self.$inner.parents('.h5p-popup-container');
      }
      var containerHeight = $parentContainer.get(0).getBoundingClientRect().height;
      var getContentHeight = function () {
        var contentHeight = 0;
        self.$inner.children().each(function () {
          contentHeight += $(this).get(0).getBoundingClientRect().height +
          parseFloat($(this).css('margin-top')) + parseFloat($(this).css('margin-bottom'));
        });
        return contentHeight;
      };
      var contentHeight = getContentHeight();
      var parentFontSize = parseFloat(self.$inner.parent().css('font-size'));
      var newFontSize = parseFloat(self.$inner.css('font-size'));
      // Decrease font size
      if (containerHeight < contentHeight) {
        while (containerHeight < contentHeight) {
          newFontSize -= C.SCALEINTERVAL;
          // Cap at min font size
          if (newFontSize < C.MINSCALE) {
            break;
          }
          // Set relative font size to scale with full screen.
          self.$inner.css('font-size', (newFontSize / parentFontSize) + 'em');
          contentHeight = getContentHeight();
        }
      }
      else { // Increase font size
        var increaseFontSize = true;
        while (increaseFontSize) {
          newFontSize += C.SCALEINTERVAL;
          // Cap max font size
          if (newFontSize > C.MAXSCALE) {
            increaseFontSize = false;
            break;
          }
          // Set relative font size to scale with full screen.
          var relativeFontSize = newFontSize / parentFontSize;
          self.$inner.css('font-size', relativeFontSize + 'em');
          contentHeight = getContentHeight();
          if (containerHeight <= contentHeight) {
            increaseFontSize = false;
            relativeFontSize = (newFontSize - C.SCALEINTERVAL) / parentFontSize;
            self.$inner.css('font-size', relativeFontSize + 'em');
          }
        }
      }
    }
    else { // Resize mobile view
      self.resizeOverflowingText();
    }
  };
  /**
   * Resize the font-size of text areas that tend to overflow when dialog cards
   * is squeezed into a tiny container.
   */
  C.prototype.resizeOverflowingText = function () {
    var self = this;
    if (!self.params.behaviour.scaleTextNotCard) {
      return; // No text scaling today
    }
    // Resize card text if needed
    var $textContainer = self.$current.find('.h5p-dialogcards-card-text');
    var $text = $textContainer.children();
    self.resizeTextToFitContainer($textContainer, $text);
  };
  /**
   * Increase or decrease font size so text wil fit inside container.
   *
   * @param {jQuery} $textContainer Outer container, must have a set size.
   * @param {jQuery} $text Inner text container
   */
  C.prototype.resizeTextToFitContainer = function ($textContainer, $text) {
    var self = this;
    // Reset text size
    $text.css('font-size', '');
    // Measure container and text height
    var currentTextContainerHeight = $textContainer.get(0).getBoundingClientRect().height;
    var currentTextHeight = $text.get(0).getBoundingClientRect().height;
    var parentFontSize = parseFloat($textContainer.css('font-size'));
    var fontSize = parseFloat($text.css('font-size'));
    var mainFontSize = parseFloat(self.$inner.css('font-size'));
    // Decrease font size
    if (currentTextHeight > currentTextContainerHeight) {
      var decreaseFontSize = true;
      while (decreaseFontSize) {
        fontSize -= C.SCALEINTERVAL;
        if (fontSize < C.MINSCALE) {
          decreaseFontSize = false;
          break;
        }
        $text.css('font-size', (fontSize / parentFontSize) + 'em');
        currentTextHeight = $text.get(0).getBoundingClientRect().height;
        if (currentTextHeight <= currentTextContainerHeight) {
          decreaseFontSize = false;
        }
      }
    }
    else { // Increase font size
      var increaseFontSize = true;
      while (increaseFontSize) {
        fontSize += C.SCALEINTERVAL;
        // Cap at  16px
        if (fontSize > mainFontSize) {
          increaseFontSize = false;
          break;
        }
        // Set relative font size to scale with full screen.
        $text.css('font-size', fontSize / parentFontSize + 'em');
        currentTextHeight = $text.get(0).getBoundingClientRect().height;
        if (currentTextHeight >= currentTextContainerHeight) {
          increaseFontSize = false;
          fontSize = fontSize- C.SCALEINTERVAL;
          $text.css('font-size', fontSize / parentFontSize + 'em');
        }
      }
    }
  };
  /**
   * Truncate retry button if width is small.
   */
  C.prototype.truncateRetryButton = function () {
    var self = this;
    if (!self.$retry) {
      return;
    }
    // Reset button to full size
    self.$retry.removeClass('truncated');
    self.$retry.html(self.params.retry);
    // Measure button
    var maxWidthPercentages = 0.3;
    var retryWidth = self.$retry.get(0).getBoundingClientRect().width +
        parseFloat(self.$retry.css('margin-left')) + parseFloat(self.$retry.css('margin-right'));
    var retryWidthPercentage = retryWidth / self.$retry.parent().get(0).getBoundingClientRect().width;
    // Truncate button
    if (retryWidthPercentage > maxWidthPercentages) {
      self.$retry.addClass('truncated');
      self.$retry.html('');
    }
  };
  /**
   * JR Remove card from DOM and from cards stack after user has checked the "gotit" button.
   */
  C.prototype.gotIt = function ($card) {
    var self = this;
    var index = $card.index();  
    
    // Mark current card with a 'gotitdone' class.
    self.$current.addClass('h5p-dialogcards-gotitdone');    
    
    // Move to next card if exists.
    var $next = self.$current.next('.h5p-dialogcards-cardwrap');
    var $prev = self.$current.prev('.h5p-dialogcards-cardwrap');
    
    if ($next.length) {
        var i = 1;
        self.nextCard(i);
    } else if ($prev.length) { // No next card left - go to previous.
        self.prevCard();
    } else { // No cards left.
    
        self.$retry.addClass('h5p-dialogcards-disabled');
        self.$cardwrapperSet.append('Finished!');
        
        // Removes footer.
        var $footer = '.h5p-dialogcards-footer';
        $( $footer ).remove();
        
        var $c = self.$inner.find('.h5p-dialogcards-cardwrap-set');
        $c.remove();
        
        var $c = self.$inner.find('.h5p-dialogcards-description');
        $c.remove();
        
        $c = self.$inner.find('.h5p-feedback');
        $c.addClass('h5p-show');
    }
       
    // Now remove the 'gotitdone' card from DOM
    var $el = '.h5p-dialogcards-gotitdone';
    $( $el ).remove();
    
    // Now remove the 'gotitdone' card from cards array.
    var cardsArray = self.params.dialogs;    
    cardsArray.splice( index, 1 );
    self.params.behaviour.nbCards = self.params.behaviour.nbCards - 1;
     
    // Update navigation
    self.updateNavigation();
  };
  /**
   * JR When navigating forward or backward, reset card to front view if has previously been turned.
   */
  C.prototype.turnCardToFront = function () {
    var self = this;
    //var $card = self.$current;
    var $c = self.$current.find('.h5p-dialogcards-card-content');
    var turned = $c.hasClass('h5p-dialogcards-turned');
        if (turned) {     
            self.turnCard(self.$current);
        }
    }
// JR added shuffle function from http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
  function shuffle(array) {
    var currentIndex = array.length, temporaryValue, randomIndex;
    // While there remain elements to shuffle
    while (0 !== currentIndex) {
      // Pick a remaining element
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;
      // And swap it with the current element.
      temporaryValue = array[currentIndex];
      array[currentIndex] = array[randomIndex];
      array[randomIndex] = temporaryValue;
    }
    return array;
  }
  C.SCALEINTERVAL = 0.2;
  C.MAXSCALE = 16;
  C.MINSCALE = 4;
  
  return C;
})(H5P.jQuery, H5P.Audio, H5P.JoubelUI);
;