<|start_filename|>playground/simplegoals.js<|end_filename|>
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("SimpleGoals", [], factory);
	else if(typeof exports === 'object')
		exports["SimpleGoals"] = factory();
	else
		root["SimpleGoals"] = factory();
})(window, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./src/index.css":
/*!***********************!*\
  !*** ./src/index.css ***!
  \***********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

// extracted by mini-css-extract-plugin

/***/ }),

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/*! exports provided: init, unlock, showOverview, default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "init", function() { return init; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unlock", function() { return unlock; });
/* harmony import */ var _modules_local_goals__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./modules/local-goals */ "./src/modules/local-goals.js");
/* harmony import */ var _modules_cloud_storage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./modules/cloud-storage */ "./src/modules/cloud-storage.js");
/* harmony import */ var _modules_overview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules/overview */ "./src/modules/overview.js");
/* harmony import */ var _modules_achievements__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./modules/achievements */ "./src/modules/achievements.js");
/* harmony import */ var _modules_buttons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./modules/buttons */ "./src/modules/buttons.js");
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "showOverview", function() { return _modules_overview__WEBPACK_IMPORTED_MODULE_2__["showOverview"]; });

__webpack_require__ (/*! ./index.css */ "./src/index.css")







let options = {
  timeout: 0,
  onGoalUnlock: (goalKey) => {},
  freshStart: false,
  useCloudStorage: false,
  appId: null,
  user: {}
}
let goals = null

const init = async config => {
  Object.assign(options, config)
  goals = Object(_modules_local_goals__WEBPACK_IMPORTED_MODULE_0__["prepareGoals"])(options)
  await Object(_modules_cloud_storage__WEBPACK_IMPORTED_MODULE_1__["loadFromCloud"])(goals, options)
  Object(_modules_overview__WEBPACK_IMPORTED_MODULE_2__["initOverview"])(goals, options)
  Object(_modules_buttons__WEBPACK_IMPORTED_MODULE_4__["initButtons"])(goals, options)
}

const unlock = async (name) => {
  Object(_modules_achievements__WEBPACK_IMPORTED_MODULE_3__["unlockGoal"])(name, goals, options)
}



/* harmony default export */ __webpack_exports__["default"] = ({init, unlock, showOverview: _modules_overview__WEBPACK_IMPORTED_MODULE_2__["showOverview"]});


/***/ }),

/***/ "./src/modules/achievements.js":
/*!*************************************!*\
  !*** ./src/modules/achievements.js ***!
  \*************************************/
/*! exports provided: unlockGoal */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unlockGoal", function() { return unlockGoal; });
/* harmony import */ var _support__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./support */ "./src/modules/support.js");
/* harmony import */ var _local_goals__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./local-goals */ "./src/modules/local-goals.js");
/* harmony import */ var _cloud_storage__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./cloud-storage */ "./src/modules/cloud-storage.js");
/* harmony import */ var _overview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./overview */ "./src/modules/overview.js");
/* harmony import */ var _styles__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./styles */ "./src/modules/styles.js");






const createAchievementHtml = (key, goal, button, options) => {
  const achievementHtmlString = `
  <div class="simplegoals-achievement" id="simplegoals-achievement-${key}">
    <div class="simplegoals-achievement__icon">
      <img src="https://static.simplegoals.co/cup.svg" />
    </div>
    <div class="simplegoals-achievement__body">
      <p class="simplegoals-achievement__notification">
        New achievement unlocked
      </p>
      <p class="simplegoals-achievement__name" id="simplegoals-achievement__name">${goal.name}</p>
    </div>
    <div class="simplegoals-achievement__button" id="simplegoals-achievement__button-${key}" role="button">${button}</div>
    <button class="simplegoals-achievement__close-button" id="simplegoals-achievement__close-button-${key}">
      <svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
        <g>
          <path d="M50 50L450 450" stroke-width="100" stroke-linecap="round"/>
          <path d="M50 450L450 50" stroke-width="100" stroke-linecap="round"/>
        </g>
      </svg>
    </button>
  </div>
  `
  const node = new DOMParser().parseFromString(achievementHtmlString , 'text/html').body.firstChild
  Object(_styles__WEBPACK_IMPORTED_MODULE_4__["applyStyles"])(node, options)
  document.body.appendChild(node)
  return node
}

const recalculateAchievementsTop = () => {
  const nodes = document.getElementsByClassName('simplegoals-achievement')
  for(let i=0; i < nodes.length; i++) {
    nodes[i].style.top = `${1 + i*7}em`
  }
}

const showAchievement = (name, goal, options) => {
  const key = Object(_support__WEBPACK_IMPORTED_MODULE_0__["hashCode"])(name)
  const achievement = createAchievementHtml(key, goal, 'Learn more', options)
  recalculateAchievementsTop()
  achievement.classList.add('simplegoals-achievement--opened')
  initAchievement(key, achievement)
  if (options.timeout) {
    achievementTimeout = setTimeout(() => hideAchievement(achievement), options.timeout)
  }
}

