docs: function jBox(type, options) {
  
  
      // Options (https://stephanwagner.me/jBox/options)
  
      this.options = {
  
        // jBox ID
        id: null,                    // Choose a unique id, otherwise jBox will set one for you (jBox1, jBox2, ...)
  
        // Dimensions
        width: 'auto',               // The width of the content area, e.g. 'auto', 200, '80%'
        height: 'auto',              // The height of the content area
        minWidth: null,              // Minimal width
        minHeight: null,             // Minimal height
        maxWidth: null,              // Maximal width
        maxHeight: null,             // Maximal height
  
        // Responsive dimensions
        responsiveWidth: true,       // Adjusts the width to fit the viewport
        responsiveHeight: true,      // Adjusts the height to fit the viewport
        responsiveMinWidth: 100,     // Don't adjust width below this value (in pixel)
        responsiveMinHeight: 100,    // Don't adjust height below this value (in pixel)
  
        // Attach
        attach: null,                // A jQuery selector to elements that will open and close your jBox, e.g. '.tooltip'
        trigger: 'click',            // The event to open or close your jBox, use 'click', 'touchclick' or 'mouseenter'
        preventDefault: false,       // Prevent the default event when opening jBox, e.g. don't follow the href in a link
  
        // Content
        content: null,               // You can use HTML or a jQuery element, e.g. jQuery('#jBox-content'). The elements will be appended to the content element and then made visible, so hide them with style="display: none" beforehand
        getContent: null,            // Get the content from an attribute when jBox opens, e.g. getContent: 'data-content'. Use 'html' to get the attached elements HTML as content
        title: null,                 // Adds a title to your jBox
        getTitle: null,              // Get the title from an attribute when jBox opens, e.g. getTitle: 'data-title'
        footer: null,                // Adds a footer to your jBox
        isolateScroll: true,         // Isolates scrolling to the content container
  
        // AJAX
        ajax: {                      // Setting an URL will make an AJAX request when jBox opens. Optional you can add any jQuery AJAX option (http://api.jquery.com/jquery.ajax/)
          url: null,                 // The URL to send the AJAX request to
          data: '',                  // Data to send with your AJAX request, e.g. {id: 82, limit: 10}
          reload: false,             // Resend the AJAX request when jBox opens. Use true to send the AJAX request only once for every attached element or 'strict' to resend every time jBox opens
          getURL: 'data-url',        // The attribute in the source element where the AJAX request will look for the URL, e.g. data-url="https://reqres.in/api/users"
          getData: 'data-ajax',      // The attribute in the source element where the AJAX request will look for the data, e.g. data-ajax="id=82&limit=10"
          setContent: true,          // Automatically set the response as new content when the AJAX request is finished
          loadingClass: true,        // Add a class to the wrapper when jBox is loading, set to class name or true to use the default class name 'jBox-loading'
          spinner: true,             // Hides the current content and adds a spinner while loading. You can pass HTML content to add your own spinner, e.g. spinner: '<div class="mySpinner"></div>'
          spinnerDelay: 300,         // Milliseconds to wait until spinner appears
          spinnerReposition: true    // Repositions jBox when the spinner is added or removed
        },
        cancelAjaxOnClose: true,     // Cancels the ajax call when jBox closes and it hasn't finished loading yet
  
        // Position
        target: null,                // The jQuery selector to the target element where jBox will be opened. If no element is found, jBox will use the attached element as target
        position: {
          x: 'center',               // Horizontal position, use a number, 'left', 'right' or 'center'
          y: 'center'                // Vertical position, use a number, 'top', 'bottom' or 'center'
        },
        outside: null,               // Use 'x', 'y', or 'xy' to move your jBox outside of the target element
        offset: 0,                   // Offset to final position, you can set different values for x and y with an object, e.g. {x: 20, y: 10}
        attributes: {                // Note that attributes can only be 'left' or 'right' when using numbers for position, e.g. {x: 300, y: 20}
          x: 'left',                 // Horizontal position, use 'left' or 'right'
          y: 'top'                   // Vertical position, use 'top' or 'bottom'
        },
        fixed: false,                // Your jBox will stay on position when scrolling
        adjustPosition: true,        // Adjusts your jBoxes position if there is not enough space, use 'flip', 'move' or true for both. This option overrides the reposition options
        adjustTracker: false,        // By default jBox adjusts its position when it opens or when the window size changes, set to true to also adjust when scrolling
        adjustDistance: 5,           // The minimal distance to the viewport edge while adjusting. Use an object to set different values, e.g. {top: 50, right: 5, bottom: 20, left: 5}
        reposition: true,            // Calculates new position when the window-size changes
        repositionOnOpen: true,      // Calculates new position each time jBox opens (rather than only when it opens the first time)
        repositionOnContent: true,   // Calculates new position when the content changes with .setContent() or .setTitle()
        holdPosition: true,          // Keeps current position if space permits. Applies only to 'Modal' type.
  
        // Pointer
        pointer: false,              // Your pointer will always point towards the target element, so the option outside needs to be 'x' or 'y'. By default the pointer is centered, set a position to move it to any side. You can also add an offset, e.g. 'left:30' or 'center:-20'
        pointTo: 'target',           // Setting something else than 'target' will add a pointer even if there is no target element set or found. Use 'top', 'right', 'bottom' or 'left'
  
        // Animations
        fade: 180,                   // Fade duration in ms, set to 0 or false to disable
        animation: null,             // Animation when opening or closing, use 'pulse', 'zoomIn', 'zoomOut', 'move', 'slide', 'flip', 'tada' (CSS inspired from Daniel Edens Animate.css: http://daneden.me/animate)
  
        // Appearance
        theme: 'Default',            // Set a jBox theme class
        addClass: null,              // Adds classes to the wrapper
        overlay: false,              // Adds an overlay to hide page content when jBox opens (adjust color and opacity with CSS)
        zIndex: 10000,               // Use a high z-index, or set to 'auto' to bring to front on open
  
        // Delays
        delayOpen: 0,                // Delay opening in ms. Note that the delay will be ignored if your jBox didn't finish closing
        delayClose: 0,               // Delay closing in ms. Nnote that there is always a closing delay of at least 10ms to ensure jBox won't be closed when opening right away
  
        // Closing
        closeOnEsc: false,           // Close jBox when pressing [esc] key
        closeOnClick: false,         // Close jBox with mouseclick. Use true (click anywhere), 'box' (click on jBox itself), 'overlay' (click on the overlay), 'body' (click anywhere but jBox)
        closeOnMouseleave: false,    // Close jBox when the mouse leaves the jBox area or the area of the attached element
        closeButton: false,          // Adds a close button to your jBox. Use 'title', 'box', 'overlay' or true (true will add the button to the overlay, title or the jBox itself, in that order if any of those elements can be found)
  
        // Other options
        appendTo: jQuery('body'),    // The element your jBox will be appended to. Any other element than jQuery('body') is only useful for fixed positions or when position values are numbers
        createOnInit: false,         // Creates jBox and makes it available in DOM when it's being initialized, otherwise it will be created when it opens for the first time
        blockScroll: false,          // Blocks scrolling when jBox is open
        blockScrollAdjust: true,     // Adjust page elements to avoid content jumps when scrolling is blocked. See more here: https://github.com/StephanWagner/unscroll
        draggable: false,            // Make your jBox draggable (use 'true', 'title' or provide an element as handle) (inspired from Chris Coyiers CSS-Tricks http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/)
        dragOver: true,              // When you have multiple draggable jBoxes, the one you select will always move over the other ones
        autoClose: false,            // Time in ms when jBox will close automatically after it was opened
        delayOnHover: false,         // Delay auto-closing while mouse is hovered
        showCountdown: false,        // Display a nice progress-indicator when autoClose is enabled
  
        // Audio                     // You can use the integrated audio function whenever you'd like to play an audio file, e.g. onInit: function () { this.audio('url_to_audio_file_without_file_extension', 75); }
        preloadAudio: true,          // Preloads the audio files set in option audio. You can also preload other audio files, e.g. ['src_to_file.mp3', 'src_to_file.ogg']
        audio: null,                 // The URL to an audio file to play when jBox opens. Set the URL without file extension, jBox will look for an .mp3 and .ogg file. To play audio when jBox closes, use an object, e.g. {open: 'src_to_audio1', close: 'src_to_audio2'}
        volume: 100,                 // The volume in percent. To have different volumes for opening and closeing, use an object, e.g. {open: 75, close: 100}
  
        // Events                    // Note that you can use 'this' in all event functions, it refers to your jBox object (e.g. onInit: function () { this.open(); })
        onInit: null,                // Fired when jBox is initialized
        onAttach: null,              // Fired when jBox attached itself to elements, the attached element will be passed as a parameter, e.g. onAttach: function (element) { element.css({color: 'red'}); }
        onPosition: null,            // Fired when jBox is positioned
        onCreated: null,             // Fired when jBox is created and availible in DOM
        onOpen: null,                // Fired when jBox opens
        onClose: null,               // Fired when jBox closes
        onCloseComplete: null,       // Fired when jBox is completely closed (when fading is finished)
        onDragStart: null,           // Fired when dragging starts
        onDragEnd: null              // Fired when dragging finished
      };
  
  
      // Default plugin options
  
      this._pluginOptions = {
  
        // Default options for tooltips
        'Tooltip': {
          getContent: 'title',
          trigger: 'mouseenter',
          position: {
            x: 'center',
            y: 'top'
          },
          outside: 'y',
          pointer: true
        },
  
        // Default options for mouse tooltips
        'Mouse': {
          responsiveWidth: false,
          responsiveHeight: false,
          adjustPosition: 'flip',
          target: 'mouse',
          trigger: 'mouseenter',
          position: {
            x: 'right',
            y: 'bottom'
          },
          outside: 'xy',
          offset: 5
        },
  
        // Default options for modal windows
        'Modal': {
          target: jQuery(window),
          fixed: true,
          blockScroll: true,
          closeOnEsc: true,
          closeOnClick: 'overlay',
          closeButton: true,
          overlay: true,
          animation: 'zoomIn'
        },
      };
  
  
      // Merge options
  
      this.options = jQuery.extend(true, this.options, this._pluginOptions[type] ? this._pluginOptions[type] : jBox._pluginOptions[type], options);
  
  
      // Set the jBox type
  
      jQuery.type(type) == 'string' && (this.type = type);
  
  
      // Checks if the user is on a touch device, borrowed from https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
  
      this.isTouchDevice = (function () {
        var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
        var mq = function (query) {
          return window.matchMedia(query).matches;
        }
  
        if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
          return true;
        }
  
        var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
        return mq(query);
      })();
  
  
      // Add close event for body click when we are on touch device and jBox triggers on mouseenter
  
      if (this.isTouchDevice && this.options.trigger === 'mouseenter' && this.options.closeOnClick === false) {
        this.options.closeOnClick = 'body';
      }
  
  
      // Local function to fire events
  
      this._fireEvent = function (event, pass)
      {
        this.options['_' + event] && (this.options['_' + event].bind(this))(pass);
        this.options[event] && (this.options[event].bind(this))(pass);
      };
  
  
      // Get a unique jBox ID
  
      this.options.id === null && (this.options.id = 'jBox' + jBox._getUniqueID());
      this.id = this.options.id;
  
  
      // Correct impossible options
  
      ((this.options.position.x == 'center' && this.options.outside == 'x') || (this.options.position.y == 'center' && this.options.outside == 'y')) && (this.options.outside = null);
      this.options.pointTo == 'target' && (!this.options.outside || this.options.outside == 'xy') && (this.options.pointer = false);
  
  
      // Correct multiple choice options
  
      jQuery.type(this.options.offset) != 'object' ? (this.options.offset = {x: this.options.offset, y: this.options.offset}) : (this.options.offset = jQuery.extend({x: 0, y: 0}, this.options.offset));
      jQuery.type(this.options.adjustDistance) != 'object' ? (this.options.adjustDistance = {top: this.options.adjustDistance, right: this.options.adjustDistance, bottom: this.options.adjustDistance, left: this.options.adjustDistance}) : (this.options.adjustDistance = jQuery.extend({top: 5, left: 5, right: 5, bottom: 5}, this.options.adjustDistance));
  
  
      // Save default outside position
  
      this.outside = this.options.outside && this.options.outside != 'xy' ? this.options.position[this.options.outside] : false;
  
  
      // Save where the jBox is aligned to
  
      this.align = this.outside ? this.outside : (this.options.position.y != 'center' && jQuery.type(this.options.position.y) != 'number' ? this.options.position.x : (this.options.position.x != 'center' && jQuery.type(this.options.position.x) != 'number' ? this.options.position.y : this.options.attributes.x));
  
  
      // Adjust option zIndex
  
      jBox.zIndexMax = Math.max(jBox.zIndexMax || 0, this.options.zIndex === 'auto' ? 10000 : this.options.zIndex);
      if (this.options.zIndex === 'auto') {
        this.adjustZIndexOnOpen = true;
        jBox.zIndexMax += 2;
        this.options.zIndex = jBox.zIndexMax;
        this.trueModal = this.options.overlay;
      }
  
      // Internal positioning functions
  
      this._getOpp = function (opp) { return {left: 'right', right: 'left', top: 'bottom', bottom: 'top', x: 'y', y: 'x'}[opp]; };
      this._getXY = function (xy) { return {left: 'x', right: 'x', top: 'y', bottom: 'y', center: 'x'}[xy]; };
      this._getTL = function (tl) { return {left: 'left', right: 'left', top: 'top', bottom: 'top', center: 'left', x: 'left', y: 'top'}[tl]; };
  
  
      // Get a dimension value in integer pixel dependent on appended element
  
      this._getInt = function (value, dimension) {
        if (value == 'auto') return 'auto';
        if (value && jQuery.type(value) == 'string' && value.slice(-1) == '%') {
          return jQuery(window)[dimension == 'height' ? 'innerHeight' : 'innerWidth']() * parseInt(value.replace('%', '')) / 100;
        }
        return value;
      };
  
  
      // Create an svg element
  
      this._createSVG = function (type, options)
      {
        var svg = document.createElementNS('http://www.w3.org/2000/svg', type);
        jQuery.each(options, function (index, item) {
          svg.setAttribute(item[0], (item[1] || ''));
        });
        return svg;
      };
  
  
      // Isolate scrolling in a container
  
      this._isolateScroll = function (el)
      {
        // Abort if element not found
        if (!el || !el.length) return;
  
        el.on('DOMMouseScroll.jBoxIsolateScroll mousewheel.jBoxIsolateScroll', function (ev) {
          var delta = ev.wheelDelta || (ev.originalEvent && ev.originalEvent.wheelDelta) || -ev.detail;
          var overflowBottom = this.scrollTop + el.outerHeight() - this.scrollHeight >= 0;
          var overflowTop = this.scrollTop <= 0;
          ((delta < 0 && overflowBottom) || (delta > 0 && overflowTop)) && ev.preventDefault();
        });
      };
  
  
      // Set the title width to content width
  
      this._setTitleWidth = function ()
      {
        // Abort if there is no title or width of content is auto
        if (!this.titleContainer || (this.content[0].style.width == 'auto' && !this.content[0].style.maxWidth)) return null;
  
        // Expose wrapper to get actual width
        if (this.wrapper.css('display') == 'none') {
          this.wrapper.css('display', 'block');
          var contentWidth = this.content.outerWidth();
          this.wrapper.css('display', 'none');
        } else {
          var contentWidth = this.content.outerWidth();
        }
  
        // Set max-width only
        this.titleContainer.css({maxWidth: (Math.max(contentWidth, parseInt(this.content[0].style.maxWidth)) || null)});
      }
  
  
      // Make jBox draggable
  
      this._draggable = function ()
      {
        // Abort if jBox is not draggable
        if (!this.options.draggable) return false;
  
        // Get the handle where jBox will be dragged with
        var handle = this.options.draggable == 'title' ? this.titleContainer : (this.options.draggable instanceof jQuery ? this.options.draggable : (jQuery.type(this.options.draggable) == 'string' ? jQuery(this.options.draggable) : this.wrapper));
  
        // Abort if no handle or if draggable was set already
        if (!handle || !(handle instanceof jQuery) || !handle.length || handle.data('jBox-draggable')) return false;
  
        // Add mouse events
        handle.addClass('jBox-draggable').data('jBox-draggable', true).on('touchstart mousedown', function (ev)
        {
          if (ev.button == 2 || jQuery(ev.target).hasClass('jBox-noDrag') || jQuery(ev.target).parents('.jBox-noDrag').length) return;
  
          // Store current mouse position
          this.draggingStartX = ev.pageX;
          this.draggingStartY = ev.pageY;
  
          // Adjust z-index when dragging jBox over another draggable jBox
          if (this.options.dragOver && !this.trueModal && parseInt(this.wrapper.css('zIndex'), 10) <= jBox.zIndexMaxDragover) {
            jBox.zIndexMaxDragover += 1;
            this.wrapper.css('zIndex', jBox.zIndexMaxDragover);
          }
  
          var drg_h = this.wrapper.outerHeight();
          var drg_w = this.wrapper.outerWidth();
          var pos_y = this.wrapper.offset().top + drg_h - ev.pageY;
          var pos_x = this.wrapper.offset().left + drg_w - ev.pageX;
  
          jQuery(document).on('touchmove.jBox-draggable-' + this.id + ' mousemove.jBox-draggable-' + this.id, function (ev) {
            // Fire onDragStart event when jBox moves
            if (!this.dragging && this.draggingStartX != ev.pageX && this.draggingStartY != ev.pageY) {
              this._fireEvent('onDragStart');
              this.dragging = true;
            }
  
            // Adjust position
            this.wrapper.offset({
              top: ev.pageY + pos_y - drg_h,
              left: ev.pageX + pos_x - drg_w
            });
          }.bind(this));
          ev.preventDefault();
  
        }.bind(this)).on('touchend mouseup', function () {
          // Remove drag event
          jQuery(document).off('touchmove.jBox-draggable-' + this.id + ' mousemove.jBox-draggable-' + this.id);
  
          // Fire onDragEnd event
          this.dragging && this._fireEvent('onDragEnd');
  
          // Reset dragging reference
          this.dragging = false;
  
          if ((this.type == 'Modal' || this.type == 'Confirm') && this.options.holdPosition) {
            // Drag end captures new position
            var jBoxOffset = jQuery('#' + this.id).offset(),
              pos = {
                x: jBoxOffset.left - jQuery(document).scrollLeft(),
                y: jBoxOffset.top - jQuery(document).scrollTop()
              };
            this.position({position: pos, offset: {x: 0, y: 0}});
          }
        }.bind(this));
  
        // Get highest z-index
        if (!this.trueModal) {
          jBox.zIndexMaxDragover = !jBox.zIndexMaxDragover ? this.options.zIndex : Math.max(jBox.zIndexMaxDragover, this.options.zIndex);
        }
  
        return this;
      };
  
      // Create jBox
  
      this._create = function ()
      {
        // Abort if jBox was created already
        if (this.wrapper) return;
  
        // Create wrapper
        this.wrapper = jQuery('<div/>', {
          id: this.id,
          'class': 'jBox-wrapper' + (this.type ? ' jBox-' + this.type : '') + (this.options.theme ? ' jBox-' + this.options.theme : '') + (this.options.addClass ? ' ' + this.options.addClass : '')
        }).css({
          position: (this.options.fixed ? 'fixed' : 'absolute'),
          display: 'none',
          opacity: 0,
          zIndex: this.options.zIndex
  
          // Save the jBox instance in the wrapper, so you can get access to your jBox when you only have the element
        }).data('jBox', this);
  
        // Add mouseleave event, only close jBox when the new target is not the source element
        this.options.closeOnMouseleave && this.wrapper.on('mouseleave', function (ev) {
          !this.source || !(ev.relatedTarget == this.source[0] || jQuery.inArray(this.source[0], jQuery(ev.relatedTarget).parents('*')) !== -1) && this.close();
        }.bind(this));
  
        // Add closeOnClick: 'box' events
        (this.options.closeOnClick == 'box') && this.wrapper.on('click', function () { this.close({ignoreDelay: true}); }.bind(this));
  
        // Create container
        this.container = jQuery('<div class="jBox-container"/>').appendTo(this.wrapper);
  
        // Create content
        this.content = jQuery('<div class="jBox-content"/>').appendTo(this.container);
  
        // Create footer
        this.options.footer && (this.footer = jQuery('<div class="jBox-footer"/>').append(this.options.footer).appendTo(this.container));
  
        // Isolate scrolling
        this.options.isolateScroll && this._isolateScroll(this.content);
  
        // Create close button
        if (this.options.closeButton) {
          var closeButtonSVG = this._createSVG('svg', [['viewBox', '0 0 24 24']]);
          closeButtonSVG.appendChild(this._createSVG('path', [['d', 'M22.2,4c0,0,0.5,0.6,0,1.1l-6.8,6.8l6.9,6.9c0.5,0.5,0,1.1,0,1.1L20,22.3c0,0-0.6,0.5-1.1,0L12,15.4l-6.9,6.9c-0.5,0.5-1.1,0-1.1,0L1.7,20c0,0-0.5-0.6,0-1.1L8.6,12L1.7,5.1C1.2,4.6,1.7,4,1.7,4L4,1.7c0,0,0.6-0.5,1.1,0L12,8.5l6.8-6.8c0.5-0.5,1.1,0,1.1,0L22.2,4z']]));
          this.closeButton = jQuery('<div class="jBox-closeButton jBox-noDrag"/>').on('click', function (ev) { this.close({ignoreDelay: true}); }.bind(this)).append(closeButtonSVG);
  
          // Add close button to jBox container
          if (this.options.closeButton == 'box' || (this.options.closeButton === true && !this.options.overlay && !this.options.title && !this.options.getTitle)) {
            this.wrapper.addClass('jBox-closeButton-box');
            this.closeButton.appendTo(this.container);
          }
        }
  
        // Append jBox to DOM
        this.wrapper.appendTo(this.options.appendTo);
  
        // Fix adjustDistance if there is a close button in the box
        this.wrapper.find('.jBox-closeButton').length &&  jQuery.each(['top', 'right', 'bottom', 'left'], function (index, pos) {
          this.wrapper.find('.jBox-closeButton').css(pos) && this.wrapper.find('.jBox-closeButton').css(pos) != 'auto' && (this.options.adjustDistance[pos] = Math.max(this.options.adjustDistance[pos], this.options.adjustDistance[pos] + (((parseInt(this.wrapper.find('.jBox-closeButton').css(pos)) || 0) + (parseInt(this.container.css('border-' + pos + '-width')) || 0)) * -1)));
        }.bind(this));
  
        // Create pointer
        if (this.options.pointer) {
  
          // Get pointer vars and save globally
          this.pointer = {
            position: (this.options.pointTo != 'target') ? this.options.pointTo : this._getOpp(this.outside),
            xy: (this.options.pointTo != 'target') ? this._getXY(this.options.pointTo) : this._getXY(this.outside),
            align: 'center',
            offset: 0
          };
  
          this.pointer.element = jQuery('<div class="jBox-pointer jBox-pointer-' + this.pointer.position + '"/>').appendTo(this.wrapper);
          this.pointer.dimensions = {
            x: this.pointer.element.outerWidth(),
            y: this.pointer.element.outerHeight()
          };
  
          if (jQuery.type(this.options.pointer) == 'string') {
            var split = this.options.pointer.split(':');
            split[0] && (this.pointer.align = split[0]);
            split[1] && (this.pointer.offset = parseInt(split[1]));
          }
          this.pointer.alignAttribute = (this.pointer.xy == 'x' ? (this.pointer.align == 'bottom' ? 'bottom' : 'top') : (this.pointer.align == 'right' ? 'right' : 'left'));
  
          // Set wrapper CSS
          this.wrapper.css('padding-' + this.pointer.position, this.pointer.dimensions[this.pointer.xy]);
  
          // Set pointer CSS
          this.pointer.element.css(this.pointer.alignAttribute, (this.pointer.align == 'center' ? '50%' : 0)).css('margin-' + this.pointer.alignAttribute, this.pointer.offset);
          this.pointer.margin = {};
          this.pointer.margin['margin-' + this.pointer.alignAttribute] = this.pointer.offset;
  
          // Add a transform to fix centered position
          (this.pointer.align == 'center') && this.pointer.element.css('transform', 'translate(' + (this.pointer.xy == 'y' ? (this.pointer.dimensions.x * -0.5 + 'px') : 0) + ', ' + (this.pointer.xy == 'x' ? (this.pointer.dimensions.y * -0.5 + 'px') : 0) + ')');
  
          this.pointer.element.css((this.pointer.xy == 'x' ? 'width' : 'height'), parseInt(this.pointer.dimensions[this.pointer.xy]) + parseInt(this.container.css('border-' + this.pointer.alignAttribute + '-width')));
  
          // Add class to wrapper for CSS access
          this.wrapper.addClass('jBox-pointerPosition-' + this.pointer.position);
        }
  
        // Set title and content
        this.setContent(this.options.content, true);
        this.setTitle(this.options.title, true);
  
        this.options.draggable && this._draggable();
  
        // Fire onCreated event
        this._fireEvent('onCreated');
      };
  
  
      // Create jBox onInit
  
      this.options.createOnInit && this._create();
  
  
      // Attach jBox
  
      this.options.attach && this.attach();
  
  
      // Attach document and window events
  
      this._attachEvents = function ()
      {
        // Cancel countdown on mouseenter if delayOnHover
        this.options.delayOnHover && jQuery('#' + this.id).on('mouseenter', function (ev) { this.isHovered = true; }.bind(this));
  
        // Resume countdown on mouseleave if delayOnHover
        this.options.delayOnHover && jQuery('#' + this.id).on('mouseleave', function (ev) { this.isHovered = false; }.bind(this));
  
        // Positioning events
        if ((this.options.adjustPosition || this.options.reposition) && !this.fixed && this.outside) {
  
          // Trigger position events when scrolling
          this.options.adjustTracker && jQuery(window).on('scroll.jBox-' + this.id, function (ev) { this.position(); }.bind(this));
  
          // Trigger position events when resizing
          (this.options.adjustPosition || this.options.reposition) && jQuery(window).on('resize.jBox-' + this.id, function (ev) { this.position(); }.bind(this));
        }
  
        // Mousemove events
        this.options.target == 'mouse' && jQuery('body').on('mousemove.jBox-' + this.id, function (ev) { this.position({mouseTarget: {top: ev.pageY, left: ev.pageX}}); }.bind(this));
      };
  
  
      // Detach document and window events
  
      this._detachEvents = function ()
      {
        // Closing event: closeOnEsc
        this.options.closeOnEsc && jQuery(document).off('keyup.jBox-' + this.id);
  
        // Closing event: closeOnClick
        (this.options.closeOnClick === true || this.options.closeOnClick == 'body') && jQuery(document).off('click.jBox-' + this.id);
  
        // Positioning events
        this.options.adjustTracker && jQuery(window).off('scroll.jBox-' + this.id);
        (this.options.adjustPosition || this.options.reposition) && jQuery(window).off('resize.jBox-' + this.id);
  
        // Mousemove events
        this.options.target == 'mouse' && jQuery('body').off('mousemove.jBox-' + this.id);
      };
  
  
      // Show overlay
  
      this._showOverlay = function ()
      {
        // Create the overlay if wasn't created already
        if (!this.overlay) {
  
          // Create element and append to the element where jBox is appended to
          this.overlay = jQuery('<div id="' + this.id + '-overlay"/>').addClass('jBox-overlay' + (this.type ? ' jBox-overlay-' + this.type : '')).css({
            display: 'none',
            opacity: 0,
            zIndex: this.options.zIndex - 1
          }).appendTo(this.options.appendTo);
  
          // Add close button to overlay
          (this.options.closeButton == 'overlay' || this.options.closeButton === true) && this.overlay.append(this.closeButton);
  
          // Add closeOnClick: 'overlay' events
          this.options.closeOnClick == 'overlay' && this.overlay.on('click', function () { this.close({ignoreDelay: true}); }.bind(this));
  
          // Adjust option adjustDistance if there is a close button in the overlay
          jQuery('#' + this.id + '-overlay .jBox-closeButton').length && (this.options.adjustDistance.top = Math.max(jQuery('#' + this.id + '-overlay .jBox-closeButton').outerHeight(), this.options.adjustDistance.top));
        }
  
        // Adjust zIndex
        if (this.adjustZIndexOnOpen === true) {
          this.overlay.css('zIndex', parseInt(this.wrapper.css('zIndex'), 10) - 1);
        }
  
        // Abort if overlay is already visible
        if (this.overlay.css('display') == 'block') return;
  
        // Show overlay
        this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 1}, {
          queue: false,
          duration: this.options.fade,
          start: function () { this.overlay.css({display: 'block'}); }.bind(this)
        })) : this.overlay.css({display: 'block', opacity: 1});
      };
  
  
      // Hide overlay
  
      this._hideOverlay = function ()
      {
        // Abort if the overlay wasn't created yet
        if (!this.overlay) return;
  
        // Hide overlay if no other jBox needs it
        this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 0}, {
          queue: false,
          duration: this.options.fade,
          complete: function () { this.overlay.css({display: 'none'}); }.bind(this)
        })) : this.overlay.css({display: 'none', opacity: 0});
      };
  
  
      // Get the correct jBox dimensions by moving jBox out of viewport
  
      this._exposeDimensions = function ()
      {
        // Move wrapper out of viewport
        this.wrapper.css({
          top: -10000,
          left: -10000,
          right: 'auto',
          bottom: 'auto'
        });
  
        // Get jBox dimensions
        var jBoxDimensions = {
          x: this.wrapper.outerWidth(),
          y: this.wrapper.outerHeight()
        };
  
        // Reset position to viewport
        this.wrapper.css({
          top: 'auto',
          left: 'auto'
        });
  
        return jBoxDimensions;
      };
  
  
      // Generate CSS for animations and append to header
  
      this._generateAnimationCSS = function ()
      {
        // Get open and close animations if none provided
        (jQuery.type(this.options.animation) != 'object') && (this.options.animation = {
          pulse: {open: 'pulse', close: 'zoomOut'},
          zoomIn: {open: 'zoomIn', close: 'zoomIn'},
          zoomOut: {open: 'zoomOut', close: 'zoomOut'},
          move: {open: 'move', close: 'move'},
          slide: {open: 'slide', close: 'slide'},
          flip: {open: 'flip', close: 'flip'},
          tada: {open: 'tada', close: 'zoomOut'}
        }[this.options.animation]);
  
        // Abort if animation not found
        if (!this.options.animation) return null;
  
        // Get direction var
        this.options.animation.open && (this.options.animation.open = this.options.animation.open.split(':'));
        this.options.animation.close && (this.options.animation.close = this.options.animation.close.split(':'));
        this.options.animation.openDirection = this.options.animation.open[1] ? this.options.animation.open[1] : null;
        this.options.animation.closeDirection = this.options.animation.close[1] ? this.options.animation.close[1] : null;
        this.options.animation.open && (this.options.animation.open = this.options.animation.open[0]);
        this.options.animation.close && (this.options.animation.close = this.options.animation.close[0]);
  
        // Add 'Open' and 'Close' to animation names
        this.options.animation.open && (this.options.animation.open += 'Open');
        this.options.animation.close && (this.options.animation.close += 'Close');
  
        // All animations
        var animations = {
          pulse: {
            duration: 350,
            css: [['0%', 'scale(1)'], ['50%', 'scale(1.1)'], ['100%', 'scale(1)']]
          },
          zoomInOpen: {
            duration: (this.options.fade || 180),
            css: [['0%', 'scale(0.9)'], ['100%', 'scale(1)']]
          },
          zoomInClose: {
            duration: (this.options.fade || 180),
            css: [['0%', 'scale(1)'], ['100%', 'scale(0.9)']]
          },
          zoomOutOpen: {
            duration: (this.options.fade || 180),
            css: [['0%', 'scale(1.1)'], ['100%', 'scale(1)']]
          },
          zoomOutClose: {
            duration: (this.options.fade || 180),
            css: [['0%', 'scale(1)'], ['100%', 'scale(1.1)']]
          },
          moveOpen: {
            duration: (this.options.fade || 180),
            positions: {top: {'0%': -12}, right: {'0%': 12}, bottom: {'0%': 12}, left: {'0%': -12}},
            css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
          },
          moveClose: {
            duration: (this.options.fade || 180),
            timing: 'ease-in',
            positions: {top: {'100%': -12}, right: {'100%': 12}, bottom: {'100%': 12}, left: {'100%': -12}},
            css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
          },
          slideOpen: {
            duration: 400,
            positions: {top: {'0%': -400}, right: {'0%': 400}, bottom: {'0%': 400}, left: {'0%': -400}},
            css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
          },
          slideClose: {
            duration: 400,
            timing: 'ease-in',
            positions: {top: {'100%': -400}, right: {'100%': 400}, bottom: {'100%': 400}, left: {'100%': -400}},
            css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
          },
          flipOpen: {
            duration: 600,
            css: [['0%', 'perspective(400px) rotateX(90deg)'], ['40%', 'perspective(400px) rotateX(-15deg)'], ['70%', 'perspective(400px) rotateX(15deg)'], ['100%', 'perspective(400px) rotateX(0deg)']]
          },
          flipClose: {
            duration: (this.options.fade || 300),
            css: [['0%', 'perspective(400px) rotateX(0deg)'], ['100%', 'perspective(400px) rotateX(90deg)']]
          },
          tada: {
            duration: 800,
            css: [['0%', 'scale(1)'], ['10%, 20%', 'scale(0.9) rotate(-3deg)'], ['30%, 50%, 70%, 90%', 'scale(1.1) rotate(3deg)'], ['40%, 60%, 80%', 'scale(1.1) rotate(-3deg)'], ['100%', 'scale(1) rotate(0)']]
          }
        };
  
        // Set Open and Close names for standalone animations
        jQuery.each(['pulse', 'tada'], function (index, item) { animations[item + 'Open'] = animations[item + 'Close'] = animations[item]; });
  
        // Function to generate the CSS for the keyframes
        var generateKeyframeCSS = function (ev, position)
        {
          // Generate keyframes CSS
          keyframe_css = '@keyframes jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
          jQuery.each(animations[this.options.animation[ev]].css, function (index, item) {
            var translate = position ? item[1].replace('%XY', this._getXY(position).toUpperCase()) : item[1];
            animations[this.options.animation[ev]].positions && (translate = translate.replace('%V', animations[this.options.animation[ev]].positions[position][item[0]]));
            keyframe_css += item[0] + ' {transform:' + translate + ';}';
          }.bind(this));
          keyframe_css += '}';
  
          // Generate class CSS
          keyframe_css += '.jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
          keyframe_css += 'animation-duration: ' + animations[this.options.animation[ev]].duration + 'ms;';
          keyframe_css += 'animation-name: jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ';';
          keyframe_css += animations[this.options.animation[ev]].timing ? ('animation-timing-function: ' + animations[this.options.animation[ev]].timing + ';') : '';
          keyframe_css += '}';
  
          return keyframe_css;
        }.bind(this);
  
        // Generate css for each event and positions
        this._animationCSS = '';
        jQuery.each(['open', 'close'], function (index, ev)
        {
          // No CSS needed for closing with no fade
          if (!this.options.animation[ev] || !animations[this.options.animation[ev]] || (ev == 'close' && !this.options.fade)) return '';
  
          // Generate CSS
          animations[this.options.animation[ev]].positions ?
            jQuery.each(['top', 'right', 'bottom', 'left'], function (index2, position) { this._animationCSS += generateKeyframeCSS(ev, position); }.bind(this)) :
            this._animationCSS += generateKeyframeCSS(ev);
        }.bind(this));
  
      };
  
  
      // Add css for animations
  
      this.options.animation && this._generateAnimationCSS();
  
  
      // Block body clicks for 10ms to prevent extra event triggering
  
      this._blockBodyClick = function ()
      {
        this.blockBodyClick = true;
        setTimeout(function () { this.blockBodyClick = false; }.bind(this), 10);
      };
  
  
      // Animations
  
      this._animate = function (ev)
      {
        // The event which triggers the animation
        !ev && (ev = this.isOpen ? 'open' : 'close');
  
        // Don't animate when closing with no fade duration
        if (!this.options.fade && ev == 'close') return null;
  
        // Get the current position, use opposite if jBox is flipped
        var animationDirection = (this.options.animation[ev + 'Direction'] || ((this.align != 'center') ? this.align : this.options.attributes.x));
        this.flipped && this._getXY(animationDirection) == (this._getXY(this.align)) && (animationDirection = this._getOpp(animationDirection));
  
        // Add event and position classes
        var classnames = 'jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + ' jBox-' + this.id + '-animation-' + this.options.animation[ev] + '-' + ev + '-' + animationDirection;
        this.wrapper.addClass(classnames);
  
        // Get duration of animation
        var animationDuration = parseFloat(this.wrapper.css('animation-duration')) * 1000;
        ev == 'close' && (animationDuration = Math.min(animationDuration, this.options.fade));
  
        // Remove animation classes when animation is finished
        setTimeout(function () { this.wrapper.removeClass(classnames); }.bind(this), animationDuration);
      };
  
  
      // Abort an animation
  
      this._abortAnimation = function ()
      {
        // Remove all animation classes
        var classes = this.wrapper.attr('class').split(' ').filter(function (c) {
          return c.lastIndexOf('jBox-' + this.id + '-animation', 0) !== 0;
        }.bind(this));
        this.wrapper.attr('class', classes.join(' '));
      };
  
  
      // Adjust dimensions when browser is resized
  
      if (this.options.responsiveWidth || this.options.responsiveHeight)
      {
        // Responsive positioning overrides options adjustPosition and reposition
        // TODO: Only add this resize event when the other one from adjustPosition and reposition was not set
        jQuery(window).on('resize.responsivejBox-' + this.id, function (ev) { if (this.isOpen) { this.position(); } }.bind(this));
      }
  
  
      // Fix audio options
  
      jQuery.type(this.options.preloadAudio) === 'string' && (this.options.preloadAudio = [this.options.preloadAudio]);
      jQuery.type(this.options.audio) === 'string' && (this.options.audio = {open: this.options.audio});
      jQuery.type(this.options.volume) === 'number' && (this.options.volume = {open: this.options.volume, close: this.options.volume});
  
      if (this.options.preloadAudio === true && this.options.audio) {
        this.options.preloadAudio = [];
        jQuery.each(this.options.audio, function (index, url) {
          this.options.preloadAudio.push(url + '.mp3');
          this.options.preloadAudio.push(url + '.ogg');
        }.bind(this));
      }
  
  
      // Preload audio files
  
      this.options.preloadAudio.length && jQuery.each(this.options.preloadAudio, function (index, url) {
        var audio = new Audio();
        audio.src = url;
        audio.preload = 'auto';
      });
  
  
      // Fire onInit event
  
      this._fireEvent('onInit');
  
  
      return this;
    }