diff --git a/bower_components/google-sheets/.bower.json b/bower_components/google-sheets/.bower.json
new file mode 100644
index 0000000..c36429b
--- /dev/null
+++ b/bower_components/google-sheets/.bower.json
@@ -0,0 +1,44 @@
+{
+  "name": "google-sheets",
+  "version": "1.0.5",
+  "homepage": "https://googlewebcomponents.github.io/google-sheets",
+  "description": "Web components to interact with Google Sheets",
+  "main": "google-sheets.html",
+  "authors": [
+    "Eric Bidelman <ebidel@gmail.com>"
+  ],
+  "license": "Apache2",
+  "ignore": [
+    "/.*",
+    "/test/"
+  ],
+  "keywords": [
+    "web-component",
+    "web-components",
+    "polymer",
+    "spreadsheets",
+    "google"
+  ],
+  "dependencies": {
+    "polymer": "Polymer/polymer#^1.1.2",
+    "google-apis": "GoogleWebComponents/google-apis#^1.1.0",
+    "google-signin": "GoogleWebComponents/google-signin#^1.0.3",
+    "iron-ajax": "PolymerElements/iron-ajax#^1.0.0"
+  },
+  "devDependencies": {
+    "iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
+    "google-map": "GoogleWebComponents/google-map#^1.1.0",
+    "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0",
+    "web-component-tester": "*"
+  },
+  "_release": "1.0.5",
+  "_resolution": {
+    "type": "version",
+    "tag": "1.0.5",
+    "commit": "1e87081a4abfdd31cf7cbf4ac970c7f9873c447d"
+  },
+  "_source": "git://github.com/GoogleWebComponents/google-sheets.git",
+  "_target": "~1.0.5",
+  "_originalSource": "GoogleWebComponents/google-sheets",
+  "_direct": true
+}
\ No newline at end of file
diff --git a/bower_components/google-sheets/LICENSE b/bower_components/google-sheets/LICENSE
new file mode 100644
index 0000000..52aea39
--- /dev/null
+++ b/bower_components/google-sheets/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2014 Google Inc
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/bower_components/google-sheets/README.md b/bower_components/google-sheets/README.md
new file mode 100755
index 0000000..119acf4
--- /dev/null
+++ b/bower_components/google-sheets/README.md
@@ -0,0 +1,4 @@
+google-sheets
+================
+
+See the [component page](https://googlewebcomponents.github.io/google-sheets) for more information.
diff --git a/bower_components/google-sheets/bower.json b/bower_components/google-sheets/bower.json
new file mode 100755
index 0000000..1b1428c
--- /dev/null
+++ b/bower_components/google-sheets/bower.json
@@ -0,0 +1,34 @@
+{
+  "name": "google-sheets",
+  "version": "1.0.5",
+  "homepage": "https://googlewebcomponents.github.io/google-sheets",
+  "description": "Web components to interact with Google Sheets",
+  "main": "google-sheets.html",
+  "authors": [
+    "Eric Bidelman <ebidel@gmail.com>"
+  ],
+  "license": "Apache2",
+  "ignore": [
+    "/.*",
+    "/test/"
+  ],
+  "keywords": [
+    "web-component",
+    "web-components",
+    "polymer",
+    "spreadsheets",
+    "google"
+  ],
+  "dependencies": {
+    "polymer": "Polymer/polymer#^1.1.2",
+    "google-apis": "GoogleWebComponents/google-apis#^1.1.0",
+    "google-signin": "GoogleWebComponents/google-signin#^1.0.3",
+    "iron-ajax": "PolymerElements/iron-ajax#^1.0.0"
+  },
+  "devDependencies": {
+    "iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
+    "google-map": "GoogleWebComponents/google-map#^1.1.0",
+    "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0",
+    "web-component-tester": "*"
+  }
+}
diff --git a/bower_components/google-sheets/demo/demo-private.html b/bower_components/google-sheets/demo/demo-private.html
new file mode 100644
index 0000000..3ecc6d9
--- /dev/null
+++ b/bower_components/google-sheets/demo/demo-private.html
@@ -0,0 +1,106 @@
+<!doctype html>
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+<html>
+<head>
+  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+  <title>google-sheets private data demo</title>
+  <script src="../../webcomponentsjs/webcomponents-lite.min.js"></script>
+  <link rel="import" href="../google-sheets.html">
+  <link rel="import" href="../../google-map/google-map.html">
+  <link rel="import" href="../../google-signin/google-signin.html">
+  <style>
+    * {
+      box-sizing: border-box;
+    }
+    body {
+      margin: 2em;
+      font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial;
+      font-weight: 300;
+      background-color: #f1f1f3;
+    }
+    a {
+      text-decoration: none;
+      color: blue;
+    }
+    ul {
+      padding-left: 0;
+    }
+    ul, li {
+      list-style: none;
+      font-size: 14px;
+    }
+    section {
+      border-radius: 3px;
+      box-shadow: 1px 1px 3px #ccc;
+      padding: 1em 2em;
+      background-color: white;
+      width: 500px;
+      min-height: 500px;
+    }
+    main {
+      justify-content: space-around;
+      margin-top: 2em;
+    }
+  </style>
+</head>
+<body>
+
+<google-signin
+  client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com"
+  scopes="https://spreadsheets.google.com/feeds">
+</google-signin>
+
+<p>A <code>&lt;google-sheets></code> element returning data from a <b>private</b> Google Spreadsheet:</p>
+<p><b>Note:</b> update the demo source to your clientId and private spreadsheet key for the demo to work.</p>
+
+<main class="layout horizontal">
+
+<template id="spreadsheets" is="dom-bind">
+
+<!-- Example: private spreadsheet -->
+<google-sheets id="sheet" tab-id="1"
+       client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com"
+       key="1QMGizivw3UJ3-R9BFK7sfrXE0RL87dygk2C0RcuKoDY"
+       open-in-google-docs-url="{{openInGoogleDocsURL}}"
+       tab="{{tab}}"
+       spreadsheets="{{spreadsheets}}"
+       rows="{{rows}}"></google-sheets>
+
+<section>
+
+  <heading>
+    <h3>List of spreadsheets</h3>
+  </heading>
+
+  <ul>
+    <template is="dom-repeat" items="[[spreadsheets]]">
+      <li>{{item.title.$t}}</li>
+    </template>
+  </ul>
+
+</section>
+
+<section>
+
+  <heading>
+    <h3>Spreadsheet rows:
+      <a href="{{openInGoogleDocsURL}}" target="_blank" title="Open in Google Docs &rarr;">
+        "<span>{{tab.title}}</span>" tab
+      </a>
+    </h3>
+    <h5>updated: <span>{{tab.updated}}</span>, by: <template is="dom-repeat" items="{{rows.authors}}"><span>{{item.name}}</span></template></h5>
+  </heading>
+  <ul>
+    <template is="dom-repeat" items="{{rows}}">
+      <li>Name: <span>{{item.gsx$name.$t}}</span> ( lat: <span>{{item.gsx$lat.$t}}</span>, lng: <span>{{item.gsx$lng.$t}}</span> )</li>
+    </template>
+  </ul>
+
+</section>
+
+</template>
+
+</main>
+
+</body>
+</html>
diff --git a/bower_components/google-sheets/demo/index.html b/bower_components/google-sheets/demo/index.html
new file mode 100755
index 0000000..623ef48
--- /dev/null
+++ b/bower_components/google-sheets/demo/index.html
@@ -0,0 +1,105 @@
+<!doctype html>
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+<html>
+<head>
+  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+  <title>google-sheets Demo</title>
+  <script src="../../webcomponentsjs/webcomponents-lite.min.js"></script>
+  <link rel="import" href="../../iron-flex-layout/classes/iron-flex-layout.html">
+  <link rel="import" href="../google-sheets.html">
+  <link rel="import" href="../../google-map/google-map.html">
+  <style>
+    * {
+      box-sizing: border-box;
+    }
+    body {
+      margin: 2em;
+      font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial;
+      font-weight: 300;
+      background-color: #f1f1f3;
+    }
+    a {
+      text-decoration: none;
+      color: blue;
+    }
+    ul {
+      padding-left: 0;
+    }
+    ul, li {
+      list-style: none;
+      font-size: 14px;
+    }
+    section {
+      border-radius: 3px;
+      box-shadow: 1px 1px 3px #ccc;
+      padding: 1em 2em;
+      background-color: white;
+      width: 500px;
+      height: 500px;
+    }
+    google-map {
+      display: block;
+      height: 100%;
+      width: 100%;
+    }
+    main {
+      justify-content: space-around;
+      margin-top: 2em;
+    }
+  </style>
+</head>
+<body>
+
+<p>A <code>&lt;google-sheets></code> element returns data from a Google Spreadsheet:</p>
+
+<main class="layout horizontal">
+
+<template id="container" is="dom-bind">
+
+  <section>
+
+      <!-- Example: published spreadsheet -->
+      <google-sheets id="sheet" key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc" tab-id="1"
+                     published rows="{{rows}}" tab="{{tab}}" open-in-google-docs-url="{{openInGoogleDocsUrl}}"></google-sheets>
+
+      <heading>
+        <h3>Spreadsheet rows:
+          <a href="{{openInGoogleDocsUrl}}" target="_blank" title="Open in Google Docs &rarr;">
+            "<span>{{tab.title}}</span>" tab
+          </a>
+        </h3>
+        <h5>updated: <span>{{tab.updated}}</span>, by: <template is="dom-repeat" items="{{rows.authors}}">{{item.name}}</template></h5>
+      </heading>
+      <ul>
+        <template is="dom-repeat" items="[[rows]]">
+          <li>Name: <span>{{item.gsx$name.$t}}</span> ( lat: <span>{{item.gsx$lat.$t}}</span>, lng: <span>{{item.gsx$lng.$t}}</span> )</li>
+        </template>
+      </ul>
+
+  </section>
+
+  <section style="padding: 0;">
+
+    <google-map disable-default-ui fit-to-markers>
+      <template is="dom-repeat" items="[[rows]]">
+        <google-map-marker latitude="{{item.gsx$lat.$t}}" longitude="{{item.gsx$lng.$t}}"></google-map-marker>
+      </template>
+    </google-map>
+
+    <button on-click="useTab" data-tabid="1">View tab 1 data</button>
+    <button on-click="useTab" data-tabid="2">View tab 2 data</button>
+
+  </section>
+
+</template>
+</main>
+
+<script>
+var template = document.querySelector('#container');
+
+template.useTab = function(e, detail, sender) {
+  document.querySelector('#sheet').tabId = Number(e.currentTarget.dataset.tabid);
+};
+</script>
+</body>
+</html>
diff --git a/bower_components/google-sheets/google-sheets.html b/bower_components/google-sheets/google-sheets.html
new file mode 100755
index 0000000..2bf9806
--- /dev/null
+++ b/bower_components/google-sheets/google-sheets.html
@@ -0,0 +1,431 @@
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+
+<link rel="import" href="../polymer/polymer.html">
+<link rel="import" href="../iron-ajax/iron-ajax.html">
+<link rel="import" href="../google-signin/google-signin-aware.html">
+
+<!--
+Element for interacting with Google Sheets.
+
+`<google-sheets>` pulls cell data from the Google Sheet specified by `key`.
+A spreadsheet's key can be found in the URL when viewing it in google docs (e.g. `docs.google.com/spreadsheet/ccc?key=<KEY>#gid=12345`).
+
+Optionally, pass the `tab-id` attribute to specify a particular worksheet tab in the spreadsheet. For example, the first tab would be `tab-id="1"`. If `tab` is updated at a later time, the underlying data is also updated. **API calls are cached** as to not make extraneous calls.
+
+See [developers.google.com/google-apps/spreadsheets](https://developers.google.com/google-apps/spreadsheets) for full Spreadsheets API documentation.
+
+#### Example
+
+    <google-sheets key="..." tab-id="1" client-id="..."></google-sheets>
+
+    <script>
+      var sheet = document.querySelector('google-sheets');
+
+      sheet.addEventListener('google-sheet-data', function(e) {
+       // this.spreadsheets - list of the user's spreadsheets
+       // this.tab - information on the tab that was fetched
+       // this.rows - cell row information for the tab that was fetched
+      });
+
+      sheet.addEventListener('error', function(e) {
+       // e.detail.response
+      });
+    </script>
+
+<b>Example</b> - `published` is a perf optimization and hints that the spreadsheet has been published (public):
+
+    <google-sheets key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc" published></google-sheets>
+
+<b>Example</b> - leaving off the `key` returns as list of the user's spreadsheets.
+
+    <google-sheets client-id="..."></google-sheets>
+
+<b>Example</b> - show a list of Map markers, using data-binding features inside Polymer:
+
+    <template is="dom-bind">
+      <google-sheets
+        key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc" tab-id="1" rows="{{rows}}"
+        client-id="...">
+      </google-sheets>
+      <google-map>
+        <google-map-marker latitude="{{gsx$lat.$t}}" longitude="{{gsx$lng.$t}}">
+      </google-map>
+    </template>
+
+<b>Example</b> - list a user's private spreadsheets. Authenticate with google-signin button.
+
+    <google-signin
+      client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com"
+      scopes="https://spreadsheets.google.com/feeds">
+    </google-signin>
+
+    <template is="dom-bind">
+      <google-sheets client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com"
+         key="1QMGizivw3UJ3-R9BFK7sfrXE0RL87dygk2C0RcuKoDY" tab-id="1"
+         spreadsheets="{{spreadsheets}}"></google-sheets>
+      <template is="dom-repeat" items="[[spreadsheets]]">
+        <p>{{item.title.$t}}</p>
+      </template>
+    </template>
+
+@demo
+-->
+
+<dom-module id="google-sheets">
+  <template>
+    <template is="dom-if" if="{{!published}}">
+      <google-signin-aware client-id="{{clientId}}"
+                     scopes="https://spreadsheets.google.com/feeds"
+                     on-google-signin-aware-success="_onSignInSuccess"
+                     on-google-signin-aware-signed-out="_onSignInFail"></google-signin-aware>
+    </template>
+
+    <iron-ajax id="publicajax" params='{"alt": "json"}' handle-as="json"
+               on-response="_onCellRows"></iron-ajax>
+    <iron-ajax id="listsheetsajax" params='{"alt": "json"}' handle-as="json"
+               on-response="_onSpreadsheetList"></iron-ajax>
+    <iron-ajax id="worksheetajax" params='{"alt": "json"}' handle-as="json"
+               on-response="_onWorksheet"></iron-ajax>
+    <iron-ajax id="cellrowsajax" params='{"alt": "json"}' handle-as="json"
+               on-response="_onCellRows"></iron-ajax>
+
+  </template>
+</dom-module>
+
+<script>
+(function() {
+  var SCOPE_ = 'https://spreadsheets.google.com/feeds';
+
+  // Minimal cache for worksheet row data. Shared across instances so subsequent
+  // accesses are fast and API calls only happen once.
+  var rowDataCache_ = {};
+
+  function generateCacheKey_() {
+    return this._worksheetId + '_'+ this.tabId;
+  }
+
+  function getLink_(rel, links) {
+    for (var i = 0, link; link = links[i]; ++i) {
+      if (link.rel === rel) {
+        return link;
+      }
+    }
+    return null;
+  }
+
+  // Conversion of Worksheet Ids to GIDs and vice versa
+  // od4 > 2
+  function wid_to_gid_(wid) {
+    return parseInt(String(wid), 36) ^ 31578;
+  }
+  // 2 > 0d4
+  function gid_to_wid_(gid) {
+    // (gid xor 31578) encoded in base 36
+    return parseInt((gid ^ 31578)).toString(36);
+  }
+
+  window.GoogleSheets = Polymer({
+
+    is: 'google-sheets',
+
+    /**
+     * Fired when the spreadsheet's cell information is available.
+     *
+     * @event google-sheet-data
+     * @param {Object} detail
+     * @param {Object} detail.data The data returned by the Spreadsheet API.
+     * @param {string} detail.type The type of data that was fetched.
+     *     One of 'spreadsheets', 'tab', 'rows' * to correspond to the feed type.
+     */
+
+    hostAttributes: {
+      hidden: true
+    },
+
+    properties: {
+      /**
+       * A Google Developers client ID. Obtain from [console.developers.google.com](https://console.developers.google.com). Required for accessing a private spreadsheet. Optional if accessing a public spreadsheet.
+       */
+      clientId: {
+        type: String,
+        value: '',
+        observer: '_configUpdate'
+      },
+
+      /**
+       * The key of the spreadsheet. This can be found in the URL when viewing
+       * the document is Google Docs (e.g. `docs.google.com/spreadsheet/ccc?key=<KEY>`).
+       *
+       * Leaving off this attribute still returns a list of the users spreadsheets in the `spreadsheets` property.
+       */
+      key: {
+        type: String,
+        value: '',
+        observer: '_keyChanged'
+      },
+
+      /**
+       * Tab within a spreadsheet. For example, the first tab in a spreadsheet
+       * would be `tab-id="1"`.
+       */
+      tabId: {
+        type: Number,
+        value: 1,
+        observer: '_configUpdate'
+      },
+
+      /**
+       * A hint that the spreadsheet is published publicly in Google Docs. Used as a performance optimization.
+       * Make sure the sheet is also publicly viewable by anyone in the Share settings.
+       *
+       * @attribute published
+       * @type boolean
+       * @default false
+       */
+      published: {
+        type: Boolean,
+        value: false,
+        observer: '_configUpdate'
+      },
+
+      /**
+       * The fetched sheet corresponding to the `key` attribute.
+       */
+      sheet: {
+        type: Object,
+        value: function() { return {}; },
+        readOnly: true,
+        notify: true,
+        observer: '_sheetChanged'
+      },
+
+      /**
+       * Meta data about the particular tab that was retrieved for the spreadsheet.
+       */
+      tab: {
+        type: Object,
+        value: function() { return {}; },
+        readOnly: true,
+        notify: true,
+        observer: '_tabChanged'
+      },
+
+      /**
+       * If a spreadsheet `key` is specified, returns a list of cell row data.
+       */
+      rows: {
+        type: Array,
+        value: function() { return []; },
+        readOnly: true,
+        notify: true
+      },
+
+      /**
+       * List of the user's spreadsheets. Shared across instances.
+       */
+      spreadsheets: {
+        type: Array,
+        readOnly: true,
+        notify: true,
+        value: function() { return []; }
+      },
+
+      /**
+       * The URL to open this spreadsheet in Google Sheets.
+       */
+      openInGoogleDocsUrl: {
+        type: String,
+        computed: '_computeGoogleDocsUrl(key)',
+        notify: true
+      }
+    },
+
+    _worksheetId: null,
+
+    _computeGoogleDocsUrl: function(key) {
+      var url = 'https://docs.google.com/spreadsheet/';
+      if (key) {
+        url += 'ccc?key=' + key;
+      }
+      return url;
+    },
+
+    _configUpdate: function(key, published, tabId, clientId) {
+      this._tabIdChanged();
+    },
+
+    _keyChanged: function(newValue, oldValue) {
+      // TODO(ericbidelman): need to better handle updates to the key attribute.
+      // Below doesn't account for private feeds.
+      if (this.published) {
+        var url = SCOPE_ + '/list/' + this.key + '/' +
+                  this.tabId + '/public/values';
+        this.$.publicajax.url = url;
+        this.$.publicajax.generateRequest();
+      }
+    },
+
+    _tabIdChanged: function(newValue, oldValue) {
+      if (this._worksheetId) {
+        this._getCellRows();
+      } else if (this.published) {
+        this._keyChanged();
+      }
+    },
+
+    _sheetChanged: function(newValue, oldValue) {
+      if (!this.sheet.title) {
+        return;
+      }
+
+      // Make metadata easily accessible on sheet object.
+      var authors = this.sheet.author && this.sheet.author.map(function(a) {
+        return {email: a.email.$t, name: a.name.$t};
+      });
+
+      this.set('sheet.title', this.sheet.title.$t);
+      this.set('sheet.updated', new Date(this.sheet.updated.$t));
+      this.set('sheet.authors', authors);
+
+      this._worksheetId = this.sheet.id.$t.split('/').slice(-1)[0];
+      this._getWorksheet();
+    },
+
+    _tabChanged: function(newValue, oldValue) {
+      if (!this.tab.title) {
+        return;
+      }
+
+      var authors = this.tab.authors = this.tab.author && this.tab.author.map(function(a) {
+        return {email: a.email.$t, name: a.name.$t};
+      });
+
+      this.set('tab.title', this.tab.title.$t);
+      this.set('tab.updated', new Date(this.tab.updated.$t));
+      this.set('tab.authors', authors);
+
+      this.fire('google-sheet-data', {
+        type: 'tab',
+        data: this.tab
+      });
+    },
+
+    _onSignInSuccess: function(e, detail) {
+      var oauthToken = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse();
+
+      var headers = {
+        'Authorization': 'Bearer ' + oauthToken.access_token
+      };
+
+      this.$.listsheetsajax.headers = headers;
+      this.$.worksheetajax.headers = headers;
+      this.$.cellrowsajax.headers = headers;
+
+      // TODO(ericbidelman): don't make this call if this.spreadsheets is
+      // already populated from another instance.
+      this._listSpreadsheets();
+    },
+
+    _onSignInFail: function(e, detail) {
+      // TODO(ericbidelman): handle this in some way.
+      console.log(e, e.type);
+    },
+
+    _listSpreadsheets: function() {
+      var url = SCOPE_ + '/spreadsheets/private/full';
+      this.$.listsheetsajax.url = url;
+      this.$.listsheetsajax.generateRequest();
+    },
+
+    _onSpreadsheetList: function(e) {
+      e.stopPropagation();
+
+      var feed = e.target.lastResponse.feed;
+
+      this._setSpreadsheets(feed.entry);
+
+      this.fire('google-sheet-data', {
+        type: 'spreadsheets',
+        data: this.spreadsheets
+      });
+
+      // Fetch worksheet feed if key was given and worksheet exists.
+      if (this.key) {
+        for (var i = 0, entry; entry = feed.entry[i]; ++i) {
+          var altLink = getLink_('alternate', entry.link);
+          if (altLink && altLink.href.indexOf(this.key) != -1) {
+            this._setSheet(entry);
+            break;
+          }
+        }
+      }
+    },
+
+    _getWorksheet: function() {
+      if (!this._worksheetId) {
+        throw new Error('workesheetId was not given.');
+      }
+
+      var url = SCOPE_ + '/worksheets/' + this._worksheetId +
+                '/private/full/' + this.tabId;
+      this.$.worksheetajax.url = url;
+      this.$.worksheetajax.generateRequest();
+    },
+
+    _onWorksheet: function(e) {
+      e.stopPropagation();
+
+      // this.tab = e.target.lastResponse.entry;
+      this._setTab(e.target.lastResponse.entry);
+      this._getCellRows();
+    },
+
+    _getCellRows: function() {
+      // Use cached data if available.
+      var key = generateCacheKey_.call(this);
+      if (key in rowDataCache_) {
+        this._onCellRows(null, null, rowDataCache_[key]);
+
+        return;
+      }
+
+      var url = SCOPE_ + '/list/' +
+                this._worksheetId + '/' + this.tabId +
+                '/private/full';
+      this.$.cellrowsajax.url = url;
+      this.$.cellrowsajax.generateRequest();
+    },
+
+    _onCellRows: function(e) {
+      e.stopPropagation();
+
+      var feed = e.target.lastResponse.feed;
+
+      // Cache data if key doesn't exist.
+      var key = generateCacheKey_.call(this);
+      if (!(key in rowDataCache_)) {
+        rowDataCache_[key] = {response: {feed: feed}};
+      }
+
+      // this.rows = feed.entry;
+      this._setRows(feed.entry);
+      var authors = feed.author && feed.author.map(function(a) {
+        return {email: a.email.$t, name: a.name.$t};
+      });
+      this.set('rows.authors', authors);
+
+      if (this.published) {
+        // this.tab = feed;
+        this._setTab(feed);
+      }
+
+      this.fire('google-sheet-data', {
+        type: 'rows',
+        data: this.rows
+      });
+    }
+
+  });
+
+})();
+</script>
diff --git a/bower_components/google-sheets/index.html b/bower_components/google-sheets/index.html
new file mode 100755
index 0000000..203f4fa
--- /dev/null
+++ b/bower_components/google-sheets/index.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+<html>
+<head>
+
+  <script src="../webcomponentsjs/webcomponents-lite.js"></script>
+  <link rel="import" href="../iron-component-page/iron-component-page.html">
+
+</head>
+<body>
+
+  <iron-component-page></iron-component-page>
+
+</body>
+</html>
diff --git a/bower_components/google-sheets/tests/google-sheet.html b/bower_components/google-sheets/tests/google-sheet.html
new file mode 100644
index 0000000..f8544ef
--- /dev/null
+++ b/bower_components/google-sheets/tests/google-sheet.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>google-sheet tests</title>
+  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+
+  <script src="../../../platform/platform.js"></script>
+  <link rel="import" href="../../../polymer-test-tools/tools.html">
+  <script src="../../../polymer-test-tools/htmltest.js"></script>
+
+  <link rel="import" href="../google-sheets.html">
+</head>
+<body>
+
+<google-sheets id="sheet1"></google-sheets>
+
+<script>
+document.addEventListener('polymer-ready', function() {
+
+  (function() {
+    var sheet = document.querySelector('#sheet1');
+    var root = sheet.shadowRoot;
+
+    // Check defaults.
+    assert.lengthOf(sheet.spreadsheets, 0,
+        '.spreadsheets length should default to 0');
+    assert.lengthOf(sheet.rows, 0, '.rows length should default to 0');
+    assert.isObject(sheet.sheet, '.sheet should default to {}');
+    assert.isObject(sheet.tab, '.tab should default to {}');
+
+    assert.equal(sheet.key, '', ".key default is not ''");
+    assert.equal(sheet.gid, 0, '.gid default is not 0');
+    assert.isFalse(sheet.published, '.published does not default to false');
+    assert.isNotNull(root.querySelector('google-signin-aware'),
+        'google-signin-aware should be created for non-public sheet');
+
+    done();
+
+  })();
+
+});
+</script>
+</body>
+</html>
diff --git a/bower_components/google-sheets/tests/index.html b/bower_components/google-sheets/tests/index.html
new file mode 100644
index 0000000..297cdef
--- /dev/null
+++ b/bower_components/google-sheets/tests/index.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Runs all tests</title>
+    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+    <script src="../../platform/platform.js"></script>
+    <link rel="import" href="tests.html">
+  </head>
+  <body>
+    <div id="mocha"></div>
+  </body>
+</html>
diff --git a/bower_components/google-sheets/tests/private.html b/bower_components/google-sheets/tests/private.html
new file mode 100644
index 0000000..c6f3ee3
--- /dev/null
+++ b/bower_components/google-sheets/tests/private.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>google-sheet tests</title>
+  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+
+  <script src="../../../platform/platform.js"></script>
+  <link rel="import" href="../../../polymer-test-tools/tools.html">
+  <script src="../../../polymer-test-tools/htmltest.js"></script>
+
+  <link rel="import" href="../google-sheets.html">
+</head>
+<body>
+
+<div>
+  <p>Tests live private spreadsheet data.</p>
+  <p>This test needs to be run manually, standalone in order to fetch an OAuth token.</p>
+
+  <b>NOTES:</b><br>
+  <li>run manually off of localhost:3000/google-sheets/tests/private.html. Can also run from port 8080.</li>
+  <li>use the account: webcomponents.test@gmail.com</li>
+</div>
+
+<google-sheets id="sheet" gid="1"
+       clientId="750497606405-1hq66meqmr4dp09dn54j9ggv85vbv0gp.apps.googleusercontent.com"
+       key="0AjqzxJ5RoRrWdGh2S0RRYURXTlNHdm9pQlFuM1ZwZWc"></google-sheets>
+
+<script>
+document.addEventListener('polymer-ready', function() {
+
+  (function() {
+    var sheet = document.querySelector('#sheet');
+    var root = sheet.shadowRoot;
+
+    // Test set attributes.
+    assert.equal(sheet.key,
+        '0AjqzxJ5RoRrWdGh2S0RRYURXTlNHdm9pQlFuM1ZwZWc', ".key was not updated");
+    assert.equal(sheet.gid, 1, '.gid was not updated');
+    assert.equal(sheet.clientId,
+        '750497606405-1hq66meqmr4dp09dn54j9ggv85vbv0gp.apps.googleusercontent.com', ".clientId was not set");
+
+    sheet.addEventListener('google-sheet-data', function(e) {
+
+      switch (e.detail.type) {
+        case 'spreadsheets':
+          assert.isTrue(this.spreadsheets.length > 0,
+              '.spreadsheets should be populated for private feeds.');
+          break;
+        case 'tab':
+          assert.equal(this.tab.title, 'SECONDTAB', '.tab.title is incorrect');
+          break;
+        case 'rows':
+          assert.lengthOf(this.rows.authors, 1, '.rows.authors array');
+
+          var name = this.rows.authors[0].name;
+          var email = this.rows.authors[0].email
+
+          assert.equal(email, 'webcomponents.test@gmail.com', 'author email not set correctly');
+          assert.equal(name, 'webcomponents.test', 'author name not set correctly');
+
+          assert.equal(this.rows[0].title.$t, 'FIRST NAME', '"name" column was incorrect');
+          assert.equal(this.rows[1].gsx$state.$t, 'OR', '"state" column was incorrect');
+
+          break;
+        default:
+          // Noop
+      }
+
+      done();
+
+    });
+
+  })();
+
+});
+</script>
+</body>
+</html>
+
diff --git a/bower_components/google-sheets/tests/published.html b/bower_components/google-sheets/tests/published.html
new file mode 100644
index 0000000..067159a
--- /dev/null
+++ b/bower_components/google-sheets/tests/published.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>google-sheet tests</title>
+  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+
+  <script src="../../../platform/platform.js"></script>
+  <link rel="import" href="../../../polymer-test-tools/tools.html">
+  <script src="../../../polymer-test-tools/htmltest.js"></script>
+
+  <link rel="import" href="../google-sheets.html">
+</head>
+<body>
+
+<google-sheets id="sheet" key="0Anye-JMjUkZZdDBkMVluMEhZMmFGeHpYdDJJV1FBRWc"
+               published></google-sheets>
+
+<script>
+document.addEventListener('polymer-ready', function() {
+
+  var sheet = document.querySelector('#sheet');
+  var root = sheet.shadowRoot;
+
+  assert.isTrue(sheet.published);
+  assert.isNull(root.querySelector('google-signin-aware'),
+      'google-signin-aware should not be created for a published sheet');
+
+  sheet.addEventListener('google-sheet-data', function(e) {
+
+    if (e.detail.type === 'tab') {
+      assert.equal(this.tab.title, 'Locations',
+                   'Published spreadsheet title is not correct.');
+      assert.isNotNull(this.tab.updated, '.tab.updated was not set');
+      assert.isTrue(this.tab.authors.length > 0, '.tab.authors was 0');
+    } else if (e.detail.type === 'rows') {
+      assert.lengthOf(this.spreadsheets, 0,
+        '.spreadsheets length should be 0 since spreadsheet key was given');
+      assert.isTrue(this.rows.length > 0, '.rows was not populated');
+    }
+
+    assert.equal(this.$.cellrowsajax.url, '',
+      '#cellrowsajax should not be invoked for a public spreadsheet');
+
+    done();
+  });
+});
+</script>
+</body>
+</html>
diff --git a/bower_components/google-sheets/tests/tests.html b/bower_components/google-sheets/tests/tests.html
new file mode 100644
index 0000000..4241f19
--- /dev/null
+++ b/bower_components/google-sheets/tests/tests.html
@@ -0,0 +1,16 @@
+<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->
+
+<link rel="import" href="../../polymer-test-tools/tools.html">
+<script src="../../polymer-test-tools/mocha-htmltest.js"></script>
+
+<script>
+mocha.setup({ui: 'tdd', slow: 1000, timeout: 5000, htmlbase: ''});
+
+htmlSuite('google-sheet', function() {
+  htmlTest('google-sheet.html');
+  htmlTest('published.html');
+  htmlTest('private.html');
+});
+
+mocha.run();
+</script>
diff --git a/bower_components/google-signin/.bower.json b/bower_components/google-signin/.bower.json
new file mode 100644
index 0000000..4a4deff
--- /dev/null
+++ b/bower_components/google-signin/.bower.json
@@ -0,0 +1,46 @@
+{
+  "name": "google-signin",
+  "version": "1.2.0",
+  "description": "Web components to authenticate with Google services",
+  "homepage": "https://googlewebcomponents.github.io/google-signin",
+  "main": "google-signin.html",
+  "authors": [
+    "Addy Osmani",
+    "Randy Merrill"
+  ],
+  "license": "Apache-2",
+  "ignore": [
+    "/.*",
+    "/test/"
+  ],
+  "keywords": [
+    "web-component",
+    "web-components",
+    "polymer",
+    "sign-in",
+    "google",
+    "authentication"
+  ],
+  "dependencies": {
+    "polymer": "Polymer/polymer#^1.0.0",
+    "font-roboto": "PolymerElements/font-roboto#^1.0.0",
+    "iron-icon": "PolymerElements/iron-icon#^1.0.0",
+    "iron-icons": "PolymerElements/iron-icons#^1.0.0",
+    "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0",
+    "paper-ripple": "PolymerElements/paper-ripple#^1.0.0",
+    "paper-material": "PolymerElements/paper-material#^1.0.0",
+    "google-apis": "GoogleWebComponents/google-apis#^1.0.0"
+  },
+  "devDependencies": {
+    "iron-component-page": "PolymerElements/iron-component-page#^1.0.2"
+  },
+  "_release": "1.2.0",
+  "_resolution": {
+    "type": "version",
+    "tag": "v1.2.0",
+    "commit": "a1414b51b39e0a7dfe7031eb73a7733e2216579d"
+  },
+  "_source": "git://github.com/GoogleWebComponents/google-signin.git",
+  "_target": "^1.0.3",
+  "_originalSource": "GoogleWebComponents/google-signin"
+}
\ No newline at end of file
diff --git a/bower_components/google-signin/LICENSE b/bower_components/google-signin/LICENSE
new file mode 100644
index 0000000..52aea39
--- /dev/null
+++ b/bower_components/google-signin/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2014 Google Inc
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/bower_components/google-signin/README.md b/bower_components/google-signin/README.md
new file mode 100644
index 0000000..7c05417
--- /dev/null
+++ b/bower_components/google-signin/README.md
@@ -0,0 +1,4 @@
+google-signin
+================
+
+See the [component page](https://googlewebcomponents.github.io/google-signin) for more information.
diff --git a/bower_components/google-signin/bower.json b/bower_components/google-signin/bower.json
new file mode 100644
index 0000000..fecb402
--- /dev/null
+++ b/bower_components/google-signin/bower.json
@@ -0,0 +1,37 @@
+{
+  "name": "google-signin",
+  "version": "1.2.0",
+  "description": "Web components to authenticate with Google services",
+  "homepage": "https://googlewebcomponents.github.io/google-signin",
+  "main": "google-signin.html",
+  "authors": [
+    "Addy Osmani",
+    "Randy Merrill"
+  ],
+  "license": "Apache-2",
+  "ignore": [
+    "/.*",
+    "/test/"
+  ],
+  "keywords": [
+    "web-component",
+    "web-components",
+    "polymer",
+    "sign-in",
+    "google",
+    "authentication"
+  ],
+  "dependencies": {
+    "polymer": "Polymer/polymer#^1.0.0",
+    "font-roboto": "PolymerElements/font-roboto#^1.0.0",
+    "iron-icon": "PolymerElements/iron-icon#^1.0.0",
+    "iron-icons": "PolymerElements/iron-icons#^1.0.0",
+    "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0",
+    "paper-ripple": "PolymerElements/paper-ripple#^1.0.0",
+    "paper-material": "PolymerElements/paper-material#^1.0.0",
+    "google-apis": "GoogleWebComponents/google-apis#^1.0.0"
+  },
+  "devDependencies": {
+    "iron-component-page": "PolymerElements/iron-component-page#^1.0.2"
+  }
+}
diff --git a/bower_components/google-signin/demo/index.html b/bower_components/google-signin/demo/index.html
new file mode 100644
index 0000000..7b0283f
--- /dev/null
+++ b/bower_components/google-signin/demo/index.html
@@ -0,0 +1,150 @@
+<!doctype html>
+<!-- Copyright (c) 2014 Google Inc. All rights reserved. -->
+<html>
+<head>
+  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+
+  <title>google-signin Demo</title>
+
+  <script src="../../webcomponentsjs/webcomponents-lite.js"></script>
+  <link rel="import" href="../google-signin.html">
+  <link rel="import" href="../google-signin-aware.html">
+
+  <!-- Demo only styles -->
+  <style>
+    body {
+      font-family: 'RobotoDraft', 'Roboto', sans-serif;
+      line-height:1.2;
+      vertical-align:middle;
+      background: rgba(204, 204, 204, 0.31);
+    }
+
+
+    .map {
+      background: whitesmoke;
+      margin: .5rem -1.5rem 0 -1.5rem;
+      padding: 0.5rem;
+    }
+
+    h1 {
+      font-size: 2rem;
+      font-weight:200;
+      clear: both;
+    }
+
+    h1 strong {
+      font-weight:300;
+      color:#539D00;
+    }
+
+    h2 {
+      font-size:.9rem;
+      line-height:2.5;
+      color:gray;
+      font-weight:400;
+      clear: both;
+    }
+
+    .showcase {
+      display: inline-block;
+      margin-right: 2rem;
+      float: left;
+    }
+  </style>
+
+</head>
+
+<body>
+  <p>A <code>&lt;google-signin&gt;</code> element looks like this button:</p>
+
+  <p><google-signin brand="google" client-id="1054047045356-j8pgqgls9vdef3rl09hapoicumbte0bo.apps.googleusercontent.com"></google-signin>
+or like this if plus scopes are present
+  <google-signin brand="google-plus"></google-signin>
+  </p>
+  <p>Signin button can vary its appearance:</p>
+  <p>Width:
+    <google-signin brand="google" width="wide"></google-signin>
+    <google-signin brand="google" width="iconOnly"></google-signin>
+  Height:
+    <google-signin brand="google" height="tall"></google-signin>
+    <google-signin brand="google" height="standard"></google-signin>
+    <google-signin brand="google" height="short"></google-signin>
+  </p>
+  <p>
+  Theme:
+    <google-signin brand="google" theme="dark"></google-signin>
+    <google-signin brand="google" theme="light"></google-signin>
+    <google-signin brand="google-plus" theme="light"></google-signin>
+    <google-signin brand="google-plus" theme="light" raised></google-signin>
+  </p>
+  <!-- Demo the ability to use the google-signin-aware element. -->
+  <p><code>&lt;google-signin-aware&gt;</code> is a companion element.</p>
+  <p>You can use it inside your components to request additional scopes.</p>
+  <p>Every signin button will request all the scopes present in the document,
+  and change its appearance to match</p>
+  <p>For example, here is a signin-aware scope. You can change its scopes via popup</p>
+  <template id="awareness" is="dom-bind">
+    <div><code>&lt;google-signin-aware
+      <div>scope=
+        <select value="{{scope::change}}">
+          <option value="">None</option>
+          <option value="https://www.googleapis.com/auth/analytics">Google Analytics</option>
+          <option value="https://www.googleapis.com/auth/plus.login">Google Plus view circles</option>
+          <option value="https://www.googleapis.com/auth/youtube">YouTube</option>
+          <option value="https://www.googleapis.com/auth/calendar">Calendar</option>
+          <option value="profile">Profile info</option>
+        </select>
+      </div>
+      <div>offline=<input type="checkbox" checked="{{offline::change}}"></div>
+      <div>signedIn="<span>{{signedIn}}</span>"</div>
+      <div>isAuthorized="<span>{{isAuthorized}}</span>"</div>
+      <div>needAdditionalAuth:"<span>{{needAdditionalAuth}}</span>"&gt;</div>
+    </code></div>
+    <p>Every new scope you select will be added to requested scopes.</p>
+    <p>When you select a Google Plus scope, button will turn red.</p>
+      <google-signin></google-signin>
+    </p>
+    <google-signin-aware
+        scopes="{{scope}}"
+        signed-in="{{signedIn}}"
+        offline="{{offline}}"
+        is-authorized="{{isAuthorized}}"
+        need-additional-auth="{{needAdditionalAuth}}"
+        on-google-signin-aware-success="handleSignIn"
+        on-google-signin-offline-success="handleOffline"
+        on-google-signin-aware-signed-out="handleSignOut"></google-signin-aware>
+    <p>User name:<span>{{userName}}</span></p>
+    <p>Testing <code>google-signin-aware</code> events: <span>{{status}}</span></p>
+    <p>Testing <code>google-signin-offline</code> events: <span>{{offlineCode}}</span></p>
+    <p><button on-click="disconnect">Disconnect to start over</button></p>
+  </template>
+  <script>
+    var aware = document.querySelector('#awareness');
+    aware.status = 'Not granted';
+    aware.offlineCode = 'No offline login.';
+    aware.userName = 'N/A';
+    aware.handleSignIn = function(response) {
+      this.status = 'Signin granted';
+      // console.log('[Aware] Signin Response', response);
+      this.userName = gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile().getName();
+    };
+    aware.handleOffline = function(response) {
+      this.offlineCode = response.detail.code;
+    };
+    aware.handleSignOut = function(response) {
+      this.status = 'Signed out';
+      // console.log('[Aware] Signout Response', response);
+      this.userName = 'N/A';
+    };
+    aware.disconnect = function() {
+      var b = document.querySelector('google-signin');
+      var currentUser = gapi.auth2.getAuthInstance().currentUser.get();
+      if (currentUser) {
+        currentUser.disconnect();
+      }
+      gapi.auth2.getAuthInstance().signOut();
+    };
+
+  </script>
+</body>
+</html>
diff --git a/bower_components/google-signin/google-icons.html b/bower_components/google-signin/google-icons.html
new file mode 100644
index 0000000..a9a463c
--- /dev/null
+++ b/bower_components/google-signin/google-icons.html
@@ -0,0 +1,17 @@
+<!--
+Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+This code may only be used under the BSD style license found at https://polymer.github.io/LICENSE.txt
+The complete set of authors may be found at https://polymer.github.io/AUTHORS.txt
+The complete set of contributors may be found at https://polymer.github.io/CONTRIBUTORS.txt
+Code distributed by Google as part of the polymer project is also
+subject to an additional IP rights grant found at https://polymer.github.io/PATENTS.txt
+-->
+
+<link rel="import" href="../iron-icon/iron-icon.html">
+<link rel="import" href="../iron-iconset-svg/iron-iconset-svg.html">
+<iron-iconset-svg name="google" iconSize="24">
+<svg><defs>
+<g id="google"><path d="M16.3,13.4l-1.1-0.8c-0.4-0.3-0.8-0.7-0.8-1.4c0-0.7,0.5-1.3,1-1.6c1.3-1,2.6-2.1,2.6-4.3c0-2.1-1.3-3.3-2-3.9h1.7L18.9,0h-6.2C8.3,0,6.1,2.8,6.1,5.8c0,2.3,1.8,4.8,5,4.8h0.8c-0.1,0.3-0.4,0.8-0.4,1.3c0,1,0.4,1.4,0.9,2c-1.4,0.1-4,0.4-5.9,1.6c-1.8,1.1-2.3,2.6-2.3,3.7c0,2.3,2.1,4.5,6.6,4.5c5.4,0,8-3,8-5.9C18.8,15.7,17.7,14.6,16.3,13.4z M8.7,4.3c0-2.2,1.3-3.2,2.7-3.2c2.6,0,4,3.5,4,5.5c0,2.6-2.1,3.1-2.9,3.1C10,9.7,8.7,6.6,8.7,4.3z M12.3,22.3c-3.3,0-5.4-1.5-5.4-3.7c0-2.2,2-2.9,2.6-3.2c1.3-0.4,3-0.5,3.3-0.5c0.3,0,0.5,0,0.7,0c2.4,1.7,3.4,2.4,3.4,4C16.9,20.8,15,22.3,12.3,22.3z"/></g>
+<g id="google-plus"><path d="M21,10V7h-2v3h-3v2h3v3h2v-3h3v-2H21z M13.3,13.4l-1.1-0.8c-0.4-0.3-0.8-0.7-0.8-1.4c0-0.7,0.5-1.3,1-1.6c1.3-1,2.6-2.1,2.6-4.3c0-2.1-1.3-3.3-2-3.9h1.7L15.9,0H9.7C5.3,0,3.1,2.8,3.1,5.8c0,2.3,1.8,4.8,5,4.8h0.8c-0.1,0.3-0.4,0.8-0.4,1.3c0,1,0.4,1.4,0.9,2c-1.4,0.1-4,0.4-5.9,1.6c-1.8,1.1-2.3,2.6-2.3,3.7c0,2.3,2.1,4.5,6.6,4.5c5.4,0,8-3,8-5.9C15.8,15.7,14.7,14.6,13.3,13.4z M5.7,4.3c0-2.2,1.3-3.2,2.7-3.2c2.6,0,4,3.5,4,5.5c0,2.6-2.1,3.1-2.9,3.1C7,9.7,5.7,6.6,5.7,4.3z M9.3,22.3c-3.3,0-5.4-1.5-5.4-3.7c0-2.2,2-2.9,2.6-3.2c1.3-0.4,3-0.5,3.3-0.5c0.3,0,0.5,0,0.7,0c2.4,1.7,3.4,2.4,3.4,4C13.9,20.8,12,22.3,9.3,22.3z"/></g>
+</defs></svg>
+</iron-iconset-svg>
diff --git a/bower_components/google-signin/google-signin-aware.html b/bower_components/google-signin/google-signin-aware.html
new file mode 100644
index 0000000..e28d8be
--- /dev/null
+++ b/bower_components/google-signin/google-signin-aware.html
@@ -0,0 +1,703 @@
+<link rel="import" href="../polymer/polymer.html">
+<link rel="import" href="../google-apis/google-js-api.html">
+
+<script>
+  (function() {
+
+    /**
+     * Enum of attributes to be passed through to the login API call.
+     * @readonly
+     * @enum {string}
+     */
+    var ProxyLoginAttributes = {
+      'appPackageName': 'apppackagename',
+      'clientId': 'clientid',
+      'cookiePolicy': 'cookiepolicy',
+      'requestVisibleActions': 'requestvisibleactions',
+      'hostedDomain': 'hostedDomain'
+    };
+
+    /**
+     * AuthEngine does all interactions with gapi.auth2
+     *
+     * It is tightly coupled with <google-signin-aware> element
+     * The elements configure AuthEngine.
+     * AuthEngine propagates all authentication events to all google-signin-aware elements
+     *
+     * API used: https://developers.google.com/identity/sign-in/web/reference
+     *
+     */
+    var AuthEngine = {
+
+      /**
+       * oauth2 argument, set by google-signin-aware
+       */
+      _clientId: null,
+
+      get clientId() {
+        return this._clientId;
+      },
+
+      set clientId(val) {
+        if (this._clientId && val && val != this._clientId) {
+          throw new Error('clientId cannot change. Values do not match. New: ' + val + ' Old:' + this._clientId);
+        }
+        if (val) {
+          this._clientId = val;
+          this.initAuth2();
+        }
+      },
+
+      /**
+       * oauth2 argument, set by google-signin-aware
+       */
+      _cookiePolicy: 'single_host_origin',
+
+      get cookiePolicy() {
+        return this._cookiePolicy;
+      },
+
+      set cookiePolicy(val) {
+        if (val) {
+          this._cookiePolicy = val;
+        }
+      },
+
+      /**
+       * oauth2 argument, set by google-signin-aware
+       */
+      _appPackageName: '',
+
+      get appPackageName() {
+        return this._appPackageName;
+      },
+
+      set appPackageName(val) {
+        if (this._appPackageName && val && val != this._appPackageName) {
+          throw new Error('appPackageName cannot change. Values do not match. New: ' + val + ' Old: ' + this._appPackageName);
+        }
+        if (val) {
+          this._appPackageName = val;
+        }
+      },
+
+     /**
+       * oauth2 argument, set by google-signin-aware
+       */
+      _requestVisibleActions: '',
+
+      get requestVisibleactions() {
+        return this._requestVisibleActions;
+      },
+
+      set requestVisibleactions(val) {
+        if (this._requestVisibleActions && val && val != this._requestVisibleActions) {
+          throw new Error('requestVisibleactions cannot change. Values do not match. New: ' + val + ' Old: ' + this._requestVisibleActions);
+        }
+        if (val)
+          this._requestVisibleActions = val;
+      },
+
+     /**
+       * oauth2 argument, set by google-signin-aware
+       */
+      _hostedDomain: '',
+
+      get hostedDomain() {
+        return this._hostedDomain;
+      },
+
+      set hostedDomain(val) {
+        if (this._hostedDomain && val && val != this._hostedDomain) {
+          throw new Error('hostedDomain cannot change. Values do not match. New: ' + val + ' Old: ' + this._hostedDomain);
+        }
+        if (val)
+          this._hostedDomain = val;
+      },
+
+      /** Is offline access currently enabled in the google-signin-aware element? */
+      _offline: false,
+
+      get offline() {
+        return this._offline;
+      },
+
+      set offline(val) {
+        this._offline = val;
+        this.updateAdditionalAuth();
+      },
+
+      /** Should we force a re-prompt for offline access? */
+      _offlineAlwaysPrompt: false,
+
+      get offlineAlwaysPrompt() {
+        return this._offlineAlwaysPrompt;
+      },
+
+      set offlineAlwaysPrompt(val) {
+        this._offlineAlwaysPrompt = val;
+        this.updateAdditionalAuth();
+      },
+
+      /** Have we already gotten offline access from Google during this session? */
+      offlineGranted: false,
+
+      /** <google-js-api> */
+      _apiLoader: null,
+
+      /** an array of wanted scopes. oauth2 argument */
+      _requestedScopeArray: [],
+
+      /** _requestedScopeArray as string */
+      get requestedScopes() {
+        return this._requestedScopeArray.join(' ');
+      },
+
+      /** Is user signed in? */
+      _signedIn: false,
+
+      /** Currently granted scopes */
+      _grantedScopeArray: [],
+
+      /** True if additional authorization is required */
+      _needAdditionalAuth: true,
+
+      /** True if have google+ scopes */
+      _hasPlusScopes: false,
+
+      /**
+       * array of <google-signin-aware>
+       * state changes are broadcast to them
+       */
+      signinAwares: [],
+
+      init: function() {
+        this._apiLoader = document.createElement('google-js-api');
+        this._apiLoader.addEventListener('js-api-load', this.loadAuth2.bind(this));
+      },
+
+      loadAuth2: function() {
+        gapi.load('auth2', this.initAuth2.bind(this));
+      },
+
+      initAuth2: function() {
+        if (!('gapi' in window) || !('auth2' in window.gapi) || !this.clientId) {
+          return;
+        }
+        var auth = gapi.auth2.init({
+          'client_id': this.clientId,
+          'cookie_policy': this.cookiePolicy,
+          'scope': this.requestedScopes,
+          'hosted_domain': this.hostedDomain
+        });
+
+        auth.currentUser.listen(this.handleUserUpdate.bind(this));
+
+        auth.then(
+          function success() {
+          // Let the current user listener trigger the changes.
+          },
+          function error(error) {
+            console.error(error);
+          }
+        );
+      },
+
+      handleUserUpdate: function(newPrimaryUser) {
+        // update and broadcast currentUser
+        var isSignedIn = newPrimaryUser.isSignedIn();
+        if (isSignedIn != this._signedIn) {
+          this._signedIn = isSignedIn;
+          for (var i=0; i<this.signinAwares.length; i++) {
+            this.signinAwares[i]._setSignedIn(isSignedIn);
+          }
+        }
+
+        // update granted scopes
+        this._grantedScopeArray = this.strToScopeArray(
+          newPrimaryUser.getGrantedScopes());
+        // console.log(this._grantedScopeArray);
+        this.updateAdditionalAuth();
+
+        var response = newPrimaryUser.getAuthResponse();
+        for (var i=0; i<this.signinAwares.length; i++) {
+          this.signinAwares[i]._updateScopeStatus(response);
+        }
+      },
+
+      setOfflineCode: function(code) {
+        for (var i=0; i<this.signinAwares.length; i++) {
+          this.signinAwares[i]._updateOfflineCode(code);
+        }
+      },
+
+      /** convert scope string to scope array */
+      strToScopeArray: function(str) {
+        if (!str) {
+          return [];
+        }
+        // remove extra spaces, then split
+        var scopes = str.replace(/\ +/g, ' ').trim().split(' ');
+        for (var i=0; i<scopes.length; i++) {
+          scopes[i] = scopes[i].toLowerCase();
+           // Handle scopes that will be deprecated but are still returned with their old value
+          if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.profile') {
+            scopes[i] = 'profile';
+          }
+          if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.email') {
+            scopes[i] = 'email';
+          }
+        }
+        // return with duplicates filtered out
+        return scopes.filter( function(value, index, self) {
+          return self.indexOf(value) === index;
+        });
+      },
+
+      /** true if scopes have google+ scopes */
+      isPlusScope: function(scope) {
+        return (scope.indexOf('/auth/games') > -1)
+            || (scope.indexOf('auth/plus.') > -1 && scope.indexOf('auth/plus.me') < 0);
+      },
+
+      /** true if scopes have been granted */
+      hasGrantedScopes: function(scopeStr) {
+        var scopes = this.strToScopeArray(scopeStr);
+        for (var i=0; i< scopes.length; i++) {
+          if (this._grantedScopeArray.indexOf(scopes[i]) === -1)
+            return false;
+        }
+        return true;
+      },
+
+      /** request additional scopes */
+      requestScopes: function(newScopeStr) {
+        var newScopes = this.strToScopeArray(newScopeStr);
+        var scopesUpdated = false;
+        for (var i=0; i<newScopes.length; i++) {
+          if (this._requestedScopeArray.indexOf(newScopes[i]) === -1) {
+            this._requestedScopeArray.push(newScopes[i]);
+            scopesUpdated = true;
+          }
+        }
+        if (scopesUpdated) {
+          this.updateAdditionalAuth();
+          this.updatePlusScopes();
+        }
+      },
+
+      /** update status of _needAdditionalAuth */
+      updateAdditionalAuth: function() {
+        var needMoreAuth = false;
+        if ((this.offlineAlwaysPrompt || this.offline ) && !this.offlineGranted) {
+          needMoreAuth = true;
+        } else {
+          for (var i=0; i<this._requestedScopeArray.length; i++) {
+            if (this._grantedScopeArray.indexOf(this._requestedScopeArray[i]) === -1) {
+              needMoreAuth = true;
+              break;
+            }
+          }
+        }
+        if (this._needAdditionalAuth != needMoreAuth) {
+          this._needAdditionalAuth = needMoreAuth;
+          // broadcast new value
+          for (var i=0; i<this.signinAwares.length; i++) {
+            this.signinAwares[i]._setNeedAdditionalAuth(needMoreAuth);
+          }
+        }
+      },
+
+      updatePlusScopes: function() {
+        var hasPlusScopes = false;
+        for (var i = 0; i < this._requestedScopeArray.length; i++) {
+          if (this.isPlusScope(this._requestedScopeArray[i])) {
+            hasPlusScopes = true;
+            break;
+          }
+        }
+        if (this._hasPlusScopes != hasPlusScopes) {
+          this._hasPlusScopes = hasPlusScopes;
+          for (var i=0; i<this.signinAwares.length; i++) {
+            this.signinAwares[i]._setHasPlusScopes(hasPlusScopes);
+          }
+        }
+      },
+      /**
+       * attached <google-signin-aware>
+       * @param {<google-signin-aware>} aware element to add
+       */
+      attachSigninAware: function(aware) {
+        if (this.signinAwares.indexOf(aware) == -1) {
+          this.signinAwares.push(aware);
+          // Initialize aware properties
+          aware._setNeedAdditionalAuth(this._needAdditionalAuth);
+          aware._setSignedIn(this._signedIn);
+          aware._setHasPlusScopes(this._hasPlusScopes);
+        } else {
+          console.warn('signinAware attached more than once', aware);
+        }
+      },
+
+      detachSigninAware: function(aware) {
+        var index = this.signinAwares.indexOf(aware);
+        if (index != -1) {
+          this.signinAwares.splice(index, 1);
+        } else {
+          console.warn('Trying to detach unattached signin-aware');
+        }
+      },
+
+      /** returns scopes not granted */
+      getMissingScopes: function() {
+        return this._requestedScopeArray.filter( function(scope) {
+          return this._grantedScopeArray.indexOf(scope) === -1;
+        }.bind(this)).join(' ');
+      },
+
+      assertAuthInitialized: function() {
+        if (!this.clientId) {
+          throw new Error("AuthEngine not initialized. clientId has not been configured.");
+        }
+        if (!('gapi' in window)) {
+          throw new Error("AuthEngine not initialized. gapi has not loaded.");
+        }
+        if (!('auth2' in window.gapi)) {
+          throw new Error("AuthEngine not initialized. auth2 not loaded.");
+        }
+      },
+
+      /** pops up sign-in dialog */
+      signIn: function() {
+        this.assertAuthInitialized();
+        var params = {
+          'scope': this.getMissingScopes()
+        };
+
+        // Proxy specific attributes through to the signIn options.
+        Object.keys(ProxyLoginAttributes).forEach(function(key) {
+          if (this[key] && this[key] !== '') {
+            params[ProxyLoginAttributes[key]] = this[key];
+          }
+        }, this);
+
+        var promise;
+        var user = gapi.auth2.getAuthInstance().currentUser.get();
+        if (!(this.offline || this.offlineAlwaysPrompt)) {
+          if (user.getGrantedScopes()) {
+            // additional auth, skip multiple account dialog
+            promise = user.grant(params);
+          } else {
+            // initial signin
+            promise = gapi.auth2.getAuthInstance().signIn(params);
+          }
+        } else {
+          params.redirect_uri = 'postmessage';
+          if (this.offlineAlwaysPrompt) {
+            params.approval_prompt = 'force';
+          }
+
+          // Despite being documented at https://goo.gl/tiO0Bk
+          // It doesn't seem like user.grantOfflineAccess() actually exists in
+          // the current version of the Google Sign-In JS client we're using
+          // through GoogleWebComponents. So in the offline case, we will not
+          // distinguish between a first auth and an additional one.
+          promise = gapi.auth2.getAuthInstance().grantOfflineAccess(params);
+        }
+        promise.then(
+          function success(response) {
+            // If login was offline, response contains one string "code"
+            // Otherwise it contains the user object already
+            var newUser;
+            if (response.code) {
+              AuthEngine.offlineGranted = true;
+              newUser = gapi.auth2.getAuthInstance().currentUser.get();
+              AuthEngine.setOfflineCode(response.code);
+            } else {
+              newUser = response;
+            }
+
+            var authResponse = newUser.getAuthResponse();
+            // Let the current user listener trigger the changes.
+          },
+          function error(error) {
+            if ("Access denied." == error.reason) {
+              // Access denied is not an error, user hit cancel
+              return;
+            } else {
+              console.error(error);
+            }
+          }
+        );
+      },
+
+      /** signs user out */
+      signOut: function() {
+        this.assertAuthInitialized();
+        gapi.auth2.getAuthInstance().signOut().then(
+          function success() {
+          // Let the current user listener trigger the changes.
+          },
+          function error(error) {
+            console.error(error);
+          }
+        );
+      }
+    };
+
+    AuthEngine.init();
+
+/**
+`google-signin-aware` is used to enable authentication in custom elements by
+interacting with a google-signin element that needs to be present somewhere
+on the page.
+
+The `scopes` attribute allows you to specify which scope permissions are required
+(e.g do you want to allow interaction with the Google Drive API).
+
+The `google-signin-aware-success` event is triggered when a user successfully
+authenticates. If either `offline` or `offlineAlwaysPrompt` is set to true, successful
+authentication will also trigger the `google-signin-offline-success`event.
+The `google-signin-aware-signed-out` event is triggered when a user explicitly
+signs out via the google-signin element.
+
+You can bind to `isAuthorized` property to monitor authorization state.
+##### Example
+
+    <google-signin-aware scopes="https://www.googleapis.com/auth/drive"></google-signin-aware>
+
+
+##### Example with offline
+    <template id="awareness" is="dom-bind">
+      <google-signin-aware
+          scopes="https://www.googleapis.com/auth/drive"
+          offline
+          on-google-signin-aware-success="handleSignin"
+          on-google-signin-offline-success="handleOffline"></google-signin-aware>
+    <\/template>
+    <script>
+      var aware = document.querySelector('#awareness');
+      aware.handleSignin = function(response) {
+        var user = gapi.auth2.getAuthInstance().currentUser.get();
+        console.log('User name: ' + user.getBasicProfile().getName());
+      };
+      aware.handleOffline = function(response) {
+        console.log('Offline code received: ' + response.detail.code);
+        // Here you would POST response.detail.code to your webserver, which can
+        // exchange the authorization code for an access token. More info at:
+        // https://developers.google.com/identity/protocols/OAuth2WebServer
+      };
+    <\/script>
+*/
+    Polymer({
+
+      is: 'google-signin-aware',
+
+      /**
+       * Fired when this scope has been authorized
+       * @param {Object} result Authorization result.
+       * @event google-signin-aware-success
+       */
+      /**
+       * Fired when an offline authorization is successful.
+       * @param {Object} detail
+       * @param {string} detail.code The one-time authorization code from Google.
+       *     Your application can exchange this for an `access_token` and `refresh_token`
+       * @event google-signin-offline-success
+       */
+      /**
+       * Fired when this scope is not authorized
+       * @event google-signin-aware-signed-out
+       */
+      properties: {
+        /**
+         * App package name for android over-the-air installs.
+         * See the relevant [docs](https://developers.google.com/+/web/signin/android-app-installs)
+         */
+        appPackageName: {
+          type: String,
+          observer: '_appPackageNameChanged'
+        },
+        /**
+         * a Google Developers clientId reference
+         */
+        clientId: {
+          type: String,
+          observer: '_clientIdChanged'
+        },
+
+        /**
+         * The cookie policy defines what URIs have access to the session cookie
+         * remembering the user's sign-in state.
+         * See the relevant [docs](https://developers.google.com/+/web/signin/reference#determining_a_value_for_cookie_policy) for more information.
+         * @default 'single_host_origin'
+         */
+        cookiePolicy: {
+          type: String,
+          observer: '_cookiePolicyChanged'
+        },
+
+        /**
+         * The app activity types you want to write on behalf of the user
+         * (e.g http://schemas.google.com/AddActivity)
+         *
+         */
+        requestVisibleActions: {
+          type: String,
+          observer: '_requestVisibleActionsChanged'
+        },
+
+        /**
+         * The Google Apps domain to which users must belong to sign in.
+         * See the relevant [docs](https://developers.google.com/identity/sign-in/web/reference) for more information.
+         */
+        hostedDomain: {
+          type: String,
+          observer: '_hostedDomainChanged'
+        },
+
+       /**
+         * Allows for offline `access_token` retrieval during the signin process.
+         * See also `offlineAlwaysPrompt`. You only need to set one of the two; if both
+         * are set, the behavior of `offlineAlwaysPrompt` will override `offline`.
+         */
+        offline: {
+          type: Boolean,
+          value: false,
+          observer: '_offlineChanged'
+        },
+
+        /**
+          * Works the same as `offline` with the addition that it will always
+          * force a re-prompt to the user, guaranteeing that you will get a
+          * refresh_token even if the user has already granted offline access to
+          * this application. You only need to set one of `offline` or
+          * `offlineAlwaysPrompt`, not both.
+          */
+        offlineAlwaysPrompt: {
+          type: Boolean,
+          value: false,
+          observer: '_offlineAlwaysPromptChanged'
+        },
+
+       /**
+         * The scopes to provide access to (e.g https://www.googleapis.com/auth/drive)
+         * and should be space-delimited.
+         */
+        scopes: {
+          type: String,
+          value: 'profile',
+          observer: '_scopesChanged'
+        },
+
+        /**
+         * True if user is signed in
+         */
+        signedIn: {
+          type: Boolean,
+          notify: true,
+          readOnly: true
+        },
+
+        /**
+         * True if authorizations for *this* element have been granted
+         */
+        isAuthorized: {
+          type: Boolean,
+          notify: true,
+          readOnly: true,
+          value: false
+        },
+
+        /**
+         * True if additional authorizations for *any* element are required
+         */
+        needAdditionalAuth: {
+          type: Boolean,
+          notify: true,
+          readOnly: true
+        },
+
+        /**
+         * True if *any* element has google+ scopes
+         */
+        hasPlusScopes: {
+          type: Boolean,
+          value: false,
+          notify: true,
+          readOnly: true
+        }
+      },
+
+      attached: function() {
+        AuthEngine.attachSigninAware(this);
+      },
+
+      detached: function() {
+        AuthEngine.detachSigninAware(this);
+      },
+
+      /** pops up the authorization dialog */
+      signIn: function() {
+        AuthEngine.signIn();
+      },
+
+      /** signs user out */
+      signOut: function() {
+        AuthEngine.signOut();
+      },
+
+      _appPackageNameChanged: function(newName, oldName) {
+        AuthEngine.appPackageName = newName;
+      },
+
+      _clientIdChanged: function(newId, oldId) {
+        AuthEngine.clientId = newId;
+      },
+
+      _cookiePolicyChanged: function(newPolicy, oldPolicy) {
+        AuthEngine.cookiePolicy = newPolicy;
+      },
+
+      _requestVisibleActionsChanged: function(newVal, oldVal) {
+        AuthEngine.requestVisibleActions = newVal;
+      },
+
+      _hostedDomainChanged: function(newVal, oldVal) {
+        AuthEngine.hostedDomain = newVal;
+      },
+
+      _offlineChanged: function(newVal, oldVal) {
+        AuthEngine.offline = newVal;
+      },
+
+      _offlineAlwaysPromptChanged: function(newVal, oldVal) {
+        AuthEngine.offlineAlwaysPrompt = newVal;
+      },
+
+      _scopesChanged: function(newVal, oldVal) {
+        AuthEngine.requestScopes(newVal);
+        this._updateScopeStatus();
+      },
+
+      _updateScopeStatus: function(user) {
+        var newAuthorized = this.signedIn && AuthEngine.hasGrantedScopes(this.scopes);
+        if (newAuthorized !== this.isAuthorized) {
+          this._setIsAuthorized(newAuthorized);
+          if (newAuthorized) {
+            this.fire('google-signin-aware-success', user);
+          }
+          else {
+            this.fire('google-signin-aware-signed-out', user);
+          }
+        }
+      },
+
+      _updateOfflineCode: function(code) {
+        if (code) {
+          this.fire('google-signin-offline-success', {code: code});
+        }
+      }
+    });
+  })();
+</script>
diff --git a/bower_components/google-signin/google-signin.css b/bower_components/google-signin/google-signin.css
new file mode 100644
index 0000000..2b98356
--- /dev/null
+++ b/bower_components/google-signin/google-signin.css
@@ -0,0 +1,231 @@
+:host {
+  display: inline-block;
+  position: relative;
+  box-sizing: border-box;
+  margin: 0 0.29em;
+  background: transparent;
+  text-align: center;
+  font: inherit;
+  outline: none;
+  border-radius: 3px;
+  -webkit-user-select: none;
+  user-select: none;
+  cursor: pointer;
+  z-index: 0;
+}
+
+:host([disabled]) {
+  cursor: auto;
+  pointer-events: none;
+}
+
+:host([disabled]) #button {
+  background: #eaeaea;
+  color: #a8a8a8;
+}
+
+#button {
+  position: relative;
+  outline: none;
+  font-size: 14px;
+  font-weight: 400;
+  font-family: 'RobotoDraft','Roboto',arial,sans-serif;
+  white-space: nowrap;
+  border-radius: inherit;
+}
+
+iron-icon {
+  width: 22px;
+  height: 22px;
+  margin: 6px;
+}
+
+.icon {
+  display: inline-block;
+  vertical-align: middle;
+}
+
+#shadow {
+  border-radius: inherit;
+}
+
+#ripple {
+  pointer-events: none;
+}
+
+.button-content {
+  outline: none;
+}
+
+.buttonText {
+  display: inline-block;
+  vertical-align: middle;
+  padding-right: .8em;
+}
+
+/*
+ * Dark Theme
+ */
+.theme-dark {
+  background: #da4336;
+  color: #ffffff;
+  border: 1px solid transparent;
+}
+
+.theme-dark.signedIn-true.additionalAuth-false {
+  background: #999;
+  border: 1px solid #888;
+}
+
+.theme-dark.signedIn-true.additionalAuth-false:hover,
+.theme-dark.signedIn-true.additionalAuth-false:focus {
+  background: #aaa;
+}
+
+:host([noink]) .theme-dark:hover,
+:host([noink]) .theme-dark:focus {
+  background: #e74b37;
+}
+
+:host([noink]) .theme-dark.signedIn-true.additionalAuth-false:hover,
+:host([noink]) .theme-dark.signedIn-true.additionalAuth-false:focus {
+  background: #aaa;
+}
+
+/*
+ * Light Theme
+ */
+.theme-light {
+  background: #fff;
+  color: #737373;
+  border: 1px solid #d9d9d9;
+}
+
+.theme-light.signedIn-true.additionalAuth-false {
+  background: #c0c0c0;
+  color: #fff;
+  border: #888 1px solid;
+}
+
+.theme-light.signedIn-true.additionalAuth-false:hover,
+.theme-light.signedIn-true.additionalAuth-false:focus {
+  background: #aaa;
+}
+
+:host([noink]) .theme-light .button-content:hover,
+:host([noink]) .theme-light:focus {
+  border: 1px solid #c0c0c0;
+}
+
+:host([noink]) .theme-light.signedIn-true.additionalAuth-false:hover,
+:host([noink]) .theme-light.signedIn-true.additionalAuth-false:focus {
+  background: #aaa;
+}
+
+/*
+ * Icon Only Width
+ */
+.width-iconOnly .buttonText {
+  display: none;
+}
+
+/*
+ * Tall Height
+ */
+.height-tall .buttonText {
+  font-size: 15px;
+  font-weight: 700;
+}
+
+.height-tall iron-icon {
+  width: 30px;
+  height: 30px;
+  margin: 8px;
+}
+
+/*
+ * Short Height
+ */
+.height-short .buttonText {
+  font-size: 11px;
+}
+
+.height-short iron-icon {
+  width: 16px;
+  height: 16px;
+  margin: 3px;
+}
+
+
+/*
+ * Branding
+ */
+
+/* Google Scopes */
+
+/* Dark Theme */
+.brand-google.theme-dark {
+  background: #4184F3;
+  color: #fff;
+  border: 1px solid #3266d5;
+}
+
+.brand-google.theme-dark #ripple {
+  color: #1b39a8;
+}
+
+:host([noink]) .brand-google.theme-dark:hover,
+:host([noink]) .brand-google.theme-dark:focus {
+  background: #e74b37;
+}
+
+.brand-google.theme-light .icon {
+  color: #4184F3;
+}
+
+.brand-google.theme-light.signedIn-true.additionalAuth-false .icon {
+  color: #fff;
+}
+
+.brand-google.theme-light #ripple {
+  color: #444;
+}
+
+:host([noink]) .brand-google.theme-light:hover,
+:host([noink]) .brand-google.theme-light:focus {
+  border: 1px solid #c0c0c0;
+}
+
+.brand-google-plus.theme-dark {
+  background: #da4336;
+  color: #fff;
+  border: 1px solid transparent;
+}
+
+.brand-google-plus.theme-dark #ripple {
+  color: #c43828;
+}
+
+/* Light Theme */
+.brand-google-plus.theme-light {
+  background: #fff;
+  color: #737373;
+  border: 1px solid #d9d9d9;
+}
+
+.brand-google-plus.theme-light .icon {
+  color: #e74b37;
+}
+
+.brand-google-plus.theme-light.signedIn-true.additionalAuth-false .icon {
+  color: #fff;
+}
+
+.brand-google-plus.theme-light #ripple {
+  color: #400;
+}
+
+:host([noink]) .brand-google-plus.theme-light:hover,
+:host([noink]) .brand-google-plus.theme-light:focus {
+  border: 1px solid #c0c0c0;
+}
diff --git a/bower_components/google-signin/google-signin.html b/bower_components/google-signin/google-signin.html
new file mode 100644
index 0000000..b8496df
--- /dev/null
+++ b/bower_components/google-signin/google-signin.html
@@ -0,0 +1,539 @@
+<link rel="import" href="../polymer/polymer.html">
+<link rel="import" href="google-signin-aware.html">
+<link rel="import" href="../iron-icon/iron-icon.html">
+<link rel="import" href="../iron-icons/iron-icons.html">
+<link rel="import" href="../font-roboto/roboto.html">
+<link rel="import" href="../google-apis/google-js-api.html">
+<link rel="import" href="../paper-ripple/paper-ripple.html">
+<link rel="import" href="../paper-material/paper-material.html">
+<link rel="import" href="../iron-flex-layout/classes/iron-flex-layout.html">
+<link rel="import" href="google-icons.html">
+
+<dom-module id="google-signin">
+  <link rel="import" type="css" href="google-signin.css">
+  <template>
+    <google-signin-aware id="aware"
+      app-package-name="{{appPackageName}}"
+      client-id="{{clientId}}"
+      cookie-policy="{{cookiePolicy}}"
+      request-visible-actions="{{requestVisibleActions}}"
+      hosted-domain="{{hostedDomain}}"
+      offline="{{offline}}"
+      offline-always-prompt="{{offlineAlwaysPrompt}}"
+      scopes="{{scopes}}"
+      signed-in="{{signedIn}}"
+      is-authorized="{{isAuthorized}}"
+      need-additional-auth="{{needAdditionalAuth}}"
+      has-plus-scopes="{{hasPlusScopes}}"></google-signin-aware>
+    <template is="dom-if" if="{{raised}}">
+      <paper-material id="shadow" class="fit" elevation="2" animated></paper-material>
+    </template>
+    <div id="button"
+      class$="[[_computeButtonClass(height, width, theme, signedIn, _brand, needAdditionalAuth)]]">
+
+      <paper-ripple id="ripple" class="fit"></paper-ripple>
+      <!-- this div is needed to position the ripple behind text content -->
+      <div relative layout horizontal center-center>
+        <template is="dom-if" if="{{_computeButtonIsSignIn(signedIn, needAdditionalAuth)}}">
+          <div class="button-content signIn" tabindex="0"
+              on-click="signIn" on-keydown="_signInKeyPress">
+            <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span>
+            <span class="buttonText">{{_labelSignin}}</span>
+          </div>
+        </template>
+        <template is="dom-if" if="{{_computeButtonIsSignOut(signedIn, needAdditionalAuth) }}">
+          <div class="button-content signOut" tabindex="0"
+              on-click="signOut" on-keydown="_signOutKeyPress">
+            <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span>
+            <span class="buttonText">{{labelSignout}}</span>
+          </div>
+        </template>
+        <template is="dom-if" if="{{_computeButtonIsSignOutAddl(signedIn, needAdditionalAuth) }}">
+          <div class="button-content signIn" tabindex="0"
+              on-click="signIn" on-keydown="_signInKeyPress">
+            <span class="icon"><iron-icon icon="[[_brandIcon]]"></iron-icon></span>
+            <span class="buttonText">{{labelAdditional}}</span>
+          </div>
+        </template>
+      </div>
+
+    </div>
+  </template>
+</dom-module>
+<script>
+  (function() {
+
+    /**
+     * Enum brand values.
+     * @readonly
+     * @enum {string}
+     */
+    var BrandValue = {
+        GOOGLE: 'google',
+        PLUS: 'google-plus'
+    };
+
+    /**
+     * Enum height values.
+     * @readonly
+     * @enum {string}
+     */
+    var HeightValue = {
+      SHORT: 'short',
+      STANDARD: 'standard',
+      TALL: 'tall'
+    };
+
+    /**
+     * Enum button label default values.
+     * @readonly
+     * @enum {string}
+     */
+    var LabelValue = {
+      STANDARD: 'Sign in',
+      WIDE: 'Sign in with Google',
+      WIDE_PLUS: 'Sign in with Google+'
+    };
+
+    /**
+     * Enum theme values.
+     * @readonly
+     * @enum {string}
+     */
+    var ThemeValue = {
+      LIGHT: 'light',
+      DARK: 'dark'
+    };
+
+    /**
+     * Enum width values.
+     * @readonly
+     * @enum {string}
+     */
+    var WidthValue = {
+      ICON_ONLY: 'iconOnly',
+      STANDARD: 'standard',
+      WIDE: 'wide'
+    };
+
+/**
+&lt;google-signin&gt; is used to authenticate with Google, allowing you to interact
+with other Google APIs such as Drive and Google+.
+
+<img style="max-width:100%;" src="https://cloud.githubusercontent.com/assets/107076/6791176/5c868822-d16a-11e4-918c-ec9b84a2db45.png"/>
+
+If you do not need to show the button, use companion `<google-signin-aware>` element to declare scopes, check authentication state.
+
+#### Examples
+
+    <google-signin client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin>
+
+    <google-signin label-signin="Sign-in" client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin>
+
+    <google-signin theme="dark" width="iconOnly" client-id="..." scopes="https://www.googleapis.com/auth/drive"></google-signin>
+
+
+#### Notes
+
+The attribute `clientId` is provided in your Google Developers Console
+(https://console.developers.google.com).
+
+The `scopes` attribute allows you to specify which scope permissions are required
+(e.g do you want to allow interaction with the Google Drive API). Many APIs also
+need to be enabled in the Google Developers Console before you can use them.
+
+The `requestVisibleActions` attribute is necessary if you want to write app
+activities (https://developers.google.com/+/web/app-activities/) on behalf of
+the user. Please note that this attribute is only valid in combination with the
+plus.login scope (https://www.googleapis.com/auth/plus.login).
+
+The `offline` attribute allows you to get an auth code which your server can
+redeem for an offline access token
+(https://developers.google.com/identity/sign-in/web/server-side-flow).
+You can also set `offline-always-prompt` instead of `offline` to ensure that your app
+will re-prompt the user for offline access and generate a working `refresh_token`
+even if they have already granted offline access to your app in the past.
+
+Use label properties to customize prompts.
+
+The button can be styled in using the `height`, `width`, and `theme` attributes.
+These attributes help you follow the Google+ Sign-In button branding guidelines
+(https://developers.google.com/+/branding-guidelines).
+
+The `google-signin-success` event is triggered when a user successfully authenticates
+and `google-signed-out` is triggered when user signs out.
+You can also use `isAuthorized` attribute to observe user's authentication state.
+
+Additional events, such as `google-signout-attempted` are
+triggered when the user attempts to sign-out and successfully signs out.
+
+When requesting offline access, the `google-signin-offline-success` event is
+triggered when the user successfully consents with offline support.
+
+The `google-signin-necessary` event is fired when scopes requested via
+google-signin-aware elements require additional user permissions.
+
+#### Testing
+
+By default, the demo accompanying this element is setup to work on localhost with
+port 8080. That said, you *should* update the `clientId` to your own one for
+any apps you're building. See the Google Developers Console
+(https://console.developers.google.com) for more info.
+
+@demo
+*/
+
+    Polymer({
+
+      is: 'google-signin',
+
+    /**
+     * Fired when user is signed in.
+     * You can use auth2 api to retrieve current user: `gapi.auth2.getAuthInstance().currentUser.get();`
+     * @event google-signin-success
+     */
+
+    /**
+     * Fired when the user is signed-out.
+     * @event google-signed-out
+     */
+
+    /**
+     * Fired if user requires additional authorization
+     * @event google-signin-necessary
+     */
+
+    /**
+     * Fired when signed in, and scope has been authorized
+     * @param {Object} result Authorization result.
+     * @event google-signin-aware-success
+     */
+
+    /**
+     * Fired when an offline authorization is successful.
+     * @event google-signin-offline-success
+     * @param {Object} detail
+     * @param {string} detail.code The one-time authorization code from Google.
+     *     Your application can exchange this for an `access_token` and `refresh_token`
+     */
+
+    /**
+     * Fired when this scope is not authorized
+     * @event google-signin-aware-signed-out
+     */
+      properties: {
+        /**
+         * App package name for android over-the-air installs.
+         * See the relevant [docs](https://developers.google.com/+/web/signin/android-app-installs)
+         */
+        appPackageName: {
+          type: String,
+          value: ''
+        },
+
+        /**
+         * The brand being used for logo and styling.
+         *
+         * @default 'google'
+         */
+        brand: {
+          type: String,
+          value: ''
+        },
+
+        /** @private */
+        _brand: {
+          type: String,
+          computed: '_computeBrand(brand, hasPlusScopes)'
+        },
+
+        /**
+         * a Google Developers clientId reference
+         */
+        clientId: {
+          type: String,
+          value: ''
+        },
+
+        /**
+         * The cookie policy defines what URIs have access to the session cookie
+         * remembering the user's sign-in state.
+         * See the relevant [docs](https://developers.google.com/+/web/signin/reference#determining_a_value_for_cookie_policy) for more information.
+         *
+         * @default 'single_host_origin'
+         */
+        cookiePolicy: {
+          type: String,
+          value: ''
+        },
+
+        /**
+         * The height to use for the button.
+         *
+         * Available options: short, standard, tall.
+         *
+         * @type {HeightValue}
+         */
+        height: {
+          type: String,
+          value: 'standard'
+        },
+
+        /**
+         * By default the ripple expands to fill the button. Set this to true to
+         * constrain the ripple to a circle within the button.
+         */
+        fill: {
+          type: Boolean,
+          value: true
+        },
+
+        /**
+         * An optional label for the button for additional permissions.
+         */
+        labelAdditional: {
+          type: String,
+          value: 'Additional permissions required'
+        },
+
+        /**
+         * An optional label for the sign-in button.
+         */
+        labelSignin: {
+          type: String,
+          value: ''
+        },
+
+        _labelSignin: {
+          type: String,
+          computed: '_computeSigninLabel(labelSignin, width, _brand)'
+        },
+
+        /**
+         * An optional label for the sign-out button.
+         */
+        labelSignout: {
+          type: String,
+          value: 'Sign out'
+        },
+
+        /**
+         * If true, the button will be styled with a shadow.
+         */
+        raised: {
+          type: Boolean,
+          value: false
+        },
+
+        /**
+         * The app activity types you want to write on behalf of the user
+         * (e.g http://schemas.google.com/AddActivity)
+         */
+        requestVisibleActions: {
+          type: String,
+          value: ''
+        },
+
+        /**
+         * The Google Apps domain to which users must belong to sign in.
+         * See the relevant [docs](https://developers.google.com/identity/sign-in/web/reference) for more information.
+         */
+        hostedDomain: {
+          type: String,
+          value: ''
+        },
+
+        /**
+         * Allows for offline `access_token` retrieval during the signin process.
+         */
+        offline: {
+          type: Boolean,
+          value: false
+        },
+
+        /**
+         * Forces a re-prompt, even if the user has already granted offline
+         * access to your application in the past. You only need one of
+         * `offline` and `offlineAlwaysPrompt`.
+         */
+        offlineAlwaysPrompt: {
+          type: Boolean,
+          value: false
+        },
+
+        /**
+         * The scopes to provide access to (e.g https://www.googleapis.com/auth/drive)
+         * and should be space-delimited.
+         */
+        scopes: {
+          type: String,
+          value: ''
+        },
+
+        /**
+         * The theme to use for the button.
+         *
+         * Available options: light, dark.
+         *
+         * @attribute theme
+         * @type {ThemeValue}
+         * @default 'dark'
+         */
+        theme: {
+          type: String,
+          value: 'dark'
+        },
+
+        /**
+         * The width to use for the button.
+         *
+         * Available options: iconOnly, standard, wide.
+         *
+         * @type {WidthValue}
+         */
+        width: {
+          type: String,
+          value: 'standard'
+        },
+
+        _brandIcon: {
+          type: String,
+          computed: '_computeIcon(_brand)'
+        },
+
+        /**
+         * True if *any* element has google+ scopes
+         */
+        hasPlusScopes: {
+          type: Boolean,
+          notify: true,
+          value: false
+        },
+
+        /**
+         * True if additional authorization required globally
+         */
+        needAdditionalAuth: {
+          type: Boolean,
+          value: false
+        },
+
+        /**
+         * Is user signed in?
+         */
+        signedIn: {
+          type: Boolean,
+          notify: true,
+          value: false,
+          observer: '_observeSignedIn'
+        },
+
+        /**
+         * True if authorizations for *this* element have been granted
+         */
+        isAuthorized: {
+          type: Boolean,
+          notify: true,
+          value: false
+        }
+
+      },
+
+      _computeButtonClass: function(height, width, theme, signedIn, brand, needAdditionalAuth) {
+        return "height-" + height + " width-" + width + " theme-" + theme + " signedIn-" + signedIn + " brand-" + brand + "  additionalAuth-" + needAdditionalAuth;
+      },
+
+      _computeIcon: function(brand) {
+        return "google:" + brand;
+      },
+
+      /* Button state computed */
+      _computeButtonIsSignIn: function(signedIn, additionalAuth) {
+        return !signedIn;
+      },
+
+      _computeButtonIsSignOut: function(signedIn, additionalAuth) {
+        return signedIn && !additionalAuth;
+      },
+
+      _computeButtonIsSignOutAddl: function(signedIn, additionalAuth) {
+        return signedIn && additionalAuth;
+      },
+
+      _computeBrand: function(attrBrand, hasPlusScopes) {
+        var newBrand;
+        if (attrBrand) {
+          newBrand = attrBrand;
+        } else if (hasPlusScopes) {
+          newBrand = BrandValue.PLUS;
+        } else {
+          newBrand = BrandValue.GOOGLE;
+        };
+        return newBrand;
+      },
+
+      _observeSignedIn: function(newVal, oldVal) {
+        if (newVal) {
+          if (this.needAdditionalAuth)
+            this.fire('google-signin-necessary');
+          this.fire('google-signin-success');
+        }
+        else
+          this.fire('google-signed-out');
+      },
+
+      /**
+       * Determines the proper label based on the attributes.
+       */
+      _computeSigninLabel: function(labelSignin, width, _brand) {
+        if (labelSignin) {
+          return labelSignin;
+        } else {
+          switch(width) {
+
+            case WidthValue.WIDE:
+              return (_brand == BrandValue.PLUS) ?
+                LabelValue.WIDE_PLUS : LabelValue.WIDE;
+
+            case WidthValue.STANDARD:
+              return LabelValue.STANDARD;
+
+            case WidthValue.ICON_ONLY:
+              return '';
+
+            default:
+              console.warn("bad width value: ", width);
+              return LabelValue.STANDARD;
+          }
+        }
+      },
+
+      /** Sign in user. Opens the authorization dialog for signing in.
+       * The dialog will be blocked by a popup blocker unless called inside click handler.
+       */
+      signIn: function () {
+        this.$.aware.signIn();
+      },
+
+      _signInKeyPress: function (e) {
+        if (e.which == 13 || e.keyCode == 13 || e.which == 32 || e.keyCode == 32) {
+          e.preventDefault();
+          this.signIn();
+        }
+      },
+
+      /** Sign out the user */
+      signOut: function () {
+        this.fire('google-signout-attempted');
+        this.$.aware.signOut();
+      },
+
+      _signOutKeyPress: function (e) {
+        if (e.which == 13 || e.keyCode == 13 || e.which == 32 || e.keyCode == 32) {
+          e.preventDefault();
+          this.signOut();
+        }
+      }
+    });
+  }());
+</script>
diff --git a/bower_components/google-signin/index.html b/bower_components/google-signin/index.html
new file mode 100644
index 0000000..e8f85ae
--- /dev/null
+++ b/bower_components/google-signin/index.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<!-- Copyright (c) 2014 Google Inc. All rights reserved. -->
+<html>
+<head>
+
+  <script src="../webcomponentsjs/webcomponents-lite.js"></script>
+  <link rel="import" href="../iron-component-page/iron-component-page.html">
+
+</head>
+<body>
+
+  <iron-component-page></iron-component-page>
+
+</body>
+</html>
diff --git a/datalets/graph-datalet/graph-datalet.html b/datalets/graph-datalet/graph-datalet.html
index 4c9fbca..985fbcf 100755
--- a/datalets/graph-datalet/graph-datalet.html
+++ b/datalets/graph-datalet/graph-datalet.html
@@ -246,12 +246,12 @@ Example:
                                                                                                     return "fill:" + t.color + "; stroke:white"
                                                                                                 });
 