const initAchievement = (key, achievement) => {
  achievement.addEventListener('click', event => achievement.classList.toggle('simplegoals-achievement--clicked'))
  const trigger = document.getElementById(`simplegoals-achievement__close-button-${key}`)
  trigger.addEventListener('click', event => hideAchievement(achievement))
  const overviewTrigger = document.getElementById(`simplegoals-achievement__button-${key}`)
  overviewTrigger.addEventListener('click', event => {
    hideAchievement(achievement)
    setTimeout(_overview__WEBPACK_IMPORTED_MODULE_3__["showOverview"], 200)
  })
}

const hideAchievement = (achievement) => {
  achievement.classList.remove('simplegoals-achievement--opened')
  achievement.classList.add('simplegoals-achievement--closed')
  setTimeout(() => {
    achievement.parentNode.removeChild(achievement)
    recalculateAchievementsTop()
  }, 2000)
}

const unlockGoal = async (name, goals, options) => {
  if(!goals || !goals[name]) { return }
  if(goals[name].unlocked){
    return
  }
  goals[name].unlocked = true
  options.onGoalUnlock(name)
  Object(_local_goals__WEBPACK_IMPORTED_MODULE_1__["saveLocalGoal"])(name, options)
  await Object(_cloud_storage__WEBPACK_IMPORTED_MODULE_2__["saveCloudGoal"])(name, options)
  showAchievement(name, goals[name], options)
  Object(_overview__WEBPACK_IMPORTED_MODULE_3__["rerenderOverviewGoals"])(goals)
}

/***/ }),

/***/ "./src/modules/buttons.js":
/*!********************************!*\
  !*** ./src/modules/buttons.js ***!
  \********************************/
/*! exports provided: initButtons */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "initButtons", function() { return initButtons; });
/* harmony import */ var _overview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./overview */ "./src/modules/overview.js");
/* harmony import */ var _achievements__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./achievements */ "./src/modules/achievements.js");



const initButtons = (goals, options) => {
  document.addEventListener('click', function (event) {
    if (event.target.dataset.simplegoalsUnlock) {
      event.preventDefault()
      Object(_achievements__WEBPACK_IMPORTED_MODULE_1__["unlockGoal"])(event.target.dataset.simplegoalsUnlock, goals, options)
    }
    if (event.target.dataset.simplegoalsOverview) {
      event.preventDefault()
      Object(_overview__WEBPACK_IMPORTED_MODULE_0__["showOverview"])();
    }
  }, false);
}

/***/ }),

/***/ "./src/modules/cloud-storage.js":
/*!**************************************!*\
  !*** ./src/modules/cloud-storage.js ***!
  \**************************************/
/*! exports provided: saveCloudGoal, loadFromCloud */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "saveCloudGoal", function() { return saveCloudGoal; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "loadFromCloud", function() { return loadFromCloud; });
/* harmony import */ var _support__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./support */ "./src/modules/support.js");


const saveCloudGoal = async (name, options) => {
  if (options.useCloudStorage) {
    let data = {
      name: name
    }
    addAuthData(data, options)
    await Object(_support__WEBPACK_IMPORTED_MODULE_0__["postRequest"])("/goals/unlock", data)
  } else {
    return false
  }
}

const addAuthData = (hash, options) => {
  hash.appId = options.appId
  hash.user = options.user
}

const loadFromCloud = async (goals, options) => {
  if (options.useCloudStorage) {
    let data = {
      goals: Object.keys(goals).map((key) => {
        return {
          name: key,
          description: goals[key].description
        }
      }),
      unlockedGoals: Object.keys(goals).filter((key) => {
        goals[key].unlocked
      })
    }
    addAuthData(data, options)
    const response = await Object(_support__WEBPACK_IMPORTED_MODULE_0__["postRequest"])("/sessions", data)
    if (response) {
      updateGoalsFromCloud(response, goals)
    }
  } else {
    return false
  }
}

const updateGoalsFromCloud = (response, goals) => {
  for (const key of Object.keys(goals)) {
    goals[key].unlocked = response.unlockedGoals.includes(key)
  }
}

/***/ }),

/***/ "./src/modules/local-goals.js":
/*!************************************!*\
  !*** ./src/modules/local-goals.js ***!
  \************************************/
/*! exports provided: saveLocalGoal, prepareGoals */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "saveLocalGoal", function() { return saveLocalGoal; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "prepareGoals", function() { return prepareGoals; });
const saveLocalGoal = (name, options) => {
  if (typeof(Storage) !== "undefined" && !options.freshStart) {
    let localGoals = getLocalGoals(options)
    localGoals.push(name)
    localStorage.setItem('simplegoals-storage', JSON.stringify(localGoals))
  }
}

const getLocalGoals = (options) => {
  if (typeof(Storage) !== "undefined" && !options.freshStart) {
    return JSON.parse(localStorage.getItem('simplegoals-storage')) || []
  } else {
    return []
  }
}

const prepareGoals = (options) => {
  let goals = options.goals
  const localGoals = getLocalGoals(options)
  for (const key of Object.keys(goals)) {
    goals[key].unlocked = localGoals.includes(key)
  }
  return goals
}

/***/ }),

/***/ "./src/modules/overview.js":
/*!*********************************!*\
  !*** ./src/modules/overview.js ***!
  \*********************************/
/*! exports provided: initOverview, rerenderOverviewGoals, showOverview */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "initOverview", function() { return initOverview; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "rerenderOverviewGoals", function() { return rerenderOverviewGoals; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "showOverview", function() { return showOverview; });
/* harmony import */ var _styles__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./styles */ "./src/modules/styles.js");


