GW2 AP Calculator

by Perry Kaufman

HTML

<h1 class=gw2-form-title>
  Guild Wars 2 Achievement Point Calculator
</h1>

<form id=gw2form class=gw2-form>
  <div class=gw2-input-key-wrapper>
    <input id=gw2key class=gw2-input-key name=key type=text maxlength=72 />
    <label class=gw2-input-key-label for=gw2key>API Key</label>
  </div>
  
  <button id=gw2submit class=gw2-form-submit name=calc type=submit>
		<label class=gw2-submit-label for=gw2submit >Calculate</label>
	</button>
</form>

<div class=gw2-output-wrapper>
  <label class=gw2-output-label for=gw2output>Achievement Points</label>
  <output class=gw2-output id=gw2output form=gw2form name=total></output>
</div>

<p class=gw2-disclaimer>
  <b>Disclaimer:</b>
  Inaccurate due to some achievements not being included in the api.
</p>

CSS

@import url('https://fonts.googleapis.com/css?family=Raleway');

:root {
  --gw2-red: rgb(170, 24, 23);
  --gw2-darkred: rgb(62, 18, 9);
}

html {
  box-sizing: border-box;
  font-family: 'Raleway', sans-serif;
  font-size: 18px;
}

body {
  align-items: center;
  display: flex;
  flex-flow: column nowrap;
  margin: 0;
  min-width: 300px;
  padding: 0;
}

b {
  color: var(--gw2-red);
}

label {
  font-weight: 600;
}

:invalid {
  box-shadow: none;
}

* {
  box-sizing: inherit;
}

.gw2-form-title {
  color: var(--gw2-red);
  font-size: 1.5em;
  margin: 5px 10px;
}

.gw2-form {
  align-items: center;
  display: flex;
  padding: 10px 5px;
  max-width: 600px;
  width: 100%;
}

/*Form Input*/

.gw2-input-key-wrapper {
  font-size: 1rem;
  height: 3em;
  flex: 1;
  position: relative;
}

.gw2-input-key {
  border: 2px solid var(--gw2-red);
  border-radius: 5px;
  font-size: .8em;
  height: 100%;
  padding: 1.5em .5em 0 .5em;
  width: 100%;
  transition: all ease-in-out .25s;
}

.gw2-input-key:focus {
  border-color: var(--gw2-darkred);
}

.gw2-input-key:invalid {
	color: red;
}

.gw2-input-key-label {
  color: var(--gw2-red);
  font-size: .9rem;
  font-weight: bold;
  position: absolute;
  top: 0.5em;
  left: 0.7em;
}

/*Form Submit*/

.gw2-form-submit {
  background: var(--gw2-red);
  border: 2px solid transparent;
  border-radius: 5px;
  color: white;
  font-size: 1rem;
  font-weight: 600;
  height: 3em;
  margin-left: 5px;
  padding: 0 0.5em;
  position: relative;
  transition: all ease-in-out .25s;
  width: 6em;
}

.gw2-form-submit:disabled {
  background: white !important;
  border: 2px solid var(--gw2-red);
  color: transparent;
}

.gw2-form-submit:disabled::before {
  color: var(--gw2-red);
  content: 'calculating...';
  font-size: 0.6em;
  left: 0;
  position: absolute;
  text-align: center;
  top: 0.3em;
  width: 100%;
}

@keyframes sidetoside {
  25% {transform: translateX(-0.25em);}
  75% {transform: translateX(0.25em);}
  100% {transform:...

JavaScript

'use strict'

//Error thrown if a key is rejected by the API.
class KeyError extends Error {}

//Error thrown if an API fetch fails.
class FetchError extends Error {}

//A sample API key.
const KEY = 'C2E85010-36C2-D446-BA0D-1237A58B731C2504C928-7861-4928-AE47-F281AE27898C';

//The form...
const FORM = document.forms[0];

//The required length of an API key.
const KEY_LENGTH = 72;

/* Contains utilities for generating api urls.
 */
const GW2Util = {
  _BASE_PATH: 'https://api.guildwars2.com',
  _ACCOUNT_PATH: '/v2/account',
  _ACHIEVE_PATH: '/v2/achievements',
  _ACCOUNT_ACHIEVE_PATH: '/v2/account/achievements',
  _TOKEN_INFO_PATH: '/v2/tokeninfo',
  /* Returns API token info path.
   */
  getTokenInfoPath(key) {
  	return this._BASE_PATH + this._TOKEN_INFO_PATH + '?access_token=' + key;
  },
  /* Returns API account info path.
   */
  getAccountPath(key) {
    return this._BASE_PATH + this._ACCOUNT_PATH + '?access_token=' + key;
  },
  /* Returns aPI Account achievements path
   */
  getAccountAchievementPath(key) {
    return this._BASE_PATH + this._ACCOUNT_ACHIEVE_PATH + '?access_token=' + key;
  },
  /* Returns API achievements path.
   */
  getAchievementPath(ids) {
    return this._BASE_PATH + this._ACHIEVE_PATH + '?ids=' + ids.join();
  }
};

/* Async functions that can be used to access account 
 * and achievement information on the Guild Wars 2 API.
 */
const GW2API = {
	/* Request options.
   */
  _OPTIONS: {
    method: 'GET',
    mode: 'cors'
  },
  /* Gets data about the key from the API.
   */
  async getTokenInfo(key) {
  	const request = new Request(GW2Util.getTokenInfo(key), this._OPTIONS);
    let response;
    try {
    	response = await fetch(request);
    } catch(error) {
    	throw new FetchError('getTokenInfo(): GET request failed');
    }
    if (!response.ok) throw new KeyError('getTokenInfo(): ' + response.status);
    return await response.json();
  },
  /* Get account data from the API.
   */
  async getAccount(key) {
    const request...