(function(cc) {

// stop from running again, if accidently included more than once.
if (cc.hasInitialised) return;

var util = {
  // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
  escapeRegExp: function(str) {
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
  },

  hasClass: function(element, selector) {
    var s = ' ';
    return element.nodeType === 1 &&
      (s + element.className + s).replace(/[\n\t]/g, s).indexOf(s + selector + s) >= 0;
  },

  addClass: function(element, className) {
    element.className += ' ' + className;
  },

  removeClass: function(element, className) {
    var regex = new RegExp('\\b' + this.escapeRegExp(className) + '\\b');
    element.className = element.className.replace(regex, '');
  },

  interpolateString: function(str, callback) {
    var marker = /{{([a-z][a-z0-9\-_]*)}}/ig;
    return str.replace(marker, function(matches) {
      return callback(arguments[1]) || '';
    })
  },

  getCookie: function(name) {
    var value = '; ' + document.cookie;
    var parts = value.split('; ' + name + '=');
    return parts.length != 2 ?
      undefined : parts.pop().split(';').shift();
  },

  setCookie: function(name, value, expiryDays, domain, path) {
    var exdate = new Date();
    exdate.setDate(exdate.getDate() + (expiryDays || 365));

    var cookie = [
      name + '=' + value,
      'expires=' + exdate.toUTCString(),
      'path=' + (path || '/')
    ];

    if (domain) {
      cookie.push('domain=' + domain);
    }
    document.cookie = cookie.join(';');
  },

  // only used for extending the initial options
  deepExtend: function(target, source) {
    for (var prop in source) {
      if (source.hasOwnProperty(prop)) {
        if (prop in target && this.isPlainObject(target[prop]) && this.isPlainObject(source[prop])) {
          this.deepExtend(target[prop], source[prop]);
        } else {
          target[prop] = source[prop];
        }
      }
    }
    return target;
  },

  // only used for throttling the 'mousemove' event (used for animating the revoke button when `animateRevokable` is true)
  throttle: function(callback, limit) {
    var wait = false;
    return function() {
      if (!wait) {
        callback.apply(this, arguments);
        wait = true;
        setTimeout(function() {
          wait = false;
        }, limit);
      }
    }
  },

  // only used for hashing json objects (used for hash mapping palette objects, used when custom colours are passed through JavaScript)
  hash: function(str) {
    var hash = 0,
      i, chr, len;
    if (str.length === 0) return hash;
    for (i = 0, len = str.length; i < len; ++i) {
      chr = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + chr;
      hash |= 0;
    }
    return hash;
  },

  normaliseHex: function(hex) {
    if (hex[0] == '#') {
      hex = hex.substr(1);
    }
    if (hex.length == 3) {
      hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    return hex;
  },

  // used to get text colors if not set
  getContrast: function(hex) {
    hex = this.normaliseHex(hex);
    var r = parseInt(hex.substr(0, 2), 16);
    var g = parseInt(hex.substr(2, 2), 16);
    var b = parseInt(hex.substr(4, 2), 16);
    var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
    return (yiq >= 128) ? '#000' : '#fff';
  },

  // used to change color on highlight
  getLuminance: function(hex) {
    var num = parseInt(this.normaliseHex(hex), 16), 
        amt = 38,
        R = (num >> 16) + amt,
        B = (num >> 8 & 0x00FF) + amt,
        G = (num & 0x0000FF) + amt;
    var newColour = (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (B<255?B<1?0:B:255)*0x100 + (G<255?G<1?0:G:255)).toString(16).slice(1);
    return '#'+newColour;
  },

  isMobile: function() {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  },

  isPlainObject: function(obj) {
    // The code "typeof obj === 'object' && obj !== null" allows Array objects
    return typeof obj === 'object' && obj !== null && obj.constructor == Object;
  },
};

// valid cookie values
cc.status = {
  deny: 'deny',
  allow: 'allow',
  dismiss: 'dismiss'
};

// detects the `transitionend` event name
cc.transitionEnd = (function() {
  var el = document.createElement('div');
  var trans = {
    t: "transitionend",
    OT: "oTransitionEnd",
    msT: "MSTransitionEnd",
    MozT: "transitionend",
    WebkitT: "webkitTransitionEnd",
  };

  for (var prefix in trans) {
    if (trans.hasOwnProperty(prefix) && typeof el.style[prefix + 'ransition'] != 'undefined') {
      return trans[prefix];
    }
  }
  return '';
}());

cc.hasTransition = !!cc.transitionEnd;

// array of valid regexp escaped statuses
var __allowedStatuses = Object.keys(cc.status).map(util.escapeRegExp);

// contains references to the custom <style> tags
cc.customStyles = {};

cc.Popup = (function() {

  var defaultOptions = {

    // if false, this prevents the popup from showing (useful for giving to control to another piece of code)
    enabled: true,

    // optional (expecting a HTML element) if passed, the popup is appended to this element. default is `document.body`
    container: null,

    // defaults cookie options - it is RECOMMENDED to set these values to correspond with your server
    cookie: {
      // This is the name of this cookie - you can ignore this
      name: 'cookieconsent_status',

      // This is the url path that the cookie 'name' belongs to. The cookie can only be read at this location
      path: '/',

      // This is the domain that the cookie 'name' belongs to. The cookie can only be read on this domain.
      //  - Guide to cookie domains - http://erik.io/blog/2014/03/04/definitive-guide-to-cookie-domains/
      domain: '',

      // The cookies expire date, specified in days (specify -1 for no expiry)
      expiryDays: 365,
    },

    // these callback hooks are called at certain points in the program execution
    onPopupOpen: function() {},
    onPopupClose: function() {},
    onInitialise: function(status) {},
    onStatusChange: function(status, chosenBefore) {},
    onRevokeChoice: function() {},

    // each item defines the inner text for the element that it references
    content: {
      header: 'Cookies used on the website!',
      message: 'This website uses cookies to ensure you get the best experience on our website.',
      dismiss: 'Got it!',
      allow: 'Allow cookies',
      deny: 'Decline',
      link: 'Learn more',
      href: 'http://cookiesandyou.com',
      close: '&#x274c;',
    },

    // This is the HTML for the elements above. The string {{header}} will be replaced with the equivalent text below.
    // You can remove "{{header}}" and write the content directly inside the HTML if you want.
    //
    //  - ARIA rules suggest to ensure controls are tabbable (so the browser can find the first control),
    //    and to set the focus to the first interactive control (http://w3c.github.io/aria-in-html/)
    elements: {
      header: '<span class="cc-header">{{header}}</span>&nbsp;',
      message: '<span id="cookieconsent:desc" class="cc-message">{{message}}</span>',
      messagelink: '<span id="cookieconsent:desc" class="cc-message">{{message}} <a aria-label="learn more about cookies" role=button tabindex="0" class="cc-link" href="{{href}}" rel="noopener noreferrer nofollow" target="_blank">{{link}}</a></span>',
      dismiss: '<a aria-label="dismiss cookie message" role=button tabindex="0" class="cc-btn cc-dismiss">{{dismiss}}</a>',
      allow: '<a aria-label="allow cookies" role=button tabindex="0"  class="cc-btn cc-allow">{{allow}}</a>',
      deny: '<a aria-label="deny cookies" role=button tabindex="0" class="cc-btn cc-deny">{{deny}}</a>',
      link: '<a aria-label="learn more about cookies" role=button tabindex="0" class="cc-link" href="{{href}}" target="_blank">{{link}}</a>',
      close: '<span aria-label="dismiss cookie message" role=button tabindex="0" class="cc-close">{{close}}</span>',

      //compliance: compliance is also an element, but it is generated by the application, depending on `type` below
    },

    // The placeholders {{classes}} and {{children}} both get replaced during initialisation:
    //  - {{classes}} is where additional classes get added
    //  - {{children}} is where the HTML children are placed
    window: '<div role="dialog" aria-live="polite" aria-label="cookieconsent" aria-describedby="cookieconsent:desc" class="cc-window {{classes}}"><!--googleoff: all-->{{children}}<!--googleon: all--></div>',

    // This is the html for the revoke button. This only shows up after the user has selected their level of consent
    // It can be enabled of disabled using the `revokable` option
    revokeBtn: '<div class="cc-revoke {{classes}}">Cookie Policy</div>',

    // define types of 'compliance' here. '{{value}}' strings in here are linked to `elements`
    compliance: {
      'info': '<div class="cc-compliance">{{dismiss}}</div>',
      'opt-in': '<div class="cc-compliance cc-highlight">{{dismiss}}{{allow}}</div>',
      'opt-out': '<div class="cc-compliance cc-highlight">{{deny}}{{dismiss}}</div>',
    },

    // select your type of popup here
    type: 'info', // refers to `compliance` (in other words, the buttons that are displayed)

    // define layout layouts here
    layouts: {
      // the 'block' layout tend to be for square floating popups
      'basic': '{{messagelink}}{{compliance}}',
      'basic-close': '{{messagelink}}{{compliance}}{{close}}',
      'basic-header': '{{header}}{{message}}{{link}}{{compliance}}',

      // add a custom layout here, then add some new css with the class '.cc-layout-my-cool-layout'
      //'my-cool-layout': '<div class="my-special-layout">{{message}}{{compliance}}</div>{{close}}',
    },

    // default layout (see above)
    layout: 'basic',

    // this refers to the popup windows position. we currently support:
    //  - banner positions: top, bottom
    //  - floating positions: top-left, top-right, bottom-left, bottom-right
    //
    // adds a class `cc-floating` or `cc-banner` which helps when styling
    position: 'bottom', // default position is 'bottom'

    // Available styles
    //    -block (default, no extra classes)
    //    -edgeless
    //    -classic
    // use your own style name and use `.cc-theme-STYLENAME` class in CSS to edit.
    // Note: style "wire" is used for the configurator, but has no CSS styles of its own, only palette is used.
    theme: 'block',

    // The popup is `fixed` by default, but if you want it to be static (inline with the page content), set this to false
    // Note: by default, we animate the height of the popup from 0 to full size
    static: false,

    // if you want custom colours, pass them in here. this object should look like this.
    // ideally, any custom colours/themes should be created in a separate style sheet, as this is more efficient.
    //   {
    //     popup: {background: '#000000', text: '#fff', link: '#fff'},
    //     button: {background: 'transparent', border: '#f8e71c', text: '#f8e71c'},
    //     highlight: {background: '#f8e71c', border: '#f8e71c', text: '#000000'},
    //   }
    // `highlight` is optional and extends `button`. if it exists, it will apply to the first button
    // only background needs to be defined for every element. if not set, other colors can be calculated from it
    palette: null,

    // Some countries REQUIRE that a user can change their mind. You can configure this yourself.
    // Most of the time this should be false, but the `cookieconsent.law` can change this to `true` if it detects that it should
    revokable: false,

    // if true, the revokable button will tranlate in and out
    animateRevokable: true,

    // used to disable link on existing layouts
    // replaces element messagelink with message and removes content of link
    showLink: true,

    // set value as scroll range to enable
    dismissOnScroll: false,

    // set value as time in milliseconds to autodismiss after set time
    dismissOnTimeout: false,

    // The application automatically decide whether the popup should open.
    // Set this to false to prevent this from happening and to allow you to control the behaviour yourself
    autoOpen: true,

    // By default the created HTML is automatically appended to the container (which defaults to <body>). You can prevent this behaviour
    // by setting this to false, but if you do, you must attach the `element` yourself, which is a public property of the popup instance:
    // 
    //     var instance = cookieconsent.factory(options);
    //     document.body.appendChild(instance.element);
    //
    autoAttach: true,

    // simple whitelist/blacklist for pages. specify page by:
    //   - using a string : '/index.html'           (matches '/index.html' exactly) OR
    //   - using RegExp   : /\/page_[\d]+\.html/    (matched '/page_1.html' and '/page_2.html' etc)
    whitelistPage: [],
    blacklistPage: [],

    // If this is defined, then it is used as the inner html instead of layout. This allows for ultimate customisation.
    // Be sure to use the classes `cc-btn` and `cc-allow`, `cc-deny` or `cc-dismiss`. They enable the app to register click
    // handlers. You can use other pre-existing classes too. See `src/styles` folder.
    overrideHTML: null,
  };

  function CookiePopup() {
    this.initialise.apply(this, arguments);
  }

  CookiePopup.prototype.initialise = function(options) {
    if (this.options) {
      this.destroy(); // already rendered
    }

    // set options back to default options
    util.deepExtend(this.options = {}, defaultOptions);

    // merge in user options
    if (util.isPlainObject(options)) {
      util.deepExtend(this.options, options);
    }

    // returns true if `onComplete` was called
    if (checkCallbackHooks.call(this)) {
      // user has already answered
      this.options.enabled = false;
    }

    // apply blacklist / whitelist
    if (arrayContainsMatches(this.options.blacklistPage, location.pathname)) {
      this.options.enabled = false;
    }
    if (arrayContainsMatches(this.options.whitelistPage, location.pathname)) {
      this.options.enabled = true;
    }

    // the full markup either contains the wrapper or it does not (for multiple instances)
    var cookiePopup = this.options.window
      .replace('{{classes}}', getPopupClasses.call(this).join(' '))
      .replace('{{children}}', getPopupInnerMarkup.call(this));

    // if user passes html, use it instead
    var customHTML = this.options.overrideHTML;
    if (typeof customHTML == 'string' && customHTML.length) {
      cookiePopup = customHTML;
    }

    // if static, we need to grow the element from 0 height so it doesn't jump the page
    // content. we wrap an element around it which will mask the hidden content
    if (this.options.static) {
      // `grower` is a wrapper div with a hidden overflow whose height is animated
      var wrapper = appendMarkup.call(this, '<div class="cc-grower">' + cookiePopup + '</div>');

      wrapper.style.display = ''; // set it to visible (because appendMarkup hides it)
      this.element = wrapper.firstChild; // get the `element` reference from the wrapper
      this.element.style.display = 'none';
      util.addClass(this.element, 'cc-invisible');
    } else {
      this.element = appendMarkup.call(this, cookiePopup);
    }

    applyAutoDismiss.call(this);

    applyRevokeButton.call(this);

    if (this.options.autoOpen) {
      this.autoOpen();
    }
  };

  CookiePopup.prototype.destroy = function() {
    if (this.onButtonClick && this.element) {
      this.element.removeEventListener('click', this.onButtonClick);
      this.onButtonClick = null;
    }

    if (this.dismissTimeout) {
      clearTimeout(this.dismissTimeout);
      this.dismissTimeout = null;
    }

    if (this.onWindowScroll) {
      window.removeEventListener('scroll', this.onWindowScroll);
      this.onWindowScroll = null;
    }

    if (this.onMouseMove) {
      window.removeEventListener('mousemove', this.onMouseMove);
      this.onMouseMove = null;
    }

    if (this.element && this.element.parentNode) {
      this.element.parentNode.removeChild(this.element);
    }
    this.element = null;

    if (this.revokeBtn && this.revokeBtn.parentNode) {
      this.revokeBtn.parentNode.removeChild(this.revokeBtn);
    }
    this.revokeBtn = null;

    removeCustomStyle(this.options.palette);
    this.options = null;
  };

  CookiePopup.prototype.open = function(callback) {
    if (!this.element) return;

    if (!this.isOpen()) {
      if (cc.hasTransition) {
        this.fadeIn();
      } else {
        this.element.style.display = '';
      }

      if (this.options.revokable) {
        this.toggleRevokeButton();
      }
      this.options.onPopupOpen.call(this);
    }

    return this;
  };

  CookiePopup.prototype.close = function(showRevoke) {
    if (!this.element) return;

    if (this.isOpen()) {
      if (cc.hasTransition) {
        this.fadeOut();
      } else {
        this.element.style.display = 'none';
      }

      if (showRevoke && this.options.revokable) {
        this.toggleRevokeButton(true);
      }
      this.options.onPopupClose.call(this);
    }

    return this;
  };

  CookiePopup.prototype.fadeIn = function() {
    var el = this.element;

    if (!cc.hasTransition || !el)
      return;

    // This should always be called AFTER fadeOut (which is governed by the 'transitionend' event).
    // 'transitionend' isn't all that reliable, so, if we try and fadeIn before 'transitionend' has
    // has a chance to run, then we run it ourselves
    if (this.afterTransition) {
      afterFadeOut.call(this, el)
    }

    if (util.hasClass(el, 'cc-invisible')) {
      el.style.display = '';

      if (this.options.static) {
        var height = this.element.clientHeight;
        this.element.parentNode.style.maxHeight = height + 'px';
      }

      var fadeInTimeout = 20; // (ms) DO NOT MAKE THIS VALUE SMALLER. See below

      // Although most browsers can handle values less than 20ms, it should remain above this value.
      // This is because we are waiting for a "browser redraw" before we remove the 'cc-invisible' class.
      // If the class is remvoed before a redraw could happen, then the fadeIn effect WILL NOT work, and
      // the popup will appear from nothing. Therefore we MUST allow enough time for the browser to do
      // its thing. The actually difference between using 0 and 20 in a set timeout is neglegible anyway
      this.openingTimeout = setTimeout(afterFadeIn.bind(this, el), fadeInTimeout);
    }
  };

  CookiePopup.prototype.fadeOut = function() {
    var el = this.element;

    if (!cc.hasTransition || !el)
      return;

    if (this.openingTimeout) {
      clearTimeout(this.openingTimeout);
      afterFadeIn.bind(this, el);
    }

    if (!util.hasClass(el, 'cc-invisible')) {
      if (this.options.static) {
        this.element.parentNode.style.maxHeight = '';
      }

      this.afterTransition = afterFadeOut.bind(this, el);
      el.addEventListener(cc.transitionEnd, this.afterTransition);

      util.addClass(el, 'cc-invisible');
    }
  };

  CookiePopup.prototype.isOpen = function() {
    return this.element && this.element.style.display == '' && (cc.hasTransition ? !util.hasClass(this.element, 'cc-invisible') : true);
  };

  CookiePopup.prototype.toggleRevokeButton = function(show) {
    if (this.revokeBtn) this.revokeBtn.style.display = show ? '' : 'none';
  };

  CookiePopup.prototype.revokeChoice = function(preventOpen) {
    this.options.enabled = true;
    this.clearStatus();

    this.options.onRevokeChoice.call(this);

    if (!preventOpen) {
      this.autoOpen();
    }
  };

  // returns true if the cookie has a valid value
  CookiePopup.prototype.hasAnswered = function(options) {
    return Object.keys(cc.status).indexOf(this.getStatus()) >= 0;
  };

  // returns true if the cookie indicates that consent has been given
  CookiePopup.prototype.hasConsented = function(options) {
    var val = this.getStatus();
    return val == cc.status.allow || val == cc.status.dismiss;
  };

  // opens the popup if no answer has been given
  CookiePopup.prototype.autoOpen = function(options) {
    !this.hasAnswered() && this.options.enabled && this.open();
  };

  CookiePopup.prototype.setStatus = function(status) {
    var c = this.options.cookie;
    var value = util.getCookie(c.name);
    var chosenBefore = Object.keys(cc.status).indexOf(value) >= 0;

    // if `status` is valid
    if (Object.keys(cc.status).indexOf(status) >= 0) {
      util.setCookie(c.name, status, c.expiryDays, c.domain, c.path);

      this.options.onStatusChange.call(this, status, chosenBefore);
    } else {
      this.clearStatus();
    }
  };

  CookiePopup.prototype.getStatus = function() {
    return util.getCookie(this.options.cookie.name);
  };

  CookiePopup.prototype.clearStatus = function() {
    var c = this.options.cookie;
    util.setCookie(c.name, '', -1, c.domain, c.path);
  };

  // This needs to be called after 'fadeIn'. This is the code that actually causes the fadeIn to work
  // There is a good reason why it's called in a timeout. Read 'fadeIn';
  function afterFadeIn(el) {
    this.openingTimeout = null;
    util.removeClass(el, 'cc-invisible');
  }

  // This is called on 'transitionend' (only on the transition of the fadeOut). That's because after we've faded out, we need to
  // set the display to 'none' (so there aren't annoying invisible popups all over the page). If for whenever reason this function
  // is not called (lack of support), the open/close mechanism will still work.
  function afterFadeOut(el) {
    el.style.display = 'none'; // after close and before open, the display should be none
    el.removeEventListener(cc.transitionEnd, this.afterTransition);
    this.afterTransition = null;
  }

  // this function calls the `onComplete` hook and returns true (if needed) and returns false otherwise
  function checkCallbackHooks() {
    var complete = this.options.onInitialise.bind(this);

    if (!window.navigator.cookieEnabled) {
      complete(cc.status.deny);
      return true;
    }

    if (window.CookiesOK || window.navigator.CookiesOK) {
      complete(cc.status.allow);
      return true;
    }

    var allowed = Object.keys(cc.status);
    var answer = this.getStatus();
    var match = allowed.indexOf(answer) >= 0;

    if (match) {
      complete(answer);
    }
    return match;
  }

  function getPositionClasses() {
    var positions = this.options.position.split('-'); // top, bottom, left, right
    var classes = [];

    // top, left, right, bottom
    positions.forEach(function(cur) {
      classes.push('cc-' + cur);
    });

    return classes;
  }

  function getPopupClasses() {
    var opts = this.options;
    var positionStyle = (opts.position == 'top' || opts.position == 'bottom') ? 'banner' : 'floating';

    if (util.isMobile()) {
      positionStyle = 'floating';
    }

    var classes = [
      'cc-' + positionStyle, // floating or banner
      'cc-type-' + opts.type, // add the compliance type
      'cc-theme-' + opts.theme, // add the theme
    ];

    if (opts.static) {
      classes.push('cc-static');
    }

    classes.push.apply(classes, getPositionClasses.call(this));

    // we only add extra styles if `palette` has been set to a valid value
    var didAttach = attachCustomPalette.call(this, this.options.palette);

    // if we override the palette, add the class that enables this
    if (this.customStyleSelector) {
      classes.push(this.customStyleSelector);
    }

    return classes;
  }

  function getPopupInnerMarkup() {
    var interpolated = {};
    var opts = this.options;

    // removes link if showLink is false
    if (!opts.showLink) {
      opts.elements.link = '';
      opts.elements.messagelink = opts.elements.message;
    }

    Object.keys(opts.elements).forEach(function(prop) {
      interpolated[prop] = util.interpolateString(opts.elements[prop], function(name) {
        var str = opts.content[name];
        return (name && typeof str == 'string' && str.length) ? str : '';
      })
    });

    // checks if the type is valid and defaults to info if it's not
    var complianceType = opts.compliance[opts.type];
    if (!complianceType) {
      complianceType = opts.compliance.info;
    }

    // build the compliance types from the already interpolated `elements`
    interpolated.compliance = util.interpolateString(complianceType, function(name) {
      return interpolated[name];
    });

    // checks if the layout is valid and defaults to basic if it's not
    var layout = opts.layouts[opts.layout];
    if (!layout) {
      layout = opts.layouts.basic;
    }

    return util.interpolateString(layout, function(match) {
      return interpolated[match];
    });
  }

  function appendMarkup(markup) {
    var opts = this.options;
    var div = document.createElement('div');
    var cont = (opts.container && opts.container.nodeType === 1) ? opts.container : document.body;

    div.innerHTML = markup;

    var el = div.children[0];

    el.style.display = 'none';

    if (util.hasClass(el, 'cc-window') && cc.hasTransition) {
      util.addClass(el, 'cc-invisible');
    }

    // save ref to the function handle so we can unbind it later
    this.onButtonClick = handleButtonClick.bind(this);

    el.addEventListener('click', this.onButtonClick);

    if (opts.autoAttach) {
      if (!cont.firstChild) {
        cont.appendChild(el);
      } else {
        cont.insertBefore(el, cont.firstChild)
      }
    }

    return el;
  }

  function handleButtonClick(event) {
    var targ = event.target;
    if (util.hasClass(targ, 'cc-btn')) {

      var matches = targ.className.match(new RegExp("\\bcc-(" + __allowedStatuses.join('|') + ")\\b"));
      var match = (matches && matches[1]) || false;

      if (match) {
        this.setStatus(match);
        this.close(true);
      }
    }
    if (util.hasClass(targ, 'cc-close')) {
      this.setStatus(cc.status.dismiss);
      this.close(true);
    }
    if (util.hasClass(targ, 'cc-revoke')) {
      this.revokeChoice();
    }
  }

  // I might change this function to use inline styles. I originally chose a stylesheet because I could select many elements with a
  // single rule (something that happened a lot), the apps has changed slightly now though, so inline styles might be more applicable.
  function attachCustomPalette(palette) {
    var hash = util.hash(JSON.stringify(palette));
    var selector = 'cc-color-override-' + hash;
    var isValid = util.isPlainObject(palette);

    this.customStyleSelector = isValid ? selector : null;

    if (isValid) {
      addCustomStyle(hash, palette, '.' + selector);
    }
    return isValid;
  }

  function addCustomStyle(hash, palette, prefix) {

    // only add this if a style like it doesn't exist
    if (cc.customStyles[hash]) {
      // custom style already exists, so increment the reference count
      ++cc.customStyles[hash].references;
      return;
    }

    var colorStyles = {};
    var popup = palette.popup;
    var button = palette.button;
    var highlight = palette.highlight;

    // needs background colour, text and link will be set to black/white if not specified
    if (popup) {
      // assumes popup.background is set
      popup.text = popup.text ? popup.text : util.getContrast(popup.background);
      popup.link = popup.link ? popup.link : popup.text;
      colorStyles[prefix + '.cc-window'] = [
        'color: ' + popup.text,
        'background-color: ' + popup.background
      ];
      colorStyles[prefix + '.cc-revoke'] = [
        'color: ' + popup.text,
        'background-color: ' + popup.background
      ];
      colorStyles[prefix + ' .cc-link,' + prefix + ' .cc-link:active,' + prefix + ' .cc-link:visited'] = [
        'color: ' + popup.link
      ];

      if (button) {
        // assumes button.background is set
        button.text = button.text ? button.text : util.getContrast(button.background);
        button.border = button.border ? button.border : 'transparent';
        colorStyles[prefix + ' .cc-btn'] = [
          'color: ' + button.text,
          'border-color: ' + button.border,
          'background-color: ' + button.background
        ];

        if(button.background != 'transparent') 
          colorStyles[prefix + ' .cc-btn:hover, ' + prefix + ' .cc-btn:focus'] = [
            'background-color: ' + getHoverColour(button.background)
          ];

        if (highlight) {
          //assumes highlight.background is set
          highlight.text = highlight.text ? highlight.text : util.getContrast(highlight.background);
          highlight.border = highlight.border ? highlight.border : 'transparent';
          colorStyles[prefix + ' .cc-highlight .cc-btn:first-child'] = [
            'color: ' + highlight.text,
            'border-color: ' + highlight.border,
            'background-color: ' + highlight.background
          ];
        } else {
          // sets highlight text color to popup text. background and border are transparent by default.
          colorStyles[prefix + ' .cc-highlight .cc-btn:first-child'] = [
            'color: ' + popup.text
          ];
        }
      }

    }

    // this will be interpretted as CSS. the key is the selector, and each array element is a rule
    var style = document.createElement('style');
    document.head.appendChild(style);

    // custom style doesn't exist, so we create it
    cc.customStyles[hash] = {
      references: 1,
      element: style.sheet
    };

    var ruleIndex = -1;
    for (var prop in colorStyles) {
      if (colorStyles.hasOwnProperty(prop)) {
        style.sheet.insertRule(prop + '{' + colorStyles[prop].join(';') + '}', ++ruleIndex);
      }
    }
  }

  function getHoverColour(hex) {
    hex = util.normaliseHex(hex);
    // for black buttons
    if (hex == '000000') {
      return '#222';
    }
    return util.getLuminance(hex);
  }

  function removeCustomStyle(palette) {
    if (util.isPlainObject(palette)) {
      var hash = util.hash(JSON.stringify(palette));
      var customStyle = cc.customStyles[hash];
      if (customStyle && !--customStyle.references) {
        var styleNode = customStyle.element.ownerNode;
        if (styleNode && styleNode.parentNode) {
          styleNode.parentNode.removeChild(styleNode);
        }
        cc.customStyles[hash] = null;
      }
    }
  }

  function arrayContainsMatches(array, search) {
    for (var i = 0, l = array.length; i < l; ++i) {
      var str = array[i];
      // if regex matches or string is equal, return true
      if ((str instanceof RegExp && str.test(search)) ||
        (typeof str == 'string' && str.length && str === search)) {
        return true;
      }
    }
    return false;
  }

  function applyAutoDismiss() {
    var setStatus = this.setStatus.bind(this);

    var delay = this.options.dismissOnTimeout;
    if (typeof delay == 'number' && delay >= 0) {
      this.dismissTimeout = window.setTimeout(function() {
        setStatus(cc.status.dismiss);
      }, Math.floor(delay));
    }

    var scrollRange = this.options.dismissOnScroll;
    if (typeof scrollRange == 'number' && scrollRange >= 0) {
      var onWindowScroll = function(evt) {
        if (window.pageYOffset > Math.floor(scrollRange)) {
          setStatus(cc.status.dismiss);

          window.removeEventListener('scroll', onWindowScroll);
          this.onWindowScroll = null;
        }
      };

      this.onWindowScroll = onWindowScroll;
      window.addEventListener('scroll', onWindowScroll);
    }
  }

  function applyRevokeButton() {
    // revokable is true if advanced compliance is selected
    if (this.options.type != 'info') this.options.revokable = true;
    // animateRevokable false for mobile devices
    if (util.isMobile()) this.options.animateRevokable = false;

    if (this.options.revokable) {
      var classes = getPositionClasses.call(this);
      if (this.options.animateRevokable) {
        classes.push('cc-animate');
      }
      if (this.customStyleSelector) {
        classes.push(this.customStyleSelector)
      }
      var revokeBtn = this.options.revokeBtn.replace('{{classes}}', classes.join(' '));
      this.revokeBtn = appendMarkup.call(this, revokeBtn);

      var btn = this.revokeBtn;
      if (this.options.animateRevokable) {
        var wait = false;
        var onMouseMove = util.throttle(function(evt) {
          var active = false;
          var minY = 20;
          var maxY = (window.innerHeight - 20);

          if (util.hasClass(btn, 'cc-top') && evt.clientY < minY) active = true;
          if (util.hasClass(btn, 'cc-bottom') && evt.clientY > maxY) active = true;

          if (active) {
            if (!util.hasClass(btn, 'cc-active')) {
              util.addClass(btn, 'cc-active');
            }
          } else {
            if (util.hasClass(btn, 'cc-active')) {
              util.removeClass(btn, 'cc-active');
            }
          }
        }, 200);

        this.onMouseMove = onMouseMove;
        window.addEventListener('mousemove', onMouseMove);
      }
    }
  }

  return CookiePopup
}());

cc.Location = (function() {

  // An object containing all the location services we have already set up.
  // When using a service, it could either return a data structure in plain text (like a JSON object) or an executable script
  // When the response needs to be executed by the browser, then `isScript` must be set to true, otherwise it won't work.

  // When the service uses a script, the chances are that you'll have to use the script to make additional requests. In these
  // cases, the services `callback` property is called with a `done` function. When performing async operations, this must be called
  // with the data (or Error), and `cookieconsent.locate` will take care of the rest
  var defaultOptions = {

    // The default timeout is 5 seconds. This is mainly needed to catch JSONP requests that error.
    // Otherwise there is no easy way to catch JSONP errors. That means that if a JSONP fails, the
    // app will take `timeout` milliseconds to react to a JSONP network error.
    timeout: 5000,

    // the order that services will be attempted in
    services: [
      'freegeoip',
      'ipinfo',
      'maxmind'

      /*

      // 'ipinfodb' requires some options, so we define it using an object
      // this object will be passed to the function that defines the service

      {
        name: 'ipinfodb',
        interpolateUrl: {
          // obviously, this is a fake key
          api_key: 'vOgI3748dnIytIrsJcxS7qsDf6kbJkE9lN4yEDrXAqXcKUNvjjZPox3ekXqmMMld'
        },
      },

      // as well as defining an object, you can define a function that returns an object

      function () {
        return {name: 'ipinfodb'};
      },

      */
    ],

    serviceDefinitions: {

      freegeoip: function() {
        return {
          // This service responds with JSON, but they do not have CORS set, so we must use JSONP and provide a callback
          // The `{callback}` is automatically rewritten by the tool
          url: '//freegeoip.net/json/?callback={callback}',
          isScript: true, // this is JSONP, therefore we must set it to run as a script
          callback: function(done, response) {
            try{
              var json = JSON.parse(response);
              return json.error ? toError(json) : {
                code: json.country_code
              };
            } catch (err) {
              return toError({error: 'Invalid response ('+err+')'});
            }
          }
        }
      },

      ipinfo: function() {
        return {
          // This service responds with JSON, so we simply need to parse it and return the country code
          url: '//ipinfo.io',
          headers: ['Accept: application/json'],
          callback: function(done, response) {
            try{
              var json = JSON.parse(response);
              return json.error ? toError(json) : {
                code: json.country
              };
            } catch (err) {
              return toError({error: 'Invalid response ('+err+')'});
            }
          }
        }
      },

      // This service requires an option to define `key`. Options are proived using objects or functions
      ipinfodb: function(options) {
        return {
          // This service responds with JSON, so we simply need to parse it and return the country code
          url: '//api.ipinfodb.com/v3/ip-country/?key={api_key}&format=json&callback={callback}',
          isScript: true, // this is JSONP, therefore we must set it to run as a script
          callback: function(done, response) {
            try{
              var json = JSON.parse(response);
              return json.statusCode == 'ERROR' ? toError({error: json.statusMessage}) : {
                code: json.countryCode
              };
            } catch (err) {
              return toError({error: 'Invalid response ('+err+')'});
            }
          }
        }
      },

      maxmind: function() {
        return {
          // This service responds with a JavaScript file which defines additional functionality. Once loaded, we must
          // make an additional AJAX call. Therefore we provide a `done` callback that can be called asynchronously
          url: '//js.maxmind.com/js/apis/geoip2/v2.1/geoip2.js',
          isScript: true, // this service responds with a JavaScript file, so it must be run as a script
          callback: function(done) {
            // if everything went okay then `geoip2` WILL be defined
            if (!window.geoip2) {
              done(new Error('Unexpected response format. The downloaded script should have exported `geoip2` to the global scope'));
              return;
            }

            geoip2.country(function(location) {
              try {
                done({
                  code: location.country.iso_code
                });
              } catch (err) {
                done(toError(err));
              }
            }, function(err) {
              done(toError(err));
            });

            // We can't return anything, because we need to wait for the second AJAX call to return.
            // Then we can 'complete' the service by passing data or an error to the `done` callback.
          }
        }
      },
    },
  };

  function Location(options) {
    // Set up options
    util.deepExtend(this.options = {}, defaultOptions);

    if (util.isPlainObject(options)) {
      util.deepExtend(this.options, options);
    }

    this.currentServiceIndex = -1; // the index (in options) of the service we're currently using
  }

  Location.prototype.getNextService = function() {
    var service;

    do {
      service = this.getServiceByIdx(++this.currentServiceIndex);
    } while (this.currentServiceIndex < this.options.services.length && !service);

    return service;
  };

  Location.prototype.getServiceByIdx = function(idx) {
    // This can either be the name of a default locationService, or a function.
    var serviceOption = this.options.services[idx];

    // If it's a string, use one of the location services.
    if (typeof serviceOption === 'function') {
      var dynamicOpts = serviceOption();
      if (dynamicOpts.name) {
        util.deepExtend(dynamicOpts, this.options.serviceDefinitions[dynamicOpts.name](dynamicOpts));
      }
      return dynamicOpts;
    }

    // If it's a string, use one of the location services.
    if (typeof serviceOption === 'string') {
      return this.options.serviceDefinitions[serviceOption]();
    }

    // If it's an object, assume {name: 'ipinfo', ...otherOptions}
    // Allows user to pass in API keys etc.
    if (util.isPlainObject(serviceOption)) {
      return this.options.serviceDefinitions[serviceOption.name](serviceOption);
    }

    return null;
  };

  // This runs the service located at index `currentServiceIndex`.
  // If the service fails, `runNextServiceOnError` will continue trying each service until all fail, or one completes successfully
  Location.prototype.locate = function(complete, error) {
    var service = this.getNextService();

    if (!service) {
      error(new Error('No services to run'));
      return;
    }

    this.callbackComplete = complete;
    this.callbackError = error;

    this.runService(service, this.runNextServiceOnError.bind(this));
  };

  // Potentially adds a callback to a url for jsonp.
  Location.prototype.setupUrl = function(service) {
    var serviceOpts = this.getCurrentServiceOpts();
    return service.url.replace(/\{(.*?)\}/g, function(_, param) {
      if (param === 'callback') {
        var tempName = 'callback' + Date.now();
        window[tempName] = function(res) {
          service.__JSONP_DATA = JSON.stringify(res);
        }
        return tempName;
      }
      if (param in serviceOpts.interpolateUrl) {
        return serviceOpts.interpolateUrl[param];
      }
    });
  };

  // requires a `service` object that defines at least a `url` and `callback`
  Location.prototype.runService = function(service, complete) {
    var self = this;

    // basic check to ensure it resembles a `service`
    if (!service || !service.url || !service.callback) {
      return;
    }

    // we call either `getScript` or `makeAsyncRequest` depending on the type of resource
    var requestFunction = service.isScript ? getScript : makeAsyncRequest;

    var url = this.setupUrl(service);

    // both functions have similar signatures so we can pass the same arguments to both
    requestFunction(url, function(xhr) {
      // if `!xhr`, then `getScript` function was used, so there is no response text
      var responseText = xhr ? xhr.responseText : '';

      // if the resource is a script, then this function is called after the script has been run.
      // if the script is JSONP, then a time defined function `callback_{Date.now}` has already
      // been called (as the JSONP callback). This callback sets the __JSONP_DATA property
      if (service.__JSONP_DATA) {
        responseText = service.__JSONP_DATA;
        delete service.__JSONP_DATA;
      }

      // call the service callback with the response text (so it can parse the response)
      self.runServiceCallback.call(self, complete, service, responseText);

    }, this.options.timeout, service.data, service.headers);

    // `service.data` and `service.headers` are optional (they only count if `!service.isScript` anyway)
  };

  // The service request has run (and possibly has a `responseText`) [no `responseText` if `isScript`]
  // We need to run its callback which determines if its successful or not
  // `complete` is called on success or failure
  Location.prototype.runServiceCallback = function(complete, service, responseText) {
    var self = this;
    // this is the function that is called if the service uses the async callback in its handler method
    var serviceResultHandler = function (asyncResult) {
      // if `result` is a valid value, then this function shouldn't really run
      // even if it is called by `service.callback`
      if (!result) {
        self.onServiceResult.call(self, complete, asyncResult)
      }
    };

    // the function `service.callback` will either extract a country code from `responseText` and return it (in `result`)
    // or (if it has to make additional requests) it will call a `done` callback with the country code when it is ready
    var result = service.callback(serviceResultHandler, responseText);

    if (result) {
      this.onServiceResult.call(this, complete, result);
    }
  };

  // This is called with the `result` from `service.callback` regardless of how it provided that result (sync or async).
  // `result` will be whatever is returned from `service.callback`. A service callback should provide an object with data
  Location.prototype.onServiceResult = function(complete, result) {
    // convert result to nodejs style async callback
    if (result instanceof Error || (result && result.error)) {
      complete.call(this, result, null);
    } else {
      complete.call(this, null, result);
    }
  };

  // if `err` is set, the next service handler is called
  // if `err` is null, the `onComplete` handler is called with `data`
  Location.prototype.runNextServiceOnError = function(err, data) {
    if (err) {
      this.logError(err);

      var nextService = this.getNextService();

      if (nextService) {
        this.runService(nextService, this.runNextServiceOnError.bind(this));
      } else {
        this.completeService.call(this, this.callbackError, new Error('All services failed'));
      }
    } else {
      this.completeService.call(this, this.callbackComplete, data);
    }
  };

  Location.prototype.getCurrentServiceOpts = function() {
    var val = this.options.services[this.currentServiceIndex];

    if (typeof val == 'string') {
      return {name: val};
    }

    if (typeof val == 'function') {
      return val();
    }

    if (util.isPlainObject(val)) {
      return val;
    }

    return {};
  };

  // calls the `onComplete` callback after resetting the `currentServiceIndex`
  Location.prototype.completeService = function(fn, data) {
    this.currentServiceIndex = -1;

    fn && fn(data);
  };

  Location.prototype.logError = function (err) {
    var idx = this.currentServiceIndex;
    var service = this.getServiceByIdx(idx);

    console.error('The service[' + idx + '] (' + service.url + ') responded with the following error', err);
  };

  function getScript(url, callback, timeout) {
    var timeoutIdx, s = document.createElement('script');

    s.type = 'text/' + (url.type || 'javascript');
    s.src = url.src || url;
    s.async = false;

    s.onreadystatechange = s.onload = function() {
      // this code handles two scenarios, whether called by onload or onreadystatechange
      var state = s.readyState;

      clearTimeout(timeoutIdx);

      if (!callback.done && (!state || /loaded|complete/.test(state))) {
        callback.done = true;
        callback();
        s.onreadystatechange = s.onload = null;
      }
    };

    document.body.appendChild(s);

    // You can't catch JSONP Errors, because it's handled by the script tag
    // one way is to use a timeout
    timeoutIdx = setTimeout(function () {
      callback.done = true;
      callback();
      s.onreadystatechange = s.onload = null;
    }, timeout);
  }

  function makeAsyncRequest(url, onComplete, timeout, postData, requestHeaders) {
    var xhr = new(window.XMLHttpRequest || window.ActiveXObject)('MSXML2.XMLHTTP.3.0');

    xhr.open(postData ? 'POST' : 'GET', url, 1);

    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

    if (Array.isArray(requestHeaders)) {
      for (var i = 0, l = requestHeaders.length; i < l; ++i) {
        var split = requestHeaders[i].split(':', 2)
        xhr.setRequestHeader(split[0].replace(/^\s+|\s+$/g, ''), split[1].replace(/^\s+|\s+$/g, ''));
      }
    }

    if (typeof onComplete == 'function') {
      xhr.onreadystatechange = function() {
        if (xhr.readyState > 3) {
          onComplete(xhr);
        }
      };
    }

    xhr.send(postData);
  }

  function toError(obj) {
    return new Error('Error [' + (obj.code || 'UNKNOWN') + ']: ' + obj.error);
  }

  return Location;
}());

cc.Law = (function() {

  var defaultOptions = {
    // Make this false if you want to disable all regional overrides for settings.
    // If true, options can differ by country, depending on their cookie law.
    // It does not affect hiding the popup for countries that do not have cookie law.
    regionalLaw: true,

    // countries that enforce some version of a cookie law
    hasLaw: ['AT', 'BE', 'BG', 'HR', 'CZ', 'CY', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'SK', 'SI', 'ES', 'SE', 'GB', 'UK'],

    // countries that say that all cookie consent choices must be revokable (a user must be able too change their mind)
    revokable: ['HR', 'CY', 'DK', 'EE', 'FR', 'DE', 'LV', 'LT', 'NL', 'PT', 'ES'],

    // countries that say that a person can only "consent" if the explicitly click on "I agree".
    // in these countries, consent cannot be implied via a timeout or by scrolling down the page
    explicitAction: ['HR', 'IT', 'ES'],
  };

  function Law(options) {
    this.initialise.apply(this, arguments);
  }

  Law.prototype.initialise = function(options) {
    // set options back to default options
    util.deepExtend(this.options = {}, defaultOptions);

    // merge in user options
    if (util.isPlainObject(options)) {
      util.deepExtend(this.options, options);
    }
  };

  Law.prototype.get = function(countryCode) {
    var opts = this.options;
    return {
      hasLaw: opts.hasLaw.indexOf(countryCode) >= 0,
      revokable: opts.revokable.indexOf(countryCode) >= 0,
      explicitAction: opts.explicitAction.indexOf(countryCode) >= 0,
    };
  };

  Law.prototype.applyLaw = function(options, countryCode) {
    var country = this.get(countryCode);

    if (!country.hasLaw) {
      // The country has no cookie law
      options.enabled = false;
    }

    if (this.options.regionalLaw) {
      if (country.revokable) {
        // We must provide an option to revoke consent at a later time
        options.revokable = true;
      }

      if (country.explicitAction) {
        // The user must explicitly click the consent button
        options.dismissOnScroll = false;
        options.dismissOnTimeout = false;
      }
    }
    return options;
  };

  return Law;
}());

// This function initialises the app by combining the use of the Popup, Locator and Law modules
// You can string together these three modules yourself however you want, by writing a new function.
cc.initialise = function(options, complete, error) {
  var law = new cc.Law(options.law);

  if (!complete) complete = function() {};
  if (!error) error = function() {};

  cc.getCountryCode(options, function(result) {
    // don't need the law or location options anymore
    delete options.law;
    delete options.location;

    if (result.code) {
      options = law.applyLaw(options, result.code);
    }

    complete(new cc.Popup(options));
  }, function(err) {
    // don't need the law or location options anymore
    delete options.law;
    delete options.location;

    error(err, new cc.Popup(options));
  });
};

// This function tries to find your current location. It either grabs it from a hardcoded option in
// `options.law.countryCode`, or attempts to make a location service request. This function accepts
// options (which can configure the `law` and `location` modules) and fires a callback with which
// passes an object `{code: countryCode}` as the first argument (which can have undefined properties)
cc.getCountryCode = function(options, complete, error) {
  if (options.law && options.law.countryCode) {
    complete({
      code: options.law.countryCode
    });
    return;
  }
  if (options.location) {
    var locator = new cc.Location(options.location);
    locator.locate(function(serviceResult) {
      complete(serviceResult || {});
    }, error);
    return;
  }
  complete({});
};

// export utils (no point in hiding them, so we may as well expose them)
cc.utils = util;

// prevent this code from being run twice
cc.hasInitialised = true;

window.cookieconsent = cc;

}(window.cookieconsent || {}));

window.addEventListener(“load”, function(){ window.cookieconsent.initialise({

"palette": {
},
"theme": "classic",
"position": "bottom-right"

})});