let overview = null

const initOverview = (goals, options) => {
  createOverviewHtml(goals, options)
  overview = document.getElementById('simplegoals-overview')
  const trigger = document.getElementById('simplegoals-overview__close-button')
  trigger.addEventListener('click', event => hideOverview())
}

const createOverviewHtml = (goals, options) => {
  const overviewHtmlString = `
  <div class="simplegoals-overview" id="simplegoals-overview">
    <div class="simplegoals-overview__wrapper">
      <div class="simplegoals-overview__body">
        <h1 class="simplegoals-overview__title">Achievements</h1>
        <p class="simplegoals-overview__subtitle">Complete tasks and unlock achievements</p>
        <div class="simplegoals-overview__goals" id="simplegoals-overview__goals">
        ` + overviewGoalsHtml(goals) + `
        </div>
        <button class="simplegoals-overview__close-button" id="simplegoals-overview__close-button">
          Close
        </button>
        <div class="simplegoals-overview__attribution">
          Powered by <a class="simplegoals-overview__attribution-link" href="https://simplegoals.co" target="_blank">SimpleGoals</a>
        </div>
      </div>
    </div>
  </div>
  `
  const node = new DOMParser().parseFromString(overviewHtmlString , 'text/html').body.firstChild
  Object(_styles__WEBPACK_IMPORTED_MODULE_0__["applyStyles"])(node, options)
  document.body.appendChild(node)
}

const overviewGoalsHtml = (goals) => {
  let result = "";
  const sortedKeys = Object.keys(goals).sort((a, b) => goals[b].unlocked - goals[a].unlocked)
  for (const key of sortedKeys) {
    result += `
    <div class="simplegoals-overview-goal ${goals[key].unlocked ? "" : "simplegoals-overview-goal--locked"}">
      <div class="simplegoals-overview-goal__icon">
        <img src="https://static.simplegoals.co/cup.svg" />
      </div>
      <div class="simplegoals-overview-goal__body">
        <h3 class="simplegoals-overview-goal__name">${goals[key].name}</h3>
        <p class="simplegoals-overview-goal__description">
          ${goals[key].description}
        </p>
      </div>
    </div>
    `
  }
  return result
}

const rerenderOverviewGoals = (goals) => {
  document.getElementById('simplegoals-overview__goals').innerHTML = overviewGoalsHtml(goals)
}

const showOverview = () => {
  if(!overview) { return }
  overview.scrollTo(0, 0)
  overview.classList.add('simplegoals-overview--opened')
}

const hideOverview = () => {
  overview.classList.remove('simplegoals-overview--opened')
}

/***/ }),

/***/ "./src/modules/styles.js":
/*!*******************************!*\
  !*** ./src/modules/styles.js ***!
  \*******************************/
/*! exports provided: applyStyles */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "applyStyles", function() { return applyStyles; });
const defaultStyleOptions = {
  fontFamily: 'BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif',
  background: '#ffffff',
  title: '#000000',
  subtitle: '#718096',
  primary: '#38b2ac',
  primaryHover: '#319795',
  opposite: '#ffffff'
}

const getStyles = (options = {}) => {
  let styles = defaultStyleOptions;
  ['fontFamily', 'background', 'title', 'subtitle'].forEach(key => {
    if (options[key]) {
      styles[key] = options[key]
    }
  })
  if (options.primary) {
    styles.primary = options.primary
    styles.primaryHover = getHover(options.primary)
    styles.opposite = getOpposite(options.primary)
  }
  ['primaryHover', 'opposite'].forEach(key => {
    if (options[key]) {
      styles[key] = options[key]
    }
  })
  return styles
}

const getHover = (color) => {
  const percent = 7

  let R = parseInt(color.substring(1,3),16);
  let G = parseInt(color.substring(3,5),16);
  let B = parseInt(color.substring(5,7),16);

  R = parseInt(R * (100 - percent) / 100);
  G = parseInt(G * (100 - percent) / 100);
  B = parseInt(B * (100 - percent) / 100);

  R = (R<255)?R:255;  
  G = (G<255)?G:255;  
  B = (B<255)?B:255;  

  const RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
  const GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
  const BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));

  return "#"+RR+GG+BB;
}

const getOpposite = (hex) => {
  if (hex.indexOf('#') === 0) {
    hex = hex.slice(1);
  }
  // convert 3-digit hex to 6-digits.
  if (hex.length === 3) {
      hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
  }
  if (hex.length !== 6) {
      throw new Error('Invalid HEX color.');
  }
  var r = parseInt(hex.slice(0, 2), 16),
      g = parseInt(hex.slice(2, 4), 16),
      b = parseInt(hex.slice(4, 6), 16);
  // http://stackoverflow.com/a/3943023/112731
  return (r * 0.299 + g * 0.587 + b * 0.114) > 186
      ? '#000000'
      : '#FFFFFF';
}

const applyStyles = (element, options) => {
  let styles = getStyles(options.theme)
  element.style.setProperty('--simplegoals-font-family', styles.fontFamily);
  element.style.setProperty('--simplegoals-color-background', styles.background);
  element.style.setProperty('--simplegoals-color-title', styles.title);
  element.style.setProperty('--simplegoals-color-subtitle', styles.subtitle);
  element.style.setProperty('--simplegoals-color-primary', styles.primary);
  element.style.setProperty('--simplegoals-color-primary-hover', styles.primaryHover);
  element.style.setProperty('--simplegoals-color-opposite', styles.opposite);
}