+                d3.selectAll(".drag").call(force.drag), this.svg.selectAll("g.node").call(this.text);
+
                 d3.selectAll('circle').attr("style", function(t){
-                    return  "fill :" + (t.image ? 'url(#image_' + t.id +')' : t.color)
+                    return  (t != undefined) ? "fill :" + (t.image ? 'url(#image_' + t.id +')' : t.color) : "";
                 });
 
-                d3.selectAll(".drag").call(force.drag), this.svg.selectAll("g.node").call(this.text);
-
             },
 
             zoom: function() {
diff --git a/datalets/test-google-components/test.html b/datalets/test-google-components/test.html
new file mode 100644
index 0000000..1b40081
--- /dev/null
+++ b/datalets/test-google-components/test.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Google spreadsheet and document test page</title>
+
+    <script type="text/javascript" src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+    <script type="text/javascript" src="../shared_js/jquery-1.11.2.min.js"></script>
+
+    <link rel="import"  href="../../bower_components/google-sheets/google-sheets.html">
+    <link rel="import"  href="../../bower_components/iron-flex-layout/iron-flex-layout.html">
+    <link rel="import"  href="../../bower_components/iron-icons/iron-icons.html">
+    <link rel="import"  href="../../bower_components/paper-fab/paper-fab.html">
+    <link rel="import"  href="../linechart-datalet/linechart-datalet.html">
+
+    <style>
+        #sheet{
+            height: 400px;
+            width: 40%;
+            margin: 40px;
+        }
+
+        #showVisualization{
+            background-color: #2196F3;
+            color: #ffffff;
+            margin-left: 50px;
+        }
+
+
+        #datalet_placeholder{
+            height: 400px;
+            width: 40%;
+            margin: 40px;/
+        }
+
+        #doc{
+            height: 400px;
+            width: 100%;
+        }
+    </style>
+
+    <script>
+
+        getData = function()
+        {
+            var sheet = document.querySelector('google-sheets');
+
+            sheet.addEventListener('google-sheet-data', function(e)
+            {
+                var data = [];
+                //get the keys
+                for(var key in this.rows[0]) {
+                    if (key.match(/gsx\$*/)) {
+                        data.push({name: key.replace("gsx$", ""), data: []});
+                    }
+                }
+                //fill the structure with values
+                for(var row in this.rows){
+                    var i = 0;
+                    for(var key in this.rows[row]){
+                        if(key.match(/gsx\$*/)){
+                            data[i++].data.push(this.rows[row][key].$t.replace("'",""));
+                        }
+                    }
+
+                }
+
+                $("#datalet_placeholder").html('<linechart-datalet ' +
+                        'data-url="https://docs.google.com/spreadsheets/d/1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao" ' +
+                            //'fields=\'[]\'' +
+                        'title="Google spreadsheet realtime data visualization"' +
+                        'data=\'' + JSON.stringify(data) + '\'>' +
+                        '</linechart-datalet>');
+
+                $("#spreadsheet_url").html('<a href="' + this.openInGoogleDocsUrl + '" target="_blank">' + this.openInGoogleDocsUrl + '</a>');
+
+            });
+
+            sheet.addEventListener('error', function(e) {
+                // e.detail.response
+            });
+
+        }
+
+        refresh = function(){
+            $("#sheet-component").html('<google-sheets key="1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao" published></google-sheets>');
+            getData();
+        }
+
+        $( document ).ready(function() {
+            getData();
+        });
+
+    </script>
+
+</head>
+<body is="dom-bind">
+    <div class="layout vertical">
+        <div class="layout vertical">
+            <div class="layout horizontal" style="width: 100%;">
+                <h1>Google Spreadsheet realtime visualization with Datalet</h1>
+                <paper-fab id="showVisualization" icon="refresh" onClick="refresh()"></paper-fab>
+            </div>
+            <div class="layout horizontal" style="width: 100%;">
+                <h3 id="spreadsheet_url"></h3>
+            </div>
+            <div class="layout horizontal">
+                <iframe id="sheet" src="https://docs.google.com/spreadsheets/d/1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao/edit?usp=sharing?embedded=true"></iframe>
+                <div id="datalet_placeholder"></div>
+            </div>
+        </div>
+
+        <div class="layout vertical">
+            <h1 style="width: 100%;margin-top: 100px;">Google Docs realtime visualization with Datalet</h1>
+            <iframe id="doc" src="https://docs.google.com/document/d/1IIfmqTGBpTkEMHPnuxJeoOJI3u-8wQu9dkf49X2T6SE/edit?usp=sharing?embedded=true"></iframe>
+        </div>
+    </div>
+
+    <div id="sheet-component">
+        <google-sheets key="1EYJ88pOsbc0GkUTTdPhuReUjHewk1fhis9VZBXes_Ao" published></google-sheets>
+    </div>
+
+</body>
+</html>
\ No newline at end of file