/***/ }),

/***/ "./src/modules/support.js":
/*!********************************!*\
  !*** ./src/modules/support.js ***!
  \********************************/
/*! exports provided: postRequest, hashCode */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "postRequest", function() { return postRequest; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hashCode", function() { return hashCode; });
const apiUrl = "https://api.simplegoals.co"

const postRequest = async (endpoint, data) => {
  const response = await fetch(`${apiUrl}${endpoint}`, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: {
      'Content-Type': 'application/json'
    }
  })
  if(response.ok) {
    return await response.json()
  } else {
    return false
  }
}

const hashCode = (source) => {
  let hash = 0;
  if (source.length == 0) { return hash; }
  for (let i = 0; i < source.length; i++) {
    let char = source.charCodeAt(i);
    hash = ((hash<<5)-hash)+char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return hash;
}

/***/ })

/******/ });
});
//# sourceMappingURL=simplegoals.js.map

<|start_filename|>src/modules/styles.js<|end_filename|>
const defaultStyleOptions = {
  fontFamily: 'BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif',
  background: '#ffffff',
  title: '#000000',
  subtitle: '#718096',
  primary: '#38b2ac',
  primaryHover: '#319795',
  opposite: '#ffffff'
}

const getStyles = (options = {}) => {
  let styles = defaultStyleOptions;
  ['fontFamily', 'background', 'title', 'subtitle'].forEach(key => {
    if (options[key]) {
      styles[key] = options[key]
    }
  })
  if (options.primary) {
    styles.primary = options.primary
    styles.primaryHover = getHover(options.primary)
    styles.opposite = getOpposite(options.primary)
  }
  ['primaryHover', 'opposite'].forEach(key => {
    if (options[key]) {
      styles[key] = options[key]
    }
  })
  return styles
}

const getHover = (color) => {
  const percent = 7

  let R = parseInt(color.substring(1,3),16);
  let G = parseInt(color.substring(3,5),16);
  let B = parseInt(color.substring(5,7),16);

  R = parseInt(R * (100 - percent) / 100);
  G = parseInt(G * (100 - percent) / 100);
  B = parseInt(B * (100 - percent) / 100);

  R = (R<255)?R:255;  
  G = (G<255)?G:255;  
  B = (B<255)?B:255;  

  const RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
  const GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
  const BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));

  return "#"+RR+GG+BB;
}

const getOpposite = (hex) => {
  if (hex.indexOf('#') === 0) {
    hex = hex.slice(1);
  }
  // convert 3-digit hex to 6-digits.
  if (hex.length === 3) {
      hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
  }
  if (hex.length !== 6) {
      throw new Error('Invalid HEX color.');
  }
  var r = parseInt(hex.slice(0, 2), 16),
      g = parseInt(hex.slice(2, 4), 16),
      b = parseInt(hex.slice(4, 6), 16);
  // http://stackoverflow.com/a/3943023/112731
  return (r * 0.299 + g * 0.587 + b * 0.114) > 186
      ? '#000000'
      : '#FFFFFF';
}

export const applyStyles = (element, options) => {
  let styles = getStyles(options.theme)
  element.style.setProperty('--simplegoals-font-family', styles.fontFamily);
  element.style.setProperty('--simplegoals-color-background', styles.background);
  element.style.setProperty('--simplegoals-color-title', styles.title);
  element.style.setProperty('--simplegoals-color-subtitle', styles.subtitle);
  element.style.setProperty('--simplegoals-color-primary', styles.primary);
  element.style.setProperty('--simplegoals-color-primary-hover', styles.primaryHover);
  element.style.setProperty('--simplegoals-color-opposite', styles.opposite);
}

<|start_filename|>src/modules/overview.js<|end_filename|>
import { applyStyles } from './styles'

let overview = null

export const initOverview = (goals, options) => {
  createOverviewHtml(goals, options)
  overview = document.getElementById('simplegoals-overview')
  const trigger = document.getElementById('simplegoals-overview__close-button')
  trigger.addEventListener('click', event => hideOverview())
}

const createOverviewHtml = (goals, options) => {
  const overviewHtmlString = `
  <div class="simplegoals-overview" id="simplegoals-overview">
    <div class="simplegoals-overview__wrapper">
      <div class="simplegoals-overview__body">
        <h1 class="simplegoals-overview__title">Achievements</h1>
        <p class="simplegoals-overview__subtitle">Complete tasks and unlock achievements</p>
        <div class="simplegoals-overview__goals" id="simplegoals-overview__goals">
        ` + overviewGoalsHtml(goals) + `
        </div>
        <button class="simplegoals-overview__close-button" id="simplegoals-overview__close-button">
          Close
        </button>
        <div class="simplegoals-overview__attribution">
          Powered by <a class="simplegoals-overview__attribution-link" href="https://simplegoals.co" target="_blank">SimpleGoals</a>
        </div>
      </div>
    </div>
  </div>
  `
  const node = new DOMParser().parseFromString(overviewHtmlString , 'text/html').body.firstChild
  applyStyles(node, options)
  document.body.appendChild(node)
}

const overviewGoalsHtml = (goals) => {
  let result = "";
  const sortedKeys = Object.keys(goals).sort((a, b) => goals[b].unlocked - goals[a].unlocked)
  for (const key of sortedKeys) {
    result += `
    <div class="simplegoals-overview-goal ${goals[key].unlocked ? "" : "simplegoals-overview-goal--locked"}">
      <div class="simplegoals-overview-goal__icon">
        <img src="https://static.simplegoals.co/cup.svg" />
      </div>
      <div class="simplegoals-overview-goal__body">
        <h3 class="simplegoals-overview-goal__name">${goals[key].name}</h3>
        <p class="simplegoals-overview-goal__description">
          ${goals[key].description}
        </p>
      </div>
    </div>
    `
  }
  return result
}

export const rerenderOverviewGoals = (goals) => {
  document.getElementById('simplegoals-overview__goals').innerHTML = overviewGoalsHtml(goals)
}

export const showOverview = () => {
  if(!overview) { return }
  overview.scrollTo(0, 0)
  overview.classList.add('simplegoals-overview--opened')
}

const hideOverview = () => {
  overview.classList.remove('simplegoals-overview--opened')
}

<|start_filename|>src/index.js<|end_filename|>
require ('./index.css')

import { prepareGoals } from './modules/local-goals'
import { loadFromCloud } from './modules/cloud-storage'
import { initOverview, showOverview } from './modules/overview'
import { unlockGoal } from './modules/achievements'
import { initButtons } from './modules/buttons'

let options = {
  timeout: 0,
  onGoalUnlock: (goalKey) => {},
  freshStart: false,
  useCloudStorage: false,
  appId: null,
  user: {}
}
let goals = null

export const init = async config => {
  Object.assign(options, config)
  goals = prepareGoals(options)
  await loadFromCloud(goals, options)
  initOverview(goals, options)
  initButtons(goals, options)
}

export const unlock = async (name) => {
  unlockGoal(name, goals, options)
}

export { showOverview } from './modules/overview'

export default {init, unlock, showOverview}


<|start_filename|>src/modules/cloud-storage.js<|end_filename|>
import { postRequest } from './support'

export const saveCloudGoal = async (name, options) => {
  if (options.useCloudStorage) {
    let data = {
      name: name
    }
    addAuthData(data, options)
    await postRequest("/goals/unlock", data)
  } else {
    return false
  }
}

const addAuthData = (hash, options) => {
  hash.appId = options.appId
  hash.user = options.user
}

export const loadFromCloud = async (goals, options) => {
  if (options.useCloudStorage) {
    let data = {
      goals: Object.keys(goals).map((key) => {
        return {
          name: key,
          description: goals[key].description
        }
      }),
      unlockedGoals: Object.keys(goals).filter((key) => {
        goals[key].unlocked
      })
    }
    addAuthData(data, options)
    const response = await postRequest("/sessions", data)
    if (response) {
      updateGoalsFromCloud(response, goals)
    }
  } else {
    return false
  }
}

const updateGoalsFromCloud = (response, goals) => {
  for (const key of Object.keys(goals)) {
    goals[key].unlocked = response.unlockedGoals.includes(key)
  }
}

<|start_filename|>src/modules/buttons.js<|end_filename|>
import { showOverview } from './overview'
import { unlockGoal } from './achievements'

export const initButtons = (goals, options) => {
  document.addEventListener('click', function (event) {
    if (event.target.dataset.simplegoalsUnlock) {
      event.preventDefault()
      unlockGoal(event.target.dataset.simplegoalsUnlock, goals, options)
    }
    if (event.target.dataset.simplegoalsOverview) {
      event.preventDefault()
      showOverview();
    }
  }, false);
}

<|start_filename|>src/modules/local-goals.js<|end_filename|>
export const saveLocalGoal = (name, options) => {
  if (typeof(Storage) !== "undefined" && !options.freshStart) {
    let localGoals = getLocalGoals(options)
    localGoals.push(name)
    localStorage.setItem('simplegoals-storage', JSON.stringify(localGoals))
  }
}

const getLocalGoals = (options) => {
  if (typeof(Storage) !== "undefined" && !options.freshStart) {
    return JSON.parse(localStorage.getItem('simplegoals-storage')) || []
  } else {
    return []
  }
}

export const prepareGoals = (options) => {
  let goals = options.goals
  const localGoals = getLocalGoals(options)
  for (const key of Object.keys(goals)) {
    goals[key].unlocked = localGoals.includes(key)
  }
  return goals
}

<|start_filename|>playground/simplegoals.css<|end_filename|>
[class^='simplegoals'], [class*=' simplegoals'],
[class^='simplegoals'], [class*=' simplegoals']::before,
[class^='simplegoals'], [class*=' simplegoals']::after {
  /* Defaults */
  padding: 0;
  margin: 0;
  text-decoration: none;
  border-width: 0;
  border-style: none;
  outline: none;
  line-height: 1.15;
  -webkit-text-size-adjust: 100%;
  box-sizing: border-box;
  font-family: var(--simplegoals-font-family);
  font-size: 1em;
  font-weight: normal;
}

[class^='simplegoals']:focus, [class*=' simplegoals']:focus {
  outline: none;
  /* outline-style: solid;
  outline-color: var(--simplegoals-color-primary); */
}

/* #region Achievement */

.simplegoals-achievement {
  display: flex;
  align-items: center;
  position: fixed;
  height: 6em;
  top: 1em;
  left: 1em;
  background: var(--simplegoals-color-background);
  border-radius: 3em;
  box-shadow: 0em 0em 2em 0em rgba(0,0,0,0.3);
  overflow: hidden;
  transition: all .7s linear;
  max-width: 6em;
  transform: translateX(-12em) rotate(-180deg);
  opacity: 0;
  z-index: 1789;
  font-size: 16px;
}

.simplegoals-achievement--opened {
  animation: simplegoals-achievement--open 2s ease-out both;
  max-width: calc(640px - 2em);
}

@keyframes simplegoals-achievement--open {
  0% {
    transform: translateX(-12em) rotate(-180deg);
    opacity: 0;
    max-width: 6em;
  }
  45% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  55% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  100% {
    transform: translateX(0) rotate(0deg);
    max-width: calc(640px - 2em);
    opacity: 1;
  }
}

.simplegoals-achievement--closed {
  animation: simplegoals-achievement--close 2s ease-out both;
}

@keyframes simplegoals-achievement--close {
  0% {
    transform: translateX(0) rotate(0deg);
    max-width: calc(640px - 2em);
    opacity: 1;
  }
  45% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  55% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  100% {
    transform: translateX(-12em) rotate(-180deg);
    opacity: 0;
    max-width: 6em;
  }
}

.simplegoals-achievement__icon {
  display: flex;
  width: 6em;
  height: 6em;
  justify-content: center;
  align-items: center;
  flex: 0 0 auto;
  transform: translateY(0);
  transition: all .5s;
}

.simplegoals-achievement__icon img {
  max-height: 3.5em;
  max-width: 3.5em;
}

.simplegoals-achievement__body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: 1 0 auto;
  max-width: calc(640px - 6em - 2em - 8em - 2.5em);
}

.simplegoals-achievement__notification {
  margin: 0;
  padding: .1em;
  font-size: 1em;
  color: var(--simplegoals-color-subtitle);
}

.simplegoals-achievement__name {
  margin: 0;
  padding: .1em;
  font-size: 1.2em;
  font-weight: bold;
  color: var(--simplegoals-color-title);
}

.simplegoals-achievement__button {
  background-color: var(--simplegoals-color-primary);
  display: flex;
  font-size: 1em;
  color: var(--simplegoals-color-opposite);
  padding: 1em;
  margin-left: 1em;
  margin-right: 1.5em;
  border-radius: 3em;
  text-decoration: none;
  cursor: pointer;
  border: none;
  flex: 1 0 auto;
  max-width: 8em;
  text-align: center;
  justify-content: center;
}

.simplegoals-achievement__button:hover {
  background-color: var(--simplegoals-color-primary-hover);
}

.simplegoals-achievement__close-button {
  position: absolute;
  background: none;
  height: 2em;
  width: 2em;
  left: 2em;
  top: 2em;
  transform: translateY(-10em);
  transition: all .5s;
  cursor: pointer;
  font-size: 1em;
}

.simplegoals-achievement__close-button svg {
  height: 2em;
  width: 2em;
  stroke: #E53E3E;
  transition: all .5s;
  transform: rotate(0);
}

.simplegoals-achievement__close-button svg:hover {
  transform: rotate(-90deg);
}

@media (min-width: 640px) and (hover: none) {
  .simplegoals-achievement--clicked .simplegoals-achievement__icon {
    transform: translateY(10em);
  }

  .simplegoals-achievement--clicked .simplegoals-achievement__close-button {
    transform: translateY(0);
  }
}

@media (min-width: 640px) and (hover: hover) {
  .simplegoals-achievement:hover .simplegoals-achievement__icon {
    transform: translateY(10em);
  }

  .simplegoals-achievement:hover .simplegoals-achievement__close-button {
    transform: translateY(0);
  }
}

@media (max-width: 639px) {
  .simplegoals-achievement--opened {
    animation: simplegoals-achievement--open-mobile 2s ease-out both;
    max-width: calc(100vw - 2em);
  }

  .simplegoals-achievement--closed {
    animation: simplegoals-achievement--close-mobile 2s ease-out both;
  }

  .simplegoals-achievement__body {
    max-width: calc(100vw - 8em);
    padding-right: 2em;
    transition: all .5s;
  }

  .simplegoals-achievement__button {
    position: absolute;
    right: 0;
    left: 4em;
    max-width: none;
    transform: translateY(-10em);
    transition: all .5s;
  }

  .simplegoals-achievement--closed .simplegoals-achievement__button{
    opacity: 0 !important;
  }
}

@media (max-width: 639px) and (hover: none) {
  .simplegoals-achievement--clicked .simplegoals-achievement__body {
    transform: translateY(10em);
  }

  .simplegoals-achievement--clicked .simplegoals-achievement__button {
    transform: translateY(0);
    opacity: 1;
  }

  .simplegoals-achievement--clicked .simplegoals-achievement__icon {
    transform: translateY(10em);
  }

  .simplegoals-achievement--clicked .simplegoals-achievement__close-button {
    transform: translateY(0);
  }
}

@media (max-width: 639px) and (hover: hover) {
  .simplegoals-achievement:hover .simplegoals-achievement__body {
    transform: translateY(10em);
  }

  .simplegoals-achievement:hover .simplegoals-achievement__button {
    transform: translateY(0);
    opacity: 1;
  }

  .simplegoals-achievement:hover .simplegoals-achievement__icon {
    transform: translateY(10em);
  }

  .simplegoals-achievement:hover .simplegoals-achievement__close-button {
    transform: translateY(0);
  }
}

@keyframes simplegoals-achievement--open-mobile {
  0% {
    transform: translateX(-12em) rotate(-180deg);
    opacity: 0;
    max-width: 6em;
  }
  45% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  55% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  100% {
    transform: translateX(0) rotate(0deg);
    max-width: calc(100vw - 2em);
    opacity: 1;
  }
}

@keyframes simplegoals-achievement--close-mobile {
  0% {
    transform: translateX(0) rotate(0deg);
    max-width: calc(100vw - 2em);
    opacity: 1;
  }
  45% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  55% {
    transform: translateX(0) rotate(0deg);
    opacity: 1;
    max-width: 6em;
  }
  100% {
    transform: translateX(-12em) rotate(-180deg);
    opacity: 0;
    max-width: 6em;
    display: none;
  }
}

/* #endregion Achievement */

/* #region Overview */

.simplegoals-overview {
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(0,0,0,.5);
  font-size: 16px;
  overflow: auto;
  z-index: 1788;
  opacity: 0;
  transition: opacity .7s;
  transform: translateY(100vh);
}

.simplegoals-overview--opened {
  transform: translateY(0);
  opacity: 1;
}

.simplegoals-overview__wrapper {
  display: flex;
  position: relative;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 10vh 1em;
  transition: all .7s;
  transform: translateY(100vh);
}
.simplegoals-overview--opened > .simplegoals-overview__wrapper {
  transform: translateY(0);
}

.simplegoals-overview__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2em;
  border-radius: 3em;
  width: 640px;
  background-color: var(--simplegoals-color-background);
}

@media (max-width: 639px) {
  .simplegoals-overview__body {
    width: auto;
  }
}

.simplegoals-overview__title {
  font-size: 2em;
  font-weight: bolder;
  margin: .5em;
  color: var(--simplegoals-color-title);
}

.simplegoals-overview__subtitle {
  color: var(--simplegoals-color-subtitle);
}

.simplegoals-overview__goals {
  margin: 1.5em 0;
}

.simplegoals-overview-goal {
  display: flex;
  align-items: center;
}

.simplegoals-overview-goal__icon {
  display: flex;
  margin-right: 1em;
  height: 6em;
  justify-content: center;
  align-items: center;
  flex: 0 0 auto;
  transform: translateY(0);
  transition: all .5s;
}

.simplegoals-overview-goal__icon img {
  max-height: 3.5em;
  max-width: 3.5em;
}

.simplegoals-overview-goal--locked .simplegoals-overview-goal__icon img {
  filter: grayscale(100%);
}

.simplegoals-overview-goal__body {
  padding: .5em 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.simplegoals-overview-goal__name {
  padding: .1em;
  font-size: 1.2em;
  font-weight: bolder;
  color: var(--simplegoals-color-title);
}

.simplegoals-overview-goal__description {
  padding: .1em;
  font-size: 1em;
  color: var(--simplegoals-color-subtitle);
}

.simplegoals-overview__close-button {
  background-color: var(--simplegoals-color-primary);
  font-size: 1em;
  color: var(--simplegoals-color-opposite);
  padding: 1em;
  margin-left: auto;
  margin-right: auto;
  margin-bottom: 1em;
  border-radius: 3em;
  min-width: 8em;
  cursor: pointer;
  text-align: center;
}

.simplegoals-overview__close-button:hover {
  background-color: var(--simplegoals-color-primary-hover);
}

.simplegoals-overview__attribution {
  font-size: .9em;
  color: var(--simplegoals-color-subtitle);
}

.simplegoals-overview__attribution-link {
  color: var(--simplegoals-color-primary);
}

.simplegoals-overview__attribution-link:hover, .simplegoals-overview__attribution-link:active {
  color: var(--simplegoals-color-primary-hover);
}

/* #endregion Overview */

/*# sourceMappingURL=simplegoals.css.map*/

<|start_filename|>package.json<|end_filename|>
{
  "name": "simplegoals",
  "version": "0.3.1",
  "description": "Motivate your users with pleasant and easy-to-use achievements.",
  "main": "dist/simplegoals.js",
  "style": "dist/simplegoals.css",
  "scripts": {
    "build": "yarn build:normal && yarn build:styles-included && yarn build:zip",
    "build:zip": "rm -f dist/simplegoals.zip && zip -r dist/simplegoals.zip dist",
    "build:normal": "yarn build:normal:uncompressed && yarn build:normal:minimized",
    "build:normal:uncompressed": "./node_modules/.bin/webpack",
    "build:normal:minimized": "./node_modules/.bin/webpack --minimize",
    "build:styles-included": "yarn build:styles-included:uncompressed && yarn build:styles-included:minimized",
    "build:styles-included:uncompressed": "./node_modules/.bin/webpack --config webpack.config.styles-included.js",
    "build:styles-included:minimized": "./node_modules/.bin/webpack --config webpack.config.styles-included.js --minimize",
    "release": "./node_modules/.bin/webpack && np --no-tests",
    "playground": "(./node_modules/.bin/webpack --watch --config webpack.config.dev.js & ./node_modules/.bin/live-server playground/ --no-browser)",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/simplegoals/simplegoals.git"
  },
  "keywords": [
    "achievements",
    "goals",
    "gamification",
    "engage",
    "engagement",
    "motivate",
    "motivation"
  ],
  "author": "<NAME>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/simplegoals/simplegoals/issues"
  },
  "homepage": "https://github.com/simplegoals/simplegoals#readme",
  "browserslist": "> 0.25%, not dead",
  "dependencies": {
    "@babel/runtime": "^7.7.4",
    "core-js": "3.4.2"
  },
  "devDependencies": {
    "@babel/cli": "^7.6.2",
    "@babel/core": "^7.6.2",
    "@babel/plugin-transform-runtime": "^7.7.4",
    "@babel/preset-env": "^7.6.2",
    "autoprefixer": "^9.6.1",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.2.0",
    "cssnano": "^4.1.10",
    "live-server": "^1.2.1",
    "mini-css-extract-plugin": "^0.8.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-cli": "^6.1.3",
    "postcss-loader": "^3.0.0",
    "style-loader": "^1.0.0",
    "uglify-js": "^3.6.0",
    "webpack": "^4.41.0",
    "webpack-cli": "^3.3.9"
  }
}


<|start_filename|>src/modules/support.js<|end_filename|>
const apiUrl = "https://api.simplegoals.co"

export const postRequest = async (endpoint, data) => {
  const response = await fetch(`${apiUrl}${endpoint}`, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: {
      'Content-Type': 'application/json'
    }
  })
  if(response.ok) {
    return await response.json()
  } else {
    return false
  }
}

export const hashCode = (source) => {
  let hash = 0;
  if (source.length == 0) { return hash; }
  for (let i = 0; i < source.length; i++) {
    let char = source.charCodeAt(i);
    hash = ((hash<<5)-hash)+char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return hash;
}

<|start_filename|>src/modules/achievements.js<|end_filename|>
import { hashCode } from './support'
import { saveLocalGoal } from './local-goals'
import { saveCloudGoal } from './cloud-storage'
import { rerenderOverviewGoals, showOverview } from './overview'
import { applyStyles } from './styles'

const createAchievementHtml = (key, goal, button, options) => {
  const achievementHtmlString = `
  <div class="simplegoals-achievement" id="simplegoals-achievement-${key}">
    <div class="simplegoals-achievement__icon">
      <img src="https://static.simplegoals.co/cup.svg" />
    </div>
    <div class="simplegoals-achievement__body">
      <p class="simplegoals-achievement__notification">
        New achievement unlocked
      </p>
      <p class="simplegoals-achievement__name" id="simplegoals-achievement__name">${goal.name}</p>
    </div>
    <div class="simplegoals-achievement__button" id="simplegoals-achievement__button-${key}" role="button">${button}</div>
    <button class="simplegoals-achievement__close-button" id="simplegoals-achievement__close-button-${key}">
      <svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
        <g>
          <path d="M50 50L450 450" stroke-width="100" stroke-linecap="round"/>
          <path d="M50 450L450 50" stroke-width="100" stroke-linecap="round"/>
        </g>
      </svg>
    </button>
  </div>
  `
  const node = new DOMParser().parseFromString(achievementHtmlString , 'text/html').body.firstChild
  applyStyles(node, options)
  document.body.appendChild(node)
  return node
}

const recalculateAchievementsTop = () => {
  const nodes = document.getElementsByClassName('simplegoals-achievement')
  for(let i=0; i < nodes.length; i++) {
    nodes[i].style.top = `${1 + i*7}em`
  }
}

const showAchievement = (name, goal, options) => {
  const key = hashCode(name)
  const achievement = createAchievementHtml(key, goal, 'Learn more', options)
  recalculateAchievementsTop()
  achievement.classList.add('simplegoals-achievement--opened')
  initAchievement(key, achievement)
  if (options.timeout) {
    achievementTimeout = setTimeout(() => hideAchievement(achievement), options.timeout)
  }
}

const initAchievement = (key, achievement) => {
  achievement.addEventListener('click', event => achievement.classList.toggle('simplegoals-achievement--clicked'))
  const trigger = document.getElementById(`simplegoals-achievement__close-button-${key}`)
  trigger.addEventListener('click', event => hideAchievement(achievement))
  const overviewTrigger = document.getElementById(`simplegoals-achievement__button-${key}`)
  overviewTrigger.addEventListener('click', event => {
    hideAchievement(achievement)
    setTimeout(showOverview, 200)
  })
}

const hideAchievement = (achievement) => {
  achievement.classList.remove('simplegoals-achievement--opened')
  achievement.classList.add('simplegoals-achievement--closed')
  setTimeout(() => {
    achievement.parentNode.removeChild(achievement)
    recalculateAchievementsTop()
  }, 2000)
}

export const unlockGoal = async (name, goals, options) => {
  if(!goals || !goals[name]) { return }
  if(goals[name].unlocked){
    return
  }
  goals[name].unlocked = true
  options.onGoalUnlock(name)
  saveLocalGoal(name, options)
  await saveCloudGoal(name, options)
  showAchievement(name, goals[name], options)
  rerenderOverviewGoals(goals)